forked from GitHub-Mirrors/foundry-sw5e
Update Core to 1.4.1
Update Core to 1.4.1 and internal Version to 1.4.1.R1-A8
This commit is contained in:
parent
f16383841b
commit
5bb253d9c3
56 changed files with 5440 additions and 3827 deletions
41
lang/en.json
41
lang/en.json
|
@ -41,6 +41,8 @@
|
||||||
"SETTINGS.5eDiagPHB": "PHB: Equidistant (5/5/5)",
|
"SETTINGS.5eDiagPHB": "PHB: Equidistant (5/5/5)",
|
||||||
"SETTINGS.5eInitTBL": "Append the raw Dexterity ability score to break ties in Initiative.",
|
"SETTINGS.5eInitTBL": "Append the raw Dexterity ability score to break ties in Initiative.",
|
||||||
"SETTINGS.5eInitTBN": "Initiative Dexterity Tiebreaker",
|
"SETTINGS.5eInitTBN": "Initiative Dexterity Tiebreaker",
|
||||||
|
"SETTINGS.5eMetricN": "Use Metric Weight Units",
|
||||||
|
"SETTINGS.5eMetricL": "Replaces all reference to lbs with kgs and updates the encumberance calculations to use metric weight units.",
|
||||||
"SETTINGS.5eNoExpL": "Remove experience bars from character sheets.",
|
"SETTINGS.5eNoExpL": "Remove experience bars from character sheets.",
|
||||||
"SETTINGS.5eNoExpN": "Disable Experience Tracking",
|
"SETTINGS.5eNoExpN": "Disable Experience Tracking",
|
||||||
"SETTINGS.5eReset": "Reset",
|
"SETTINGS.5eReset": "Reset",
|
||||||
|
@ -58,6 +60,7 @@
|
||||||
"SW5E.AbbreviationConc": "Conc.",
|
"SW5E.AbbreviationConc": "Conc.",
|
||||||
"SW5E.AbbreviationCR": "CR",
|
"SW5E.AbbreviationCR": "CR",
|
||||||
"SW5E.AbbreviationDC": "DC",
|
"SW5E.AbbreviationDC": "DC",
|
||||||
|
"SW5E.AbbreviationKgs": "kgs",
|
||||||
"SW5E.AbbreviationLbs": "lbs.",
|
"SW5E.AbbreviationLbs": "lbs.",
|
||||||
"SW5E.AbbreviationLevel": "Lvl.",
|
"SW5E.AbbreviationLevel": "Lvl.",
|
||||||
"SW5E.AbbreviationLR": "LR",
|
"SW5E.AbbreviationLR": "LR",
|
||||||
|
@ -127,6 +130,19 @@
|
||||||
"SW5E.ArchetypeName": "Archetype Name",
|
"SW5E.ArchetypeName": "Archetype Name",
|
||||||
"SW5E.Archetypes": "Archetypes",
|
"SW5E.Archetypes": "Archetypes",
|
||||||
"SW5E.ArmorClass": "Armor Class",
|
"SW5E.ArmorClass": "Armor Class",
|
||||||
|
"SW5E.ArmorClassEquipment": "Equipped Armor",
|
||||||
|
"SW5E.ArmorClassFlat": "Flat",
|
||||||
|
"SW5E.ArmorClassUnarmored": "Unarmored",
|
||||||
|
"SW5E.ArmorClassNatural": "Natural Armor",
|
||||||
|
"SW5E.ArmorClassMage": "Mage Armor",
|
||||||
|
"SW5E.ArmorClassDraconic": "Draconic Resilience",
|
||||||
|
"SW5E.ArmorClassUnarmoredMonk": "Unarmored Defense (Monk)",
|
||||||
|
"SW5E.ArmorClassUnarmoredBarbarian": "Unarmored Defense (Barbarian)",
|
||||||
|
"SW5E.ArmorClassCustom": "Custom Formula",
|
||||||
|
"SW5E.ArmorConfig": "Configure Armor",
|
||||||
|
"SW5E.ArmorConfigHint": "Fill in the above box to override automatically calculated armor class.",
|
||||||
|
"SW5E.ArmorClassCalculation": "Calculation",
|
||||||
|
"SW5E.ArmorClassFormula": "Formula",
|
||||||
"SW5E.ArmorProperAbsorptive": "Absorptive",
|
"SW5E.ArmorProperAbsorptive": "Absorptive",
|
||||||
"SW5E.ArmorProperAgile": "Agile",
|
"SW5E.ArmorProperAgile": "Agile",
|
||||||
"SW5E.ArmorProperAnchor": "Anchor",
|
"SW5E.ArmorProperAnchor": "Anchor",
|
||||||
|
@ -171,6 +187,7 @@
|
||||||
"SW5E.Background": "Background",
|
"SW5E.Background": "Background",
|
||||||
"SW5E.Biography": "Biography",
|
"SW5E.Biography": "Biography",
|
||||||
"SW5E.Bonds": "Bonds",
|
"SW5E.Bonds": "Bonds",
|
||||||
|
"SW5E.Bonus": "Bonus",
|
||||||
"SW5E.BonusAbilityCheck": "Global Ability Check Bonus",
|
"SW5E.BonusAbilityCheck": "Global Ability Check Bonus",
|
||||||
"SW5E.BonusAbilitySave": "Global Saving Throw Bonus",
|
"SW5E.BonusAbilitySave": "Global Saving Throw Bonus",
|
||||||
"SW5E.BonusAbilitySkill": "Global Skill Check Bonus",
|
"SW5E.BonusAbilitySkill": "Global Skill Check Bonus",
|
||||||
|
@ -211,6 +228,7 @@
|
||||||
"SW5E.ClassOriginal": "Original Class",
|
"SW5E.ClassOriginal": "Original Class",
|
||||||
"SW5E.ClassSaves": "Saving Throws",
|
"SW5E.ClassSaves": "Saving Throws",
|
||||||
"SW5E.ClassSkillsChosen": "Chosen Class Skills",
|
"SW5E.ClassSkillsChosen": "Chosen Class Skills",
|
||||||
|
"SW5E.ClassSkillsEligible": "Eligible Class Skills",
|
||||||
"SW5E.ClassSkillsNumber": "Number of Starting Skills",
|
"SW5E.ClassSkillsNumber": "Number of Starting Skills",
|
||||||
"SW5E.Collapse": "Collapse/Expand",
|
"SW5E.Collapse": "Collapse/Expand",
|
||||||
"SW5E.ComponentMaterial": "Material",
|
"SW5E.ComponentMaterial": "Material",
|
||||||
|
@ -340,6 +358,8 @@
|
||||||
"SW5E.Disadvantage": "Disadvantage",
|
"SW5E.Disadvantage": "Disadvantage",
|
||||||
"SW5E.DistAny": "Any",
|
"SW5E.DistAny": "Any",
|
||||||
"SW5E.DistFt": "Feet",
|
"SW5E.DistFt": "Feet",
|
||||||
|
"SW5E.DistKm": "Kilometers",
|
||||||
|
"SW5E.DistM": "Meters",
|
||||||
"SW5E.DistMi": "Miles",
|
"SW5E.DistMi": "Miles",
|
||||||
"SW5E.DistSelf": "Self",
|
"SW5E.DistSelf": "Self",
|
||||||
"SW5E.DistTouch": "Touch",
|
"SW5E.DistTouch": "Touch",
|
||||||
|
@ -351,6 +371,8 @@
|
||||||
"SW5E.EffectInactive": "Inactive Effects",
|
"SW5E.EffectInactive": "Inactive Effects",
|
||||||
"SW5E.EffectNew": "New Effect",
|
"SW5E.EffectNew": "New Effect",
|
||||||
"SW5E.EffectPassive": "Passive Effects",
|
"SW5E.EffectPassive": "Passive Effects",
|
||||||
|
"SW5E.EffectUnavailable": "Unavailable Effects",
|
||||||
|
"SW5E.EffectUnavailableInfo": "Source item must be equipped or attuned to activate these",
|
||||||
"SW5E.Effects": "Effects",
|
"SW5E.Effects": "Effects",
|
||||||
"SW5E.EffectTemporary": "Temporary Effects",
|
"SW5E.EffectTemporary": "Temporary Effects",
|
||||||
"SW5E.EffectsCategoryInactive": "Inactive Effects",
|
"SW5E.EffectsCategoryInactive": "Inactive Effects",
|
||||||
|
@ -581,11 +603,18 @@
|
||||||
"SW5E.ItemName": "Item Name",
|
"SW5E.ItemName": "Item Name",
|
||||||
"SW5E.ItemNew": "New {type}",
|
"SW5E.ItemNew": "New {type}",
|
||||||
"SW5E.ItemNoUses": "{name} has no available uses remaining.",
|
"SW5E.ItemNoUses": "{name} has no available uses remaining.",
|
||||||
|
"SW5E.ItemRarityCommon": "common",
|
||||||
|
"SW5E.ItemRarityUncommon": "uncommon",
|
||||||
|
"SW5E.ItemRarityRare": "rare",
|
||||||
|
"SW5E.ItemRarityVeryRare": "very rare",
|
||||||
|
"SW5E.ItemRarityLegendary": "legendary",
|
||||||
|
"SW5E.ItemRarityArtifact": "artifact",
|
||||||
"SW5E.ItemRechargeCheck": "{name} recharge check",
|
"SW5E.ItemRechargeCheck": "{name} recharge check",
|
||||||
"SW5E.ItemRechargeFailure": "failure!",
|
"SW5E.ItemRechargeFailure": "failure!",
|
||||||
"SW5E.ItemRechargeSuccess": "success!",
|
"SW5E.ItemRechargeSuccess": "success!",
|
||||||
"SW5E.ItemRequiredStr": "Required Strength",
|
"SW5E.ItemRequiredStr": "Required Strength",
|
||||||
"SW5E.ItemToolProficiency": "Tool Proficiency",
|
"SW5E.ItemToolProficiency": "Tool Proficiency",
|
||||||
|
"SW5E.ItemToolType": "Tool Type",
|
||||||
"SW5E.ItemTypeArchetype": "Archetype",
|
"SW5E.ItemTypeArchetype": "Archetype",
|
||||||
"SW5E.ItemTypeBackground": "Background",
|
"SW5E.ItemTypeBackground": "Background",
|
||||||
"Sw5E.ItemTypeBackgroundPl": "Backgrounds",
|
"Sw5E.ItemTypeBackgroundPl": "Backgrounds",
|
||||||
|
@ -892,6 +921,8 @@
|
||||||
"SW5E.Price": "Price",
|
"SW5E.Price": "Price",
|
||||||
"SW5E.Proficiency": "Proficiency",
|
"SW5E.Proficiency": "Proficiency",
|
||||||
"SW5E.Proficient": "Proficient",
|
"SW5E.Proficient": "Proficient",
|
||||||
|
"SW5E.PropertyBase": "Base",
|
||||||
|
"SW5E.PropertyTotal": "Total",
|
||||||
"SW5E.Quantity": "Quantity",
|
"SW5E.Quantity": "Quantity",
|
||||||
"SW5E.Range": "Range",
|
"SW5E.Range": "Range",
|
||||||
"SW5E.Rank": "Rank",
|
"SW5E.Rank": "Rank",
|
||||||
|
@ -1121,7 +1152,7 @@
|
||||||
"SW5E.ToolSurveyor": "Surveyor's Tools",
|
"SW5E.ToolSurveyor": "Surveyor's Tools",
|
||||||
"SW5E.ToolSynthweaver": "Synthweavers's Tools",
|
"SW5E.ToolSynthweaver": "Synthweavers's Tools",
|
||||||
"SW5E.ToolTinker": "Tinker's Tools",
|
"SW5E.ToolTinker": "Tinker's Tools",
|
||||||
"SW5E.ToolVehicle": "Vehicle (Land or Water)",
|
"SW5E.ToolVehicle": "Vehicles",
|
||||||
"SW5E.TraitArmorProf": "Armor Proficiencies",
|
"SW5E.TraitArmorProf": "Armor Proficiencies",
|
||||||
"SW5E.TraitSave": "Update",
|
"SW5E.TraitSave": "Update",
|
||||||
"SW5E.TraitSelectorSpecial": "Special (Split with Semi-Colon)",
|
"SW5E.TraitSelectorSpecial": "Special (Split with Semi-Colon)",
|
||||||
|
@ -1148,10 +1179,16 @@
|
||||||
"SW5E.VehicleEquipment": "Vehicle Equipment",
|
"SW5E.VehicleEquipment": "Vehicle Equipment",
|
||||||
"SW5E.VehicleMishap": "Mishap",
|
"SW5E.VehicleMishap": "Mishap",
|
||||||
"SW5E.VehiclePassengers": "Passengers",
|
"SW5E.VehiclePassengers": "Passengers",
|
||||||
|
"SW5E.VehicleTypeAir": "Air Vehicle",
|
||||||
|
"SW5E.VehicleTypeLand": "Land Vehicle",
|
||||||
|
"SW5E.VehicleTypeWater": "Water Vehicle",
|
||||||
"SW5E.VehicleUncrewed": "Uncrewed",
|
"SW5E.VehicleUncrewed": "Uncrewed",
|
||||||
"SW5E.Versatile": "Versatile",
|
"SW5E.Versatile": "Versatile",
|
||||||
"SW5E.VersatileDamage": "Versatile Damage",
|
"SW5E.VersatileDamage": "Versatile Damage",
|
||||||
"SW5E.VsDC": "vs DC.",
|
"SW5E.VsDC": "vs DC.",
|
||||||
|
"SW5E.WarnBadACFormula": "The provided AC formula could not be evaluated.",
|
||||||
|
"SW5E.WarnMultipleArmor": "More than one suit of armor equipped, AC calculation may be incorrect.",
|
||||||
|
"SW5E.WarnMultipleShields": "More than one shield equipped, AC calculation may be incorrect.",
|
||||||
"SW5E.WeaponAmmo": "Ammunition",
|
"SW5E.WeaponAmmo": "Ammunition",
|
||||||
"SW5E.WeaponBlasterPistolProficiency": "Blaster Pistol",
|
"SW5E.WeaponBlasterPistolProficiency": "Blaster Pistol",
|
||||||
"SW5E.WeaponChakramProficiency": "Chakrams",
|
"SW5E.WeaponChakramProficiency": "Chakrams",
|
||||||
|
@ -1234,4 +1271,4 @@
|
||||||
"SW5E.WeaponVibrorapierProficiency": "Vibrorapier",
|
"SW5E.WeaponVibrorapierProficiency": "Vibrorapier",
|
||||||
"SW5E.WeaponVibrowhipProficiency": "Vibrowhip",
|
"SW5E.WeaponVibrowhipProficiency": "Vibrowhip",
|
||||||
"SW5E.Weight": "Weight"
|
"SW5E.Weight": "Weight"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -4,54 +4,58 @@
|
||||||
/* Basic Structure */
|
/* Basic Structure */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
.sw5e.sheet.actor.npc {
|
.sw5e.sheet.actor.npc {
|
||||||
min-width: 872px;
|
min-width: 872px;
|
||||||
min-height: 680px;
|
min-height: 680px;
|
||||||
|
|
||||||
.header-exp {
|
.header-exp {
|
||||||
flex: 0 0 80px;
|
flex: 0 0 80px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
.cr {
|
.cr {
|
||||||
flex: 0 0 32px;
|
flex: 0 0 32px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
margin-bottom: -5px;
|
margin-bottom: -5px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
input {
|
input {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.experience,
|
||||||
|
.proficiency {
|
||||||
|
flex: 0 0 18px;
|
||||||
|
color: @colorTan;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.proficiency {
|
||||||
|
margin-top: -0.3em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.experience {
|
|
||||||
flex: 0 0 18px;
|
.summary {
|
||||||
color: @colorTan;
|
font-size: 18px;
|
||||||
font-size: 16px;
|
|
||||||
|
li.creature-type {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 1em;
|
||||||
|
padding: 0 3px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-button {
|
||||||
|
display: none;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
&:hover .config-button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
li.creature-type {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 1em;
|
|
||||||
padding: 0 3px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-button {
|
|
||||||
display: none;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 2em;
|
|
||||||
}
|
|
||||||
&:hover .config-button {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
.sw5e.sheet.actor.vehicle {
|
.sw5e.sheet.actor.vehicle {
|
||||||
.features {
|
min-width: 720px;
|
||||||
.item-controls {
|
min-height: 680px;
|
||||||
flex: 0 0 68px;
|
|
||||||
.item-toggle {
|
.features {
|
||||||
color: #b5b3a4;
|
.item-controls {
|
||||||
&.active {
|
flex: 0 0 68px;
|
||||||
color: #4b4a44;
|
.item-toggle {
|
||||||
|
color: #b5b3a4;
|
||||||
|
&.active {
|
||||||
|
color: #4b4a44;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
.counters {
|
||||||
.counters {
|
.counter.creature-cap {
|
||||||
.counter.creature-cap {
|
.counter-value {
|
||||||
.counter-value {
|
flex: 1;
|
||||||
flex: 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter.cargo-cap {
|
.counter.cargo-cap {
|
||||||
input {
|
input {
|
||||||
max-width: 40px;
|
max-width: 40px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
113
module/active-effect.js
Normal file
113
module/active-effect.js
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/**
|
||||||
|
* Extend the base ActiveEffect class to implement system-specific logic.
|
||||||
|
* @extends {ActiveEffect}
|
||||||
|
*/
|
||||||
|
export default class ActiveEffect5e extends ActiveEffect {
|
||||||
|
/**
|
||||||
|
* Is this active effect currently suppressed?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isSuppressed = false;
|
||||||
|
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
apply(actor, change) {
|
||||||
|
if ( this.isSuppressed ) return null;
|
||||||
|
return super.apply(actor, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether this Active Effect is suppressed or not.
|
||||||
|
*/
|
||||||
|
determineSuppression() {
|
||||||
|
this.isSuppressed = false;
|
||||||
|
if ( this.data.disabled || (this.parent.documentName !== "Actor") ) return;
|
||||||
|
const [parentType, parentId, documentType, documentId] = this.data.origin?.split(".") ?? [];
|
||||||
|
if ( (parentType !== "Actor") || (parentId !== this.parent.id) || (documentType !== "Item") ) return;
|
||||||
|
const item = this.parent.items.get(documentId);
|
||||||
|
if ( !item ) return;
|
||||||
|
const itemData = item.data.data;
|
||||||
|
// If an item is not equipped, or it is equipped but it requires attunement and is not attuned, then disable any
|
||||||
|
// Active Effects that might have originated from it.
|
||||||
|
this.isSuppressed = itemData.equipped === false || (itemData.attunement === CONFIG.SW5E.attunementTypes.REQUIRED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage Active Effect instances through the Actor Sheet via effect control buttons.
|
||||||
|
* @param {MouseEvent} event The left-click event on the effect control
|
||||||
|
* @param {Actor|Item} owner The owning entity which manages this effect
|
||||||
|
*/
|
||||||
|
static onManageActiveEffect(event, owner) {
|
||||||
|
event.preventDefault();
|
||||||
|
const a = event.currentTarget;
|
||||||
|
const li = a.closest("li");
|
||||||
|
const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null;
|
||||||
|
switch ( a.dataset.action ) {
|
||||||
|
case "create":
|
||||||
|
return owner.createEmbeddedDocuments("ActiveEffect", [{
|
||||||
|
label: game.i18n.localize("SW5E.EffectNew"),
|
||||||
|
icon: "icons/svg/aura.svg",
|
||||||
|
origin: owner.uuid,
|
||||||
|
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
|
||||||
|
disabled: li.dataset.effectType === "inactive"
|
||||||
|
}]);
|
||||||
|
case "edit":
|
||||||
|
return effect.sheet.render(true);
|
||||||
|
case "delete":
|
||||||
|
return effect.delete();
|
||||||
|
case "toggle":
|
||||||
|
return effect.update({disabled: !effect.data.disabled});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the data structure for Active Effects which are currently applied to an Actor or Item.
|
||||||
|
* @param {ActiveEffect[]} effects The array of Active Effect instances to prepare sheet data for
|
||||||
|
* @return {object} Data for rendering
|
||||||
|
*/
|
||||||
|
static prepareActiveEffectCategories(effects) {
|
||||||
|
// Define effect header categories
|
||||||
|
const categories = {
|
||||||
|
temporary: {
|
||||||
|
type: "temporary",
|
||||||
|
label: game.i18n.localize("SW5E.EffectTemporary"),
|
||||||
|
effects: []
|
||||||
|
},
|
||||||
|
passive: {
|
||||||
|
type: "passive",
|
||||||
|
label: game.i18n.localize("SW5E.EffectPassive"),
|
||||||
|
effects: []
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
type: "inactive",
|
||||||
|
label: game.i18n.localize("SW5E.EffectInactive"),
|
||||||
|
effects: []
|
||||||
|
},
|
||||||
|
suppressed: {
|
||||||
|
type: "suppressed",
|
||||||
|
label: game.i18n.localize("SW5E.EffectUnavailable"),
|
||||||
|
effects: [],
|
||||||
|
info: [game.i18n.localize("SW5E.EffectUnavailableInfo")]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over active effects, classifying them into categories
|
||||||
|
for ( let e of effects ) {
|
||||||
|
e._getSourceName(); // Trigger a lookup for the source name
|
||||||
|
if ( e.isSuppressed ) categories.suppressed.effects.push(e);
|
||||||
|
else if ( e.data.disabled ) categories.inactive.effects.push(e);
|
||||||
|
else if ( e.isTemporary ) categories.temporary.effects.push(e);
|
||||||
|
else categories.passive.effects.push(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
categories.suppressed.hidden = !categories.suppressed.effects.length;
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ export default class Actor5e extends Actor {
|
||||||
*/
|
*/
|
||||||
get classes() {
|
get classes() {
|
||||||
if (this._classes !== undefined) return this._classes;
|
if (this._classes !== undefined) return this._classes;
|
||||||
if (this.data.type !== "character") return (this._classes = {});
|
if (!["character", "npc"].includes(this.data.type)) return (this._classes = {});
|
||||||
return (this._classes = this.items
|
return (this._classes = this.items
|
||||||
.filter((item) => item.type === "class")
|
.filter((item) => item.type === "class")
|
||||||
.reduce((obj, cls) => {
|
.reduce((obj, cls) => {
|
||||||
|
@ -52,6 +52,7 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
prepareData() {
|
prepareData() {
|
||||||
|
this._preparationWarnings = [];
|
||||||
super.prepareData();
|
super.prepareData();
|
||||||
|
|
||||||
// iterate over owned items and recompute attributes that depend on prepared actor data
|
// iterate over owned items and recompute attributes that depend on prepared actor data
|
||||||
|
@ -62,6 +63,7 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
|
this._prepareBaseArmorClass(this.data);
|
||||||
switch (this.data.type) {
|
switch (this.data.type) {
|
||||||
case "character":
|
case "character":
|
||||||
return this._prepareCharacterData(this.data);
|
return this._prepareCharacterData(this.data);
|
||||||
|
@ -74,6 +76,16 @@ export default class Actor5e extends Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
applyActiveEffects() {
|
||||||
|
// The Active Effects do not have access to their parent at preparation time so we wait until this stage to
|
||||||
|
// determine whether they are suppressed or not.
|
||||||
|
this.effects.forEach((e) => e.determineSuppression());
|
||||||
|
return super.applyActiveEffects();
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
|
@ -146,6 +158,12 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
// Prepare power-casting data
|
// Prepare power-casting data
|
||||||
this._computeDerivedPowercasting(this.data);
|
this._computeDerivedPowercasting(this.data);
|
||||||
|
|
||||||
|
// Prepare armor class data
|
||||||
|
const ac = this._computeArmorClass(data);
|
||||||
|
this.armor = ac.equippedArmor || null;
|
||||||
|
this.shield = ac.equippedShield || null;
|
||||||
|
if (ac.warnings) this._preparationWarnings.push(...ac.warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -425,6 +443,21 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize derived AC fields for Active Effects to target.
|
||||||
|
* @param actorData
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareBaseArmorClass(actorData) {
|
||||||
|
const ac = actorData.data.attributes.ac;
|
||||||
|
ac.base = 10;
|
||||||
|
ac.shield = ac.bonus = ac.cover = 0;
|
||||||
|
this.armor = null;
|
||||||
|
this.shield = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare data related to the power-casting capabilities of the Actor
|
* Prepare data related to the power-casting capabilities of the Actor
|
||||||
* @private
|
* @private
|
||||||
|
@ -658,6 +691,103 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine a character's AC value from their equipped armor and shield.
|
||||||
|
* @param {object} data Note that this object will be mutated.
|
||||||
|
* @return {{
|
||||||
|
* calc: string,
|
||||||
|
* value: number,
|
||||||
|
* base: number,
|
||||||
|
* shield: number,
|
||||||
|
* bonus: number,
|
||||||
|
* cover: number,
|
||||||
|
* flat: number,
|
||||||
|
* equippedArmor: Item5e,
|
||||||
|
* equippedShield: Item5e,
|
||||||
|
* warnings: string[]
|
||||||
|
* }}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_computeArmorClass(data) {
|
||||||
|
// Get AC configuration and apply automatic migrations for older data structures
|
||||||
|
const ac = data.attributes.ac;
|
||||||
|
ac.warnings = [];
|
||||||
|
let cfg = CONFIG.SW5E.armorClasses[ac.calc];
|
||||||
|
if (!cfg) {
|
||||||
|
ac.calc = "flat";
|
||||||
|
if (Number.isNumeric(ac.value)) ac.flat = Number(ac.value);
|
||||||
|
cfg = CONFIG.SW5E.armorClasses.flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify Equipped Items
|
||||||
|
const armorTypes = new Set(Object.keys(CONFIG.SW5E.armorTypes));
|
||||||
|
const {armors, shields} = this.itemTypes.equipment.reduce(
|
||||||
|
(obj, equip) => {
|
||||||
|
const armor = equip.data.data.armor;
|
||||||
|
if (!equip.data.data.equipped || !armorTypes.has(armor?.type)) return obj;
|
||||||
|
if (armor.type === "shield") obj.shields.push(equip);
|
||||||
|
else obj.armors.push(equip);
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
{armors: [], shields: []}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Determine base AC
|
||||||
|
switch (ac.calc) {
|
||||||
|
// Flat AC (no additional bonuses)
|
||||||
|
case "flat":
|
||||||
|
ac.value = ac.flat;
|
||||||
|
return ac;
|
||||||
|
|
||||||
|
// Natural AC (includes bonuses)
|
||||||
|
case "natural":
|
||||||
|
ac.base = ac.flat;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Equipment-based AC
|
||||||
|
case "default":
|
||||||
|
if (armors.length) {
|
||||||
|
if (armors.length > 1) ac.warnings.push("SW5E.WarnMultipleArmor");
|
||||||
|
const armorData = armors[0].data.data.armor;
|
||||||
|
const isHeavy = armorData.type === "heavy";
|
||||||
|
ac.dex = isHeavy ? 0 : Math.min(armorData.dex ?? Infinity, data.abilities.dex.mod);
|
||||||
|
ac.base = (armorData.value ?? 0) + ac.dex;
|
||||||
|
ac.equippedArmor = armors[0];
|
||||||
|
} else {
|
||||||
|
ac.dex = data.abilities.dex.mod;
|
||||||
|
ac.base = 10 + ac.dex;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Formula-based AC
|
||||||
|
default:
|
||||||
|
let formula = ac.calc === "custom" ? ac.formula : cfg.formula;
|
||||||
|
const rollData = this.getRollData();
|
||||||
|
try {
|
||||||
|
const replaced = Roll.replaceFormulaData(formula, rollData);
|
||||||
|
ac.base = Roll.safeEval(replaced);
|
||||||
|
} catch (err) {
|
||||||
|
ac.warnings.push("SW5E.WarnBadACFormula");
|
||||||
|
const replaced = Roll.replaceFormulaData(CONFIG.SW5E.armorClasses.default.formula, rollData);
|
||||||
|
ac.base = Roll.safeEval(replaced);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equipped Shield
|
||||||
|
if (shields.length) {
|
||||||
|
if (shields.length > 1) ac.warnings.push("SW5E.WarnMultipleShields");
|
||||||
|
ac.shield = shields[0].data.data.armor.value ?? 0;
|
||||||
|
ac.equippedShield = shields[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute total AC and return
|
||||||
|
ac.value = ac.base + ac.shield + ac.bonus + ac.cover;
|
||||||
|
return ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare data related to the power-casting capabilities of the Actor
|
* Prepare data related to the power-casting capabilities of the Actor
|
||||||
* @private
|
* @private
|
||||||
|
@ -712,7 +842,12 @@ export default class Actor5e extends Actor {
|
||||||
if (game.settings.get("sw5e", "currencyWeight") && actorData.data.currency) {
|
if (game.settings.get("sw5e", "currencyWeight") && actorData.data.currency) {
|
||||||
const currency = actorData.data.currency;
|
const currency = actorData.data.currency;
|
||||||
const numCoins = Object.values(currency).reduce((val, denom) => (val += Math.max(denom, 0)), 0);
|
const numCoins = Object.values(currency).reduce((val, denom) => (val += Math.max(denom, 0)), 0);
|
||||||
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
|
||||||
|
const currencyPerWeight = game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? CONFIG.SW5E.encumbrance.currencyPerWeight.metric
|
||||||
|
: CONFIG.SW5E.encumbrance.currencyPerWeight.imperial;
|
||||||
|
|
||||||
|
weight += numCoins / currencyPerWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the encumbrance size class
|
// Determine the encumbrance size class
|
||||||
|
@ -729,9 +864,14 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
// Compute Encumbrance percentage
|
// Compute Encumbrance percentage
|
||||||
weight = weight.toNearest(0.1);
|
weight = weight.toNearest(0.1);
|
||||||
const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod;
|
|
||||||
|
const strengthMultiplier = game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? CONFIG.SW5E.encumbrance.strMultiplier.metric
|
||||||
|
: CONFIG.SW5E.encumbrance.strMultiplier.imperial;
|
||||||
|
|
||||||
|
const max = (actorData.data.abilities.str.value * strengthMultiplier * mod).toNearest(0.1);
|
||||||
const pct = Math.clamped((weight * 100) / max, 0, 100);
|
const pct = Math.clamped((weight * 100) / max, 0, 100);
|
||||||
return {value: weight.toNearest(0.1), max, pct, encumbered: pct > 2 / 3};
|
return {value: weight.toNearest(0.1), max, pct, encumbered: pct > 200 / 3};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -741,7 +881,10 @@ export default class Actor5e extends Actor {
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
await super._preCreate(data, options, user);
|
await super._preCreate(data, options, user);
|
||||||
|
const sourceId = this.getFlag("core", "sourceId");
|
||||||
|
if (sourceId?.startsWith("Compendium.")) return;
|
||||||
|
|
||||||
|
// Some sensible defaults for convenience
|
||||||
// Token size category
|
// Token size category
|
||||||
const s = CONFIG.SW5E.tokenSizes[this.data.data.traits.size || "med"];
|
const s = CONFIG.SW5E.tokenSizes[this.data.data.traits.size || "med"];
|
||||||
this.data.token.update({width: s, height: s});
|
this.data.token.update({width: s, height: s});
|
||||||
|
@ -1044,6 +1187,7 @@ export default class Actor5e extends Actor {
|
||||||
// Evaluate a global saving throw bonus
|
// Evaluate a global saving throw bonus
|
||||||
const parts = [];
|
const parts = [];
|
||||||
const data = {};
|
const data = {};
|
||||||
|
const speaker = options.speaker || ChatMessage.getSpeaker({actor: this});
|
||||||
|
|
||||||
// Include a global actor ability save bonus
|
// Include a global actor ability save bonus
|
||||||
const bonuses = foundry.utils.getProperty(this.data.data, "bonuses.abilities") || {};
|
const bonuses = foundry.utils.getProperty(this.data.data, "bonuses.abilities") || {};
|
||||||
|
@ -1060,7 +1204,7 @@ export default class Actor5e extends Actor {
|
||||||
halflingLucky: this.getFlag("sw5e", "halflingLucky"),
|
halflingLucky: this.getFlag("sw5e", "halflingLucky"),
|
||||||
targetValue: 10,
|
targetValue: 10,
|
||||||
messageData: {
|
messageData: {
|
||||||
"speaker": options.speaker || ChatMessage.getSpeaker({actor: this}),
|
"speaker": speaker,
|
||||||
"flags.sw5e.roll": {type: "death"}
|
"flags.sw5e.roll": {type: "death"}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1192,6 +1336,7 @@ export default class Actor5e extends Actor {
|
||||||
* @property {number} dhd Hit dice recovered or spent during the rest.
|
* @property {number} dhd Hit dice recovered or spent during the rest.
|
||||||
* @property {object} updateData Updates applied to the actor.
|
* @property {object} updateData Updates applied to the actor.
|
||||||
* @property {Array.<object>} updateItems Updates applied to actor's items.
|
* @property {Array.<object>} updateItems Updates applied to actor's items.
|
||||||
|
* @property {boolean} longRest Whether the rest type was a long rest.
|
||||||
* @property {boolean} newDay Whether a new day occurred during the rest.
|
* @property {boolean} newDay Whether a new day occurred during the rest.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -1316,7 +1461,8 @@ export default class Actor5e extends Actor {
|
||||||
...hitDiceUpdates,
|
...hitDiceUpdates,
|
||||||
...this._getRestItemUsesRecovery({recoverLongRestUses: longRest, recoverDailyUses: newDay})
|
...this._getRestItemUsesRecovery({recoverLongRestUses: longRest, recoverDailyUses: newDay})
|
||||||
],
|
],
|
||||||
newDay: newDay
|
longRest,
|
||||||
|
newDay
|
||||||
};
|
};
|
||||||
|
|
||||||
// Perform updates
|
// Perform updates
|
||||||
|
@ -1326,6 +1472,9 @@ export default class Actor5e extends Actor {
|
||||||
// Display a Chat Message summarizing the rest effects
|
// Display a Chat Message summarizing the rest effects
|
||||||
if (chat) await this._displayRestResultMessage(result, longRest);
|
if (chat) await this._displayRestResultMessage(result, longRest);
|
||||||
|
|
||||||
|
// Call restCompleted hook so that modules can easily perform actions when actors finish a rest
|
||||||
|
Hooks.callAll("restCompleted", this, result);
|
||||||
|
|
||||||
// Return data summarizing the rest effects
|
// Return data summarizing the rest effects
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1364,13 +1513,13 @@ export default class Actor5e extends Actor {
|
||||||
// Determine the chat message to display
|
// Determine the chat message to display
|
||||||
if (longRest) {
|
if (longRest) {
|
||||||
message = "SW5E.LongRestResult";
|
message = "SW5E.LongRestResult";
|
||||||
if (dhp !== 0) message += "HP";
|
if (healthRestored) message += "HP";
|
||||||
if (dfp !== 0) message += "FP";
|
if (dfp !== 0) message += "FP";
|
||||||
if (dtp !== 0) message += "TP";
|
if (dtp !== 0) message += "TP";
|
||||||
if (dhd !== 0) message += "HD";
|
if (diceRestored) message += "HD";
|
||||||
} else {
|
} else {
|
||||||
message = "SW5E.ShortRestResultShort";
|
message = "SW5E.ShortRestResultShort";
|
||||||
if (dhd !== 0 && dhp !== 0) {
|
if (diceRestored && healthRestored) {
|
||||||
if (dtp !== 0) {
|
if (dtp !== 0) {
|
||||||
message = "SW5E.ShortRestResultWithTech";
|
message = "SW5E.ShortRestResultWithTech";
|
||||||
} else {
|
} else {
|
||||||
|
@ -1581,19 +1730,19 @@ export default class Actor5e extends Actor {
|
||||||
/**
|
/**
|
||||||
* Transform this Actor into another one.
|
* Transform this Actor into another one.
|
||||||
*
|
*
|
||||||
* @param {Actor} target The target Actor.
|
* @param {Actor5e} target The target Actor.
|
||||||
* @param {boolean} [keepPhysical] Keep physical abilities (str, dex, con)
|
* @param {boolean} [keepPhysical] Keep physical abilities (str, dex, con)
|
||||||
* @param {boolean} [keepMental] Keep mental abilities (int, wis, cha)
|
* @param {boolean} [keepMental] Keep mental abilities (int, wis, cha)
|
||||||
* @param {boolean} [keepSaves] Keep saving throw proficiencies
|
* @param {boolean} [keepSaves] Keep saving throw proficiencies
|
||||||
* @param {boolean} [keepSkills] Keep skill proficiencies
|
* @param {boolean} [keepSkills] Keep skill proficiencies
|
||||||
* @param {boolean} [mergeSaves] Take the maximum of the save proficiencies
|
* @param {boolean} [mergeSaves] Take the maximum of the save proficiencies
|
||||||
* @param {boolean} [mergeSkills] Take the maximum of the skill proficiencies
|
* @param {boolean} [mergeSkills] Take the maximum of the skill proficiencies
|
||||||
* @param {boolean} [keepClass] Keep proficiency bonus
|
* @param {boolean} [keepClass] Keep proficiency bonus
|
||||||
* @param {boolean} [keepFeats] Keep features
|
* @param {boolean} [keepFeats] Keep features
|
||||||
* @param {boolean} [keepPowers] Keep powers
|
* @param {boolean} [keepPowers] Keep powers
|
||||||
* @param {boolean} [keepItems] Keep items
|
* @param {boolean} [keepItems] Keep items
|
||||||
* @param {boolean} [keepBio] Keep biography
|
* @param {boolean} [keepBio] Keep biography
|
||||||
* @param {boolean} [keepVision] Keep vision
|
* @param {boolean} [keepVision] Keep vision
|
||||||
* @param {boolean} [transformTokens] Transform linked tokens too
|
* @param {boolean} [transformTokens] Transform linked tokens too
|
||||||
*/
|
*/
|
||||||
async transformInto(
|
async transformInto(
|
||||||
|
@ -1649,16 +1798,16 @@ export default class Actor5e extends Actor {
|
||||||
d.data.attributes.exhaustion = o.data.attributes.exhaustion; // Keep your prior exhaustion level
|
d.data.attributes.exhaustion = o.data.attributes.exhaustion; // Keep your prior exhaustion level
|
||||||
d.data.attributes.inspiration = o.data.attributes.inspiration; // Keep inspiration
|
d.data.attributes.inspiration = o.data.attributes.inspiration; // Keep inspiration
|
||||||
d.data.powers = o.data.powers; // Keep power slots
|
d.data.powers = o.data.powers; // Keep power slots
|
||||||
|
d.data.attributes.ac.flat = target.data.data.attributes.ac.value; // Override AC
|
||||||
|
|
||||||
// Token appearance updates
|
// Token appearance updates
|
||||||
d.token = {name: d.name};
|
d.token = {name: d.name};
|
||||||
for (let k of ["width", "height", "scale", "img", "mirrorX", "mirrorY", "tint", "alpha", "lockRotation"]) {
|
for (let k of ["width", "height", "scale", "img", "mirrorX", "mirrorY", "tint", "alpha", "lockRotation"]) {
|
||||||
d.token[k] = source.token[k];
|
d.token[k] = source.token[k];
|
||||||
}
|
}
|
||||||
if (!keepVision) {
|
const vision = keepVision ? o.token : source.token;
|
||||||
for (let k of ["dimSight", "brightSight", "dimLight", "brightLight", "vision", "sightAngle"]) {
|
for (let k of ["dimSight", "brightSight", "dimLight", "brightLight", "vision", "sightAngle"]) {
|
||||||
d.token[k] = source.token[k];
|
d.token[k] = vision[k];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (source.token.randomImg) {
|
if (source.token.randomImg) {
|
||||||
const images = await target.getTokenImages();
|
const images = await target.getTokenImages();
|
||||||
|
@ -1745,9 +1894,9 @@ export default class Actor5e extends Actor {
|
||||||
const tokens = this.getActiveTokens(true);
|
const tokens = this.getActiveTokens(true);
|
||||||
const updates = tokens.map((t) => {
|
const updates = tokens.map((t) => {
|
||||||
const newTokenData = foundry.utils.deepClone(d.token);
|
const newTokenData = foundry.utils.deepClone(d.token);
|
||||||
if (!t.data.actorLink) newTokenData.actorData = newActor.data;
|
|
||||||
newTokenData._id = t.data._id;
|
newTokenData._id = t.data._id;
|
||||||
newTokenData.actorId = newActor.id;
|
newTokenData.actorId = newActor.id;
|
||||||
|
newTokenData.actorLink = true;
|
||||||
return newTokenData;
|
return newTokenData;
|
||||||
});
|
});
|
||||||
return canvas.scene?.updateEmbeddedDocuments("Token", updates);
|
return canvas.scene?.updateEmbeddedDocuments("Token", updates);
|
||||||
|
@ -1771,10 +1920,25 @@ export default class Actor5e extends Actor {
|
||||||
const baseActor = game.actors.get(this.token.data.actorId);
|
const baseActor = game.actors.get(this.token.data.actorId);
|
||||||
const prototypeTokenData = await baseActor.getTokenData();
|
const prototypeTokenData = await baseActor.getTokenData();
|
||||||
const tokenUpdate = {actorData: {}};
|
const tokenUpdate = {actorData: {}};
|
||||||
for (let k of ["width", "height", "scale", "img", "mirrorX", "mirrorY", "tint", "alpha", "lockRotation"]) {
|
for (let k of [
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"scale",
|
||||||
|
"img",
|
||||||
|
"mirrorX",
|
||||||
|
"mirrorY",
|
||||||
|
"tint",
|
||||||
|
"alpha",
|
||||||
|
"lockRotation",
|
||||||
|
"name"
|
||||||
|
]) {
|
||||||
tokenUpdate[k] = prototypeTokenData[k];
|
tokenUpdate[k] = prototypeTokenData[k];
|
||||||
}
|
}
|
||||||
return this.token.update(tokenUpdate, {recursive: false});
|
await this.token.update(tokenUpdate, {recursive: false});
|
||||||
|
await this.sheet.close();
|
||||||
|
const actor = this.token.getActor();
|
||||||
|
actor.sheet.render(true);
|
||||||
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain a reference to the original actor
|
// Obtain a reference to the original actor
|
||||||
|
@ -1854,6 +2018,45 @@ export default class Actor5e extends Actor {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate a proficiency object with a `selected` field containing a combination of
|
||||||
|
* localizable group & individual proficiencies from `value` and the contents of `custom`.
|
||||||
|
*
|
||||||
|
* @param {object} data Object containing proficiency data
|
||||||
|
* @param {Array.<string>} data.value Array of standard proficiency keys
|
||||||
|
* @param {string} data.custom Semicolon-separated string of custom proficiencies
|
||||||
|
* @param {string} type "armor", "weapon", or "tool"
|
||||||
|
*/
|
||||||
|
static prepareProficiencies(data, type) {
|
||||||
|
const profs = CONFIG.SW5E[`${type}Proficiencies`];
|
||||||
|
const itemTypes = CONFIG.SW5E[`${type}Ids`];
|
||||||
|
|
||||||
|
let values = [];
|
||||||
|
if (data.value) {
|
||||||
|
values = data.value instanceof Array ? data.value : [data.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
data.selected = {};
|
||||||
|
const pack = game.packs.get(CONFIG.SW5E.sourcePacks.ITEMS);
|
||||||
|
for (const key of values) {
|
||||||
|
if (profs[key]) {
|
||||||
|
data.selected[key] = profs[key];
|
||||||
|
} else if (itemTypes && itemTypes[key]) {
|
||||||
|
const item = pack.index.get(itemTypes[key]);
|
||||||
|
data.selected[key] = item.name;
|
||||||
|
} else if (type === "tool" && CONFIG.SW5E.vehicleTypes[key]) {
|
||||||
|
data.selected[key] = CONFIG.SW5E.vehicleTypes[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom entries
|
||||||
|
if (data.custom) {
|
||||||
|
data.custom.split(";").forEach((c, i) => (data.selected[`custom${i + 1}`] = c.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* DEPRECATED METHODS */
|
/* DEPRECATED METHODS */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
import Actor5e from "../../entity.js";
|
||||||
import Item5e from "../../../item/entity.js";
|
import Item5e from "../../../item/entity.js";
|
||||||
|
import ProficiencySelector from "../../../apps/proficiency-selector.js";
|
||||||
|
import PropertyAttribution from "../../../apps/property-attribution.js";
|
||||||
import TraitSelector from "../../../apps/trait-selector.js";
|
import TraitSelector from "../../../apps/trait-selector.js";
|
||||||
|
import ActorArmorConfig from "../../../apps/actor-armor.js";
|
||||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||||
import {SW5E} from "../../../config.js";
|
import {SW5E} from "../../../config.js";
|
||||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
import ActiveEffect5e from "../../../active-effect.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||||
|
@ -85,6 +89,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
// The Actor's data
|
// The Actor's data
|
||||||
const actorData = this.actor.data.toObject(false);
|
const actorData = this.actor.data.toObject(false);
|
||||||
|
const source = this.actor.data._source.data;
|
||||||
data.actor = actorData;
|
data.actor = actorData;
|
||||||
data.data = actorData.data;
|
data.data = actorData.data;
|
||||||
|
|
||||||
|
@ -105,6 +110,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
abl.icon = this._getProficiencyIcon(abl.proficient);
|
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||||
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||||
abl.label = CONFIG.SW5E.abilities[a];
|
abl.label = CONFIG.SW5E.abilities[a];
|
||||||
|
abl.baseProf = source.abilities[a].proficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skills
|
// Skills
|
||||||
|
@ -117,6 +123,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
skl.label = CONFIG.SW5E.starshipSkills[s];
|
skl.label = CONFIG.SW5E.starshipSkills[s];
|
||||||
} else {
|
} else {
|
||||||
skl.label = CONFIG.SW5E.skills[s];
|
skl.label = CONFIG.SW5E.skills[s];
|
||||||
|
skl.baseValue = source.skills[s].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +141,15 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
this._prepareItems(data);
|
this._prepareItems(data);
|
||||||
|
|
||||||
// Prepare active effects
|
// Prepare active effects
|
||||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
data.effects = ActiveEffect5e.prepareActiveEffectCategories(this.actor.effects);
|
||||||
|
|
||||||
|
// Prepare warnings
|
||||||
|
data.warnings = this.actor._preparationWarnings;
|
||||||
|
|
||||||
|
// Prepare property attributions
|
||||||
|
this.attribution = {
|
||||||
|
"attributes.ac": this._prepareArmorClassAttribution(actorData.data)
|
||||||
|
};
|
||||||
|
|
||||||
// Return data to the sheet
|
// Return data to the sheet
|
||||||
return data;
|
return data;
|
||||||
|
@ -204,6 +219,105 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a list of armor class attribution objects.
|
||||||
|
* @param {object} data Actor data to determine the attributions from.
|
||||||
|
* @return {AttributionDescription[]} List of attribution descriptions.
|
||||||
|
*/
|
||||||
|
_prepareArmorClassAttribution(data) {
|
||||||
|
const ac = data.attributes.ac;
|
||||||
|
const cfg = CONFIG.SW5E.armorClasses[ac.calc];
|
||||||
|
const attribution = [];
|
||||||
|
|
||||||
|
// Base AC Attribution
|
||||||
|
switch (ac.calc) {
|
||||||
|
// Flat AC
|
||||||
|
case "flat":
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: game.i18n.localize("SW5E.ArmorClassFlat"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: ac.flat
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Natural armor
|
||||||
|
case "natural":
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.ArmorClassNatural"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: ac.flat
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Equipment-based AC
|
||||||
|
case "default":
|
||||||
|
const hasArmor = !!this.actor.armor;
|
||||||
|
attribution.push({
|
||||||
|
label: hasArmor ? this.actor.armor.name : game.i18n.localize("SW5E.ArmorClassUnarmored"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: hasArmor ? this.actor.armor.data.data.armor.value : 10
|
||||||
|
});
|
||||||
|
if (ac.dex !== 0) {
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.AbilityDex"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.dex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Other AC formula
|
||||||
|
default:
|
||||||
|
const formula = ac.calc === "custom" ? ac.formula : cfg.formula;
|
||||||
|
let base = ac.base;
|
||||||
|
const dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi);
|
||||||
|
for (const [match, term] of formula.matchAll(dataRgx)) {
|
||||||
|
const value = foundry.utils.getProperty(data, term);
|
||||||
|
if (term === "attributes.ac.base" || value === 0) continue;
|
||||||
|
if (Number.isNumeric(value)) base -= Number(value);
|
||||||
|
attribution.push({
|
||||||
|
label: match,
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: foundry.utils.getProperty(data, term)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
attribution.unshift({
|
||||||
|
label: game.i18n.localize("SW5E.PropertyBase"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: base
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shield
|
||||||
|
if (ac.shield !== 0)
|
||||||
|
attribution.push({
|
||||||
|
label: this.actor.shield?.name ?? game.i18n.localize("SW5E.EquipmentShield"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.shield
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bonus
|
||||||
|
if (ac.bonus !== 0)
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.Bonus"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.bonus
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cover
|
||||||
|
if (ac.cover !== 0)
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.Cover"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.cover
|
||||||
|
});
|
||||||
|
return attribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
||||||
* @param {object} traits The raw traits data object from the actor data
|
* @param {object} traits The raw traits data object from the actor data
|
||||||
|
@ -215,10 +329,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
di: CONFIG.SW5E.damageResistanceTypes,
|
di: CONFIG.SW5E.damageResistanceTypes,
|
||||||
dv: CONFIG.SW5E.damageResistanceTypes,
|
dv: CONFIG.SW5E.damageResistanceTypes,
|
||||||
ci: CONFIG.SW5E.conditionTypes,
|
ci: CONFIG.SW5E.conditionTypes,
|
||||||
languages: CONFIG.SW5E.languages,
|
languages: CONFIG.SW5E.languages
|
||||||
armorProf: CONFIG.SW5E.armorProficiencies,
|
|
||||||
weaponProf: CONFIG.SW5E.weaponProficiencies,
|
|
||||||
toolProf: CONFIG.SW5E.toolProficiencies
|
|
||||||
};
|
};
|
||||||
for (let [t, choices] of Object.entries(map)) {
|
for (let [t, choices] of Object.entries(map)) {
|
||||||
const trait = traits[t];
|
const trait = traits[t];
|
||||||
|
@ -238,6 +349,14 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
}
|
}
|
||||||
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate and localize proficiencies
|
||||||
|
for (const t of ["armor", "weapon", "tool"]) {
|
||||||
|
const trait = traits[`${t}Prof`];
|
||||||
|
if (!trait) continue;
|
||||||
|
Actor5e.prepareProficiencies(trait, t);
|
||||||
|
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -414,6 +533,8 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
// View Item Sheets
|
// View Item Sheets
|
||||||
html.find(".item-edit").click(this._onItemEdit.bind(this));
|
html.find(".item-edit").click(this._onItemEdit.bind(this));
|
||||||
|
// Property attributions
|
||||||
|
html.find(".attributable").mouseover(this._onPropertyAttribution.bind(this));
|
||||||
|
|
||||||
// Editable Only Listeners
|
// Editable Only Listeners
|
||||||
if (this.isEditable) {
|
if (this.isEditable) {
|
||||||
|
@ -429,6 +550,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
||||||
|
|
||||||
// Trait Selector
|
// Trait Selector
|
||||||
|
html.find(".proficiency-selector").click(this._onProficiencySelector.bind(this));
|
||||||
html.find(".trait-selector").click(this._onTraitSelector.bind(this));
|
html.find(".trait-selector").click(this._onTraitSelector.bind(this));
|
||||||
|
|
||||||
// Configure Special Flags
|
// Configure Special Flags
|
||||||
|
@ -446,7 +568,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
html.find(".decrement-class-level").click(this._onDecrementClassLevel.bind(this));
|
html.find(".decrement-class-level").click(this._onDecrementClassLevel.bind(this));
|
||||||
|
|
||||||
// Active Effect management
|
// Active Effect management
|
||||||
html.find(".effect-control").click((ev) => onManageActiveEffect(ev, this.actor));
|
html.find(".effect-control").click((ev) => ActiveEffect5e.onManageActiveEffect(ev, this.actor));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Owner Only Listeners
|
// Owner Only Listeners
|
||||||
|
@ -474,7 +596,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iinitialize Item list filters by activating the set of filters which are currently applied
|
* Initialize Item list filters by activating the set of filters which are currently applied
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_initializeFilterItemList(i, ul) {
|
_initializeFilterItemList(i, ul) {
|
||||||
|
@ -517,6 +639,9 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
const button = event.currentTarget;
|
const button = event.currentTarget;
|
||||||
let app;
|
let app;
|
||||||
switch (button.dataset.action) {
|
switch (button.dataset.action) {
|
||||||
|
case "armor":
|
||||||
|
app = new ActorArmorConfig(this.object);
|
||||||
|
break;
|
||||||
case "hit-dice":
|
case "hit-dice":
|
||||||
app = new ActorHitDiceConfig(this.object);
|
app = new ActorHitDiceConfig(this.object);
|
||||||
break;
|
break;
|
||||||
|
@ -530,7 +655,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
app = new ActorSensesConfig(this.object);
|
app = new ActorSensesConfig(this.object);
|
||||||
break;
|
break;
|
||||||
case "type":
|
case "type":
|
||||||
new ActorTypeConfig(this.object).render(true);
|
app = new ActorTypeConfig(this.object);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
app?.render(true);
|
app?.render(true);
|
||||||
|
@ -545,22 +670,19 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
*/
|
*/
|
||||||
_onCycleSkillProficiency(event) {
|
_onCycleSkillProficiency(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const field = $(event.currentTarget).siblings('input[type="hidden"]');
|
const field = event.currentTarget.previousElementSibling;
|
||||||
|
const skillName = field.parentElement.dataset.skill;
|
||||||
|
const source = this.actor.data._source.data.skills[skillName];
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
// Get the current level and the array of levels
|
// Cycle to the next or previous skill level
|
||||||
const level = parseFloat(field.val());
|
|
||||||
const levels = [0, 1, 0.5, 2];
|
const levels = [0, 1, 0.5, 2];
|
||||||
let idx = levels.indexOf(level);
|
let idx = levels.indexOf(source.value);
|
||||||
|
const next = idx + (event.type === "click" ? 1 : 3);
|
||||||
// Toggle next level - forward on click, backwards on right
|
field.value = levels[next % 4];
|
||||||
if (event.type === "click") {
|
|
||||||
field.val(levels[idx === levels.length - 1 ? 0 : idx + 1]);
|
|
||||||
} else if (event.type === "contextmenu") {
|
|
||||||
field.val(levels[idx === 0 ? levels.length - 1 : idx - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the field value and save the form
|
// Update the field value and save the form
|
||||||
this._onSubmit(event);
|
return this._onSubmit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -674,7 +796,12 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
|
if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
|
||||||
const similarItem = this.actor.items.find((i) => {
|
const similarItem = this.actor.items.find((i) => {
|
||||||
const sourceId = i.getFlag("core", "sourceId");
|
const sourceId = i.getFlag("core", "sourceId");
|
||||||
return sourceId && sourceId === itemData.flags.core?.sourceId && i.type === "consumable";
|
return (
|
||||||
|
sourceId &&
|
||||||
|
sourceId === itemData.flags.core?.sourceId &&
|
||||||
|
i.type === "consumable" &&
|
||||||
|
i.name === itemData.name
|
||||||
|
);
|
||||||
});
|
});
|
||||||
if (similarItem && itemData.name !== "Power Cell") {
|
if (similarItem && itemData.name !== "Power Cell") {
|
||||||
// Always create a new powercell instead of increasing quantity
|
// Always create a new powercell instead of increasing quantity
|
||||||
|
@ -901,6 +1028,22 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle displaying the property attribution tooltip when a property is hovered over.
|
||||||
|
* @param {Event} event The originating mouse event.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onPropertyAttribution(event) {
|
||||||
|
const existingTooltip = event.currentTarget.querySelector("div.tooltip");
|
||||||
|
const property = event.currentTarget.dataset.property;
|
||||||
|
if (existingTooltip || !property || !this.attribution) return;
|
||||||
|
|
||||||
|
let html = await new PropertyAttribution(this.object, this.attribution, property).renderTooltip();
|
||||||
|
event.currentTarget.insertAdjacentElement("beforeend", html[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle rolling an Ability check, either a test or a saving throw
|
* Handle rolling an Ability check, either a test or a saving throw
|
||||||
* @param {Event} event The originating click event
|
* @param {Event} event The originating click event
|
||||||
|
@ -957,6 +1100,22 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle spawning the ProficiencySelector application to configure armor, weapon, and tool proficiencies.
|
||||||
|
* @param {Event} event The click event which originated the selection
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onProficiencySelector(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const a = event.currentTarget;
|
||||||
|
const label = a.parentElement.querySelector("label");
|
||||||
|
const options = {name: a.dataset.target, title: label.innerText, type: a.dataset.type};
|
||||||
|
if (options.type === "tool") options.sortCategories = true;
|
||||||
|
return new ProficiencySelector(this.actor, options).render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||||
* @param {Event} event The click event which originated the selection
|
* @param {Event} event The click event which originated the selection
|
||||||
|
|
|
@ -62,6 +62,10 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
|
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
|
||||||
})
|
})
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
// Weight unit
|
||||||
|
sheetData["weightUnit"] = game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? game.i18n.localize("SW5E.AbbreviationKgs")
|
||||||
|
: game.i18n.localize("SW5E.AbbreviationLbs");
|
||||||
|
|
||||||
// Return data for rendering
|
// Return data for rendering
|
||||||
return sheetData;
|
return sheetData;
|
||||||
|
|
|
@ -111,9 +111,28 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
||||||
|
|
||||||
// Creature Type
|
// Creature Type
|
||||||
data.labels["type"] = this.actor.labels.creatureType;
|
data.labels["type"] = this.actor.labels.creatureType;
|
||||||
|
|
||||||
|
// Armor Type
|
||||||
|
data.labels["armorType"] = this.getArmorLabel();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format NPC armor information into a localized string.
|
||||||
|
* @return {string} Formatted armor label.
|
||||||
|
*/
|
||||||
|
getArmorLabel() {
|
||||||
|
const ac = this.actor.data.data.attributes.ac;
|
||||||
|
const label = [];
|
||||||
|
if (ac.calc === "default") label.push(this.actor.armor?.name || game.i18n.localize("SW5E.ArmorClassUnarmored"));
|
||||||
|
else label.push(game.i18n.localize(CONFIG.SW5E.armorClasses[ac.calc].label));
|
||||||
|
if (this.actor.shield) label.push(this.actor.shield.name);
|
||||||
|
return label.filterJoin(", ");
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Object Updates */
|
/* Object Updates */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
return mergeObject(super.defaultOptions, {
|
return mergeObject(super.defaultOptions, {
|
||||||
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||||
width: 605,
|
width: 720,
|
||||||
height: 680
|
height: 680
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -47,10 +47,17 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
_computeEncumbrance(totalWeight, actorData) {
|
_computeEncumbrance(totalWeight, actorData) {
|
||||||
// Compute currency weight
|
// Compute currency weight
|
||||||
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
||||||
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
|
||||||
|
const currencyPerWeight = game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? CONFIG.SW5E.encumbrance.currencyPerWeight.metric
|
||||||
|
: CONFIG.SW5E.encumbrance.currencyPerWeight.imperial;
|
||||||
|
|
||||||
|
totalWeight += totalCoins / currencyPerWeight;
|
||||||
|
|
||||||
// Vehicle weights are an order of magnitude greater.
|
// Vehicle weights are an order of magnitude greater.
|
||||||
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
totalWeight /= game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? CONFIG.SW5E.encumbrance.vehicleWeightMultiplier.metric
|
||||||
|
: CONFIG.SW5E.encumbrance.vehicleWeightMultiplier.imperial;
|
||||||
|
|
||||||
// Compute overall encumbrance
|
// Compute overall encumbrance
|
||||||
const max = actorData.data.attributes.capacity.cargo;
|
const max = actorData.data.attributes.capacity.cargo;
|
||||||
|
@ -80,12 +87,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
|
||||||
// Handle crew actions
|
// Handle crew actions
|
||||||
if (item.type === "feat" && item.data.activation.type === "crew") {
|
if (item.type === "feat" && item.data.activation.type === "crew") {
|
||||||
item.crew = item.data.activation.cost;
|
|
||||||
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
|
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
|
||||||
if (item.data.cover === 0.5) item.cover = "½";
|
if (item.data.cover === 0.5) item.cover = "½";
|
||||||
else if (item.data.cover === 0.75) item.cover = "¾";
|
else if (item.data.cover === 0.75) item.cover = "¾";
|
||||||
else if (item.data.cover === null) item.cover = "—";
|
else if (item.data.cover === null) item.cover = "—";
|
||||||
if (item.crew < 1 || item.crew === null) item.crew = "—";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare vehicle weapons
|
// Prepare vehicle weapons
|
||||||
|
@ -114,7 +119,8 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
{
|
{
|
||||||
label: game.i18n.localize("SW5E.Quantity"),
|
label: game.i18n.localize("SW5E.Quantity"),
|
||||||
css: "item-qty",
|
css: "item-qty",
|
||||||
property: "data.quantity"
|
property: "data.quantity",
|
||||||
|
editable: "Number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: game.i18n.localize("SW5E.AC"),
|
label: game.i18n.localize("SW5E.AC"),
|
||||||
|
@ -138,14 +144,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
actions: {
|
actions: {
|
||||||
label: game.i18n.localize("SW5E.ActionPl"),
|
label: game.i18n.localize("SW5E.ActionPl"),
|
||||||
items: [],
|
items: [],
|
||||||
|
hasActions: true,
|
||||||
crewable: true,
|
crewable: true,
|
||||||
dataset: {"type": "feat", "activation.type": "crew"},
|
dataset: {"type": "feat", "activation.type": "crew"},
|
||||||
columns: [
|
columns: [
|
||||||
{
|
|
||||||
label: game.i18n.localize("SW5E.VehicleCrew"),
|
|
||||||
css: "item-crew",
|
|
||||||
property: "crew"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: game.i18n.localize("SW5E.Cover"),
|
label: game.i18n.localize("SW5E.Cover"),
|
||||||
css: "item-cover",
|
css: "item-cover",
|
||||||
|
@ -179,6 +181,13 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
data.items.forEach((item) => {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
const cargo = {
|
const cargo = {
|
||||||
crew: {
|
crew: {
|
||||||
label: game.i18n.localize("SW5E.VehicleCrew"),
|
label: game.i18n.localize("SW5E.VehicleCrew"),
|
||||||
|
@ -284,6 +293,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
.click((evt) => evt.target.select())
|
.click((evt) => evt.target.select())
|
||||||
.change(this._onCargoRowChange.bind(this));
|
.change(this._onCargoRowChange.bind(this));
|
||||||
|
|
||||||
|
html.find(".item-qty:not(.cargo) input")
|
||||||
|
.click((evt) => evt.target.select())
|
||||||
|
.change(this._onQtyChange.bind(this));
|
||||||
|
|
||||||
if (this.actor.data.data.attributes.actions.stations) {
|
if (this.actor.data.data.attributes.actions.stations) {
|
||||||
html.find(".counter.actions, .counter.action-thresholds").hide();
|
html.find(".counter.actions, .counter.action-thresholds").hide();
|
||||||
}
|
}
|
||||||
|
@ -416,6 +429,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special handling for editing quantity value of equipment and weapons inside the features tab.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
_onQtyChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest(".item").dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const qty = parseInt(event.currentTarget.value);
|
||||||
|
event.currentTarget.value = qty;
|
||||||
|
return item.update({"data.quantity": qty});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle toggling an item's crewed status.
|
* Handle toggling an item's crewed status.
|
||||||
* @param event {Event}
|
* @param event {Event}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import Actor5e from "../../entity.js";
|
||||||
import Item5e from "../../../item/entity.js";
|
import Item5e from "../../../item/entity.js";
|
||||||
|
import ProficiencySelector from "../../../apps/proficiency-selector.js";
|
||||||
|
import PropertyAttribution from "../../../apps/property-attribution.js";
|
||||||
import TraitSelector from "../../../apps/trait-selector.js";
|
import TraitSelector from "../../../apps/trait-selector.js";
|
||||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||||
|
@ -6,7 +9,7 @@ import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||||
import {SW5E} from "../../../config.js";
|
import {SW5E} from "../../../config.js";
|
||||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
import ActiveEffect5e from "../../../active-effect.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||||
|
@ -82,6 +85,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
// The Actor's data
|
// The Actor's data
|
||||||
const actorData = this.actor.data.toObject(false);
|
const actorData = this.actor.data.toObject(false);
|
||||||
|
const source = this.actor.data._source.data;
|
||||||
data.actor = actorData;
|
data.actor = actorData;
|
||||||
data.data = actorData.data;
|
data.data = actorData.data;
|
||||||
|
|
||||||
|
@ -102,6 +106,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
abl.icon = this._getProficiencyIcon(abl.proficient);
|
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||||
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||||
abl.label = CONFIG.SW5E.abilities[a];
|
abl.label = CONFIG.SW5E.abilities[a];
|
||||||
|
abl.baseProf = source.abilities[a].proficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skills
|
// Skills
|
||||||
|
@ -111,6 +116,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
skl.icon = this._getProficiencyIcon(skl.value);
|
skl.icon = this._getProficiencyIcon(skl.value);
|
||||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||||
skl.label = CONFIG.SW5E.skills[s];
|
skl.label = CONFIG.SW5E.skills[s];
|
||||||
|
skl.baseValue = source.skills[s].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +133,15 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
this._prepareItems(data);
|
this._prepareItems(data);
|
||||||
|
|
||||||
// Prepare active effects
|
// Prepare active effects
|
||||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
data.effects = ActiveEffect5e.prepareActiveEffectCategories(this.actor.effects);
|
||||||
|
|
||||||
|
// Prepare warnings
|
||||||
|
data.warnings = this.actor._preparationWarnings;
|
||||||
|
|
||||||
|
// Prepare property attributions
|
||||||
|
this.attribution = {
|
||||||
|
"attributes.ac": this._prepareArmorClassAttribution(actorData.data)
|
||||||
|
};
|
||||||
|
|
||||||
// Return data to the sheet
|
// Return data to the sheet
|
||||||
return data;
|
return data;
|
||||||
|
@ -197,6 +211,105 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a list of armor class attribution objects.
|
||||||
|
* @param {object} data Actor data to determine the attributions from.
|
||||||
|
* @return {AttributionDescription[]} List of attribution descriptions.
|
||||||
|
*/
|
||||||
|
_prepareArmorClassAttribution(data) {
|
||||||
|
const ac = data.attributes.ac;
|
||||||
|
const cfg = CONFIG.SW5E.armorClasses[ac.calc];
|
||||||
|
const attribution = [];
|
||||||
|
|
||||||
|
// Base AC Attribution
|
||||||
|
switch (ac.calc) {
|
||||||
|
// Flat AC
|
||||||
|
case "flat":
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: game.i18n.localize("SW5E.ArmorClassFlat"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: ac.flat
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Natural armor
|
||||||
|
case "natural":
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.ArmorClassNatural"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: ac.flat
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Equipment-based AC
|
||||||
|
case "default":
|
||||||
|
const hasArmor = !!this.actor.armor;
|
||||||
|
attribution.push({
|
||||||
|
label: hasArmor ? this.actor.armor.name : game.i18n.localize("SW5E.ArmorClassUnarmored"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: hasArmor ? this.actor.armor.data.data.armor.value : 10
|
||||||
|
});
|
||||||
|
if (ac.dex !== 0) {
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.AbilityDex"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.dex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Other AC formula
|
||||||
|
default:
|
||||||
|
const formula = ac.calc === "custom" ? ac.formula : cfg.formula;
|
||||||
|
let base = ac.base;
|
||||||
|
const dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi);
|
||||||
|
for (const [match, term] of formula.matchAll(dataRgx)) {
|
||||||
|
const value = foundry.utils.getProperty(data, term);
|
||||||
|
if (term === "attributes.ac.base" || value === 0) continue;
|
||||||
|
if (Number.isNumeric(value)) base -= Number(value);
|
||||||
|
attribution.push({
|
||||||
|
label: match,
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: foundry.utils.getProperty(data, term)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
attribution.unshift({
|
||||||
|
label: game.i18n.localize("SW5E.PropertyBase"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
value: base
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shield
|
||||||
|
if (ac.shield !== 0)
|
||||||
|
attribution.push({
|
||||||
|
label: this.actor.shield?.name ?? game.i18n.localize("SW5E.EquipmentShield"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.shield
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bonus
|
||||||
|
if (ac.bonus !== 0)
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.Bonus"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.bonus
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cover
|
||||||
|
if (ac.cover !== 0)
|
||||||
|
attribution.push({
|
||||||
|
label: game.i18n.localize("SW5E.Cover"),
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: ac.cover
|
||||||
|
});
|
||||||
|
return attribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
||||||
* @param {object} traits The raw traits data object from the actor data
|
* @param {object} traits The raw traits data object from the actor data
|
||||||
|
@ -208,10 +321,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
di: CONFIG.SW5E.damageResistanceTypes,
|
di: CONFIG.SW5E.damageResistanceTypes,
|
||||||
dv: CONFIG.SW5E.damageResistanceTypes,
|
dv: CONFIG.SW5E.damageResistanceTypes,
|
||||||
ci: CONFIG.SW5E.conditionTypes,
|
ci: CONFIG.SW5E.conditionTypes,
|
||||||
languages: CONFIG.SW5E.languages,
|
languages: CONFIG.SW5E.languages
|
||||||
armorProf: CONFIG.SW5E.armorProficiencies,
|
|
||||||
weaponProf: CONFIG.SW5E.weaponProficiencies,
|
|
||||||
toolProf: CONFIG.SW5E.toolProficiencies
|
|
||||||
};
|
};
|
||||||
for (let [t, choices] of Object.entries(map)) {
|
for (let [t, choices] of Object.entries(map)) {
|
||||||
const trait = traits[t];
|
const trait = traits[t];
|
||||||
|
@ -231,6 +341,14 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
}
|
}
|
||||||
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate and localize proficiencies
|
||||||
|
for (const t of ["armor", "weapon", "tool"]) {
|
||||||
|
const trait = traits[`${t}Prof`];
|
||||||
|
if (!trait) continue;
|
||||||
|
Actor5e.prepareProficiencies(trait, t);
|
||||||
|
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -418,6 +536,9 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
// View Item Sheets
|
// View Item Sheets
|
||||||
html.find(".item-edit").click(this._onItemEdit.bind(this));
|
html.find(".item-edit").click(this._onItemEdit.bind(this));
|
||||||
|
|
||||||
|
// Property attributions
|
||||||
|
html.find(".attributable").mouseover(this._onPropertyAttribution.bind(this));
|
||||||
|
|
||||||
// Editable Only Listeners
|
// Editable Only Listeners
|
||||||
if (this.isEditable) {
|
if (this.isEditable) {
|
||||||
// Input focus and update
|
// Input focus and update
|
||||||
|
@ -432,6 +553,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
||||||
|
|
||||||
// Trait Selector
|
// Trait Selector
|
||||||
|
html.find(".proficiency-selector").click(this._onProficiencySelector.bind(this));
|
||||||
html.find(".trait-selector").click(this._onTraitSelector.bind(this));
|
html.find(".trait-selector").click(this._onTraitSelector.bind(this));
|
||||||
|
|
||||||
// Configure Special Flags
|
// Configure Special Flags
|
||||||
|
@ -446,7 +568,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
|
html.find(".slot-max-override").click(this._onPowerSlotOverride.bind(this));
|
||||||
|
|
||||||
// Active Effect management
|
// Active Effect management
|
||||||
html.find(".effect-control").click((ev) => onManageActiveEffect(ev, this.actor));
|
html.find(".effect-control").click((ev) => ActiveEffect5e.onManageActiveEffect(ev, this.actor));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Owner Only Listeners
|
// Owner Only Listeners
|
||||||
|
@ -474,7 +596,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iinitialize Item list filters by activating the set of filters which are currently applied
|
* Initialize Item list filters by activating the set of filters which are currently applied
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_initializeFilterItemList(i, ul) {
|
_initializeFilterItemList(i, ul) {
|
||||||
|
@ -517,6 +639,9 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
const button = event.currentTarget;
|
const button = event.currentTarget;
|
||||||
let app;
|
let app;
|
||||||
switch (button.dataset.action) {
|
switch (button.dataset.action) {
|
||||||
|
case "armor":
|
||||||
|
app = new ActorArmorConfig(this.object);
|
||||||
|
break;
|
||||||
case "hit-dice":
|
case "hit-dice":
|
||||||
app = new ActorHitDiceConfig(this.object);
|
app = new ActorHitDiceConfig(this.object);
|
||||||
break;
|
break;
|
||||||
|
@ -530,7 +655,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
app = new ActorSensesConfig(this.object);
|
app = new ActorSensesConfig(this.object);
|
||||||
break;
|
break;
|
||||||
case "type":
|
case "type":
|
||||||
new ActorTypeConfig(this.object).render(true);
|
app = new ActorTypeConfig(this.object);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
app?.render(true);
|
app?.render(true);
|
||||||
|
@ -545,22 +670,19 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
*/
|
*/
|
||||||
_onCycleSkillProficiency(event) {
|
_onCycleSkillProficiency(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const field = $(event.currentTarget).siblings('input[type="hidden"]');
|
const field = event.currentTarget.previousElementSibling;
|
||||||
|
const skillName = field.parentElement.dataset.skill;
|
||||||
|
const source = this.actor.data._source.data.skills[skillName];
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
// Get the current level and the array of levels
|
// Cycle to the next or previous skill level
|
||||||
const level = parseFloat(field.val());
|
|
||||||
const levels = [0, 1, 0.5, 2];
|
const levels = [0, 1, 0.5, 2];
|
||||||
let idx = levels.indexOf(level);
|
let idx = levels.indexOf(source.value);
|
||||||
|
const next = idx + (event.type === "click" ? 1 : 3);
|
||||||
// Toggle next level - forward on click, backwards on right
|
field.value = levels[next % 4];
|
||||||
if (event.type === "click") {
|
|
||||||
field.val(levels[idx === levels.length - 1 ? 0 : idx + 1]);
|
|
||||||
} else if (event.type === "contextmenu") {
|
|
||||||
field.val(levels[idx === 0 ? levels.length - 1 : idx - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the field value and save the form
|
// Update the field value and save the form
|
||||||
this._onSubmit(event);
|
return this._onSubmit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -675,7 +797,12 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
|
if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
|
||||||
const similarItem = this.actor.items.find((i) => {
|
const similarItem = this.actor.items.find((i) => {
|
||||||
const sourceId = i.getFlag("core", "sourceId");
|
const sourceId = i.getFlag("core", "sourceId");
|
||||||
return sourceId && sourceId === itemData.flags.core?.sourceId && i.type === "consumable";
|
return (
|
||||||
|
sourceId &&
|
||||||
|
sourceId === itemData.flags.core?.sourceId &&
|
||||||
|
i.type === "consumable" &&
|
||||||
|
i.name === itemData.name
|
||||||
|
);
|
||||||
});
|
});
|
||||||
if (similarItem) {
|
if (similarItem) {
|
||||||
return similarItem.update({
|
return similarItem.update({
|
||||||
|
@ -832,6 +959,22 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle displaying the property attribution tooltip when a property is hovered over.
|
||||||
|
* @param {Event} event The originating mouse event.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onPropertyAttribution(event) {
|
||||||
|
const existingTooltip = event.currentTarget.querySelector("div.tooltip");
|
||||||
|
const property = event.currentTarget.dataset.property;
|
||||||
|
if (existingTooltip || !property || !this.attribution) return;
|
||||||
|
|
||||||
|
let html = await new PropertyAttribution(this.object, this.attribution, property).renderTooltip();
|
||||||
|
event.currentTarget.insertAdjacentElement("beforeend", html[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle rolling an Ability check, either a test or a saving throw
|
* Handle rolling an Ability check, either a test or a saving throw
|
||||||
* @param {Event} event The originating click event
|
* @param {Event} event The originating click event
|
||||||
|
@ -888,6 +1031,22 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle spawning the ProficiencySelector application to configure armor, weapon, and tool proficiencies.
|
||||||
|
* @param {Event} event The click event which originated the selection
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onProficiencySelector(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const a = event.currentTarget;
|
||||||
|
const label = a.parentElement.querySelector("label");
|
||||||
|
const options = {name: a.dataset.target, title: label.innerText, type: a.dataset.type};
|
||||||
|
if (options.type === "tool") options.sortCategories = true;
|
||||||
|
return new ProficiencySelector(this.actor, options).render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||||
* @param {Event} event The click event which originated the selection
|
* @param {Event} event The click event which originated the selection
|
||||||
|
|
|
@ -51,6 +51,11 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
})
|
})
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
|
// Weight unit
|
||||||
|
sheetData["weightUnit"] = game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? game.i18n.localize("SW5E.AbbreviationKgs")
|
||||||
|
: game.i18n.localize("SW5E.AbbreviationLbs");
|
||||||
|
|
||||||
// Return data for rendering
|
// Return data for rendering
|
||||||
return sheetData;
|
return sheetData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,9 +96,28 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
|
|
||||||
// Creature Type
|
// Creature Type
|
||||||
data.labels["type"] = this.actor.labels.creatureType;
|
data.labels["type"] = this.actor.labels.creatureType;
|
||||||
|
|
||||||
|
// Armor Type
|
||||||
|
data.labels["armorType"] = this.getArmorLabel();
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format NPC armor information into a localized string.
|
||||||
|
* @return {string} Formatted armor label.
|
||||||
|
*/
|
||||||
|
getArmorLabel() {
|
||||||
|
const ac = this.actor.data.data.attributes.ac;
|
||||||
|
const label = [];
|
||||||
|
if (ac.calc === "default") label.push(this.actor.armor?.name || game.i18n.localize("SW5E.ArmorClassUnarmored"));
|
||||||
|
else label.push(game.i18n.localize(CONFIG.SW5E.armorClasses[ac.calc].label));
|
||||||
|
if (this.actor.shield) label.push(this.actor.shield.name);
|
||||||
|
return label.filterJoin(", ");
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Object Updates */
|
/* Object Updates */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
return mergeObject(super.defaultOptions, {
|
return mergeObject(super.defaultOptions, {
|
||||||
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||||
width: 605,
|
width: 720,
|
||||||
height: 680
|
height: 680
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -47,10 +47,17 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
_computeEncumbrance(totalWeight, actorData) {
|
_computeEncumbrance(totalWeight, actorData) {
|
||||||
// Compute currency weight
|
// Compute currency weight
|
||||||
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
||||||
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
|
||||||
|
const currencyPerWeight = game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? CONFIG.SW5E.encumbrance.currencyPerWeight.metric
|
||||||
|
: CONFIG.SW5E.encumbrance.currencyPerWeight.imperial;
|
||||||
|
|
||||||
|
totalWeight += totalCoins / currencyPerWeight;
|
||||||
|
|
||||||
// Vehicle weights are an order of magnitude greater.
|
// Vehicle weights are an order of magnitude greater.
|
||||||
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
totalWeight /= game.settings.get("sw5e", "metricWeightUnits")
|
||||||
|
? CONFIG.SW5E.encumbrance.vehicleWeightMultiplier.metric
|
||||||
|
: CONFIG.SW5E.encumbrance.vehicleWeightMultiplier.imperial;
|
||||||
|
|
||||||
// Compute overall encumbrance
|
// Compute overall encumbrance
|
||||||
const max = actorData.data.attributes.capacity.cargo;
|
const max = actorData.data.attributes.capacity.cargo;
|
||||||
|
@ -80,12 +87,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
|
||||||
// Handle crew actions
|
// Handle crew actions
|
||||||
if (item.type === "feat" && item.data.activation.type === "crew") {
|
if (item.type === "feat" && item.data.activation.type === "crew") {
|
||||||
item.crew = item.data.activation.cost;
|
|
||||||
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
|
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? "CoverTotal" : "None"}`);
|
||||||
if (item.data.cover === 0.5) item.cover = "½";
|
if (item.data.cover === 0.5) item.cover = "½";
|
||||||
else if (item.data.cover === 0.75) item.cover = "¾";
|
else if (item.data.cover === 0.75) item.cover = "¾";
|
||||||
else if (item.data.cover === null) item.cover = "—";
|
else if (item.data.cover === null) item.cover = "—";
|
||||||
if (item.crew < 1 || item.crew === null) item.crew = "—";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare vehicle weapons
|
// Prepare vehicle weapons
|
||||||
|
@ -114,7 +119,8 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
{
|
{
|
||||||
label: game.i18n.localize("SW5E.Quantity"),
|
label: game.i18n.localize("SW5E.Quantity"),
|
||||||
css: "item-qty",
|
css: "item-qty",
|
||||||
property: "data.quantity"
|
property: "data.quantity",
|
||||||
|
editable: "Number"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: game.i18n.localize("SW5E.AC"),
|
label: game.i18n.localize("SW5E.AC"),
|
||||||
|
@ -138,14 +144,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
actions: {
|
actions: {
|
||||||
label: game.i18n.localize("SW5E.ActionPl"),
|
label: game.i18n.localize("SW5E.ActionPl"),
|
||||||
items: [],
|
items: [],
|
||||||
|
hasActions: true,
|
||||||
crewable: true,
|
crewable: true,
|
||||||
dataset: {"type": "feat", "activation.type": "crew"},
|
dataset: {"type": "feat", "activation.type": "crew"},
|
||||||
columns: [
|
columns: [
|
||||||
{
|
|
||||||
label: game.i18n.localize("SW5E.VehicleCrew"),
|
|
||||||
css: "item-crew",
|
|
||||||
property: "crew"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: game.i18n.localize("SW5E.Cover"),
|
label: game.i18n.localize("SW5E.Cover"),
|
||||||
css: "item-cover",
|
css: "item-cover",
|
||||||
|
@ -179,6 +181,13 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
data.items.forEach((item) => {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
const cargo = {
|
const cargo = {
|
||||||
crew: {
|
crew: {
|
||||||
label: game.i18n.localize("SW5E.VehicleCrew"),
|
label: game.i18n.localize("SW5E.VehicleCrew"),
|
||||||
|
@ -284,6 +293,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
.click((evt) => evt.target.select())
|
.click((evt) => evt.target.select())
|
||||||
.change(this._onCargoRowChange.bind(this));
|
.change(this._onCargoRowChange.bind(this));
|
||||||
|
|
||||||
|
html.find(".item-qty:not(.cargo) input")
|
||||||
|
.click((evt) => evt.target.select())
|
||||||
|
.change(this._onQtyChange.bind(this));
|
||||||
|
|
||||||
if (this.actor.data.data.attributes.actions.stations) {
|
if (this.actor.data.data.attributes.actions.stations) {
|
||||||
html.find(".counter.actions, .counter.action-thresholds").hide();
|
html.find(".counter.actions, .counter.action-thresholds").hide();
|
||||||
}
|
}
|
||||||
|
@ -416,6 +429,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special handling for editing quantity value of equipment and weapons inside the features tab.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
_onQtyChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest(".item").dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const qty = parseInt(event.currentTarget.value);
|
||||||
|
event.currentTarget.value = qty;
|
||||||
|
return item.update({"data.quantity": qty});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle toggling an item's crewed status.
|
* Handle toggling an item's crewed status.
|
||||||
* @param event {Event}
|
* @param event {Event}
|
||||||
|
|
94
module/apps/actor-armor.js
Normal file
94
module/apps/actor-armor.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* Interface for managing a character's armor calculation.
|
||||||
|
* @extends {DocumentSheet}
|
||||||
|
*/
|
||||||
|
export default class ActorArmorConfig extends DocumentSheet {
|
||||||
|
/** @inheritdoc */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
id: "actor-armor-config",
|
||||||
|
classes: ["sw5e", "actor-armor-config"],
|
||||||
|
template: "systems/sw5e/templates/apps/actor-armor.html",
|
||||||
|
width: 320,
|
||||||
|
height: "auto"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
get title() {
|
||||||
|
return `${game.i18n.localize("SW5E.ArmorConfig")}: ${this.document.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async getData() {
|
||||||
|
// Get actor AC data
|
||||||
|
const actorData = foundry.utils.deepClone(this.object.data.data);
|
||||||
|
const ac = foundry.utils.getProperty(actorData, "attributes.ac");
|
||||||
|
|
||||||
|
// Get configuration data for the calculation mode
|
||||||
|
let cfg = CONFIG.SW5E.armorClasses[ac.calc];
|
||||||
|
if (!cfg) {
|
||||||
|
ac.calc = "flat";
|
||||||
|
cfg = CONFIG.SW5E.armorClasses.flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return context data
|
||||||
|
return {
|
||||||
|
ac: ac,
|
||||||
|
calculations: CONFIG.SW5E.armorClasses,
|
||||||
|
value: this.object._computeArmorClass(actorData).value,
|
||||||
|
valueDisabled: !["flat", "natural"].includes(ac.calc),
|
||||||
|
formula: ac.calc === "custom" ? ac.formula : cfg.formula,
|
||||||
|
formulaDisabled: ac.calc !== "custom"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _updateObject(event, formData) {
|
||||||
|
const ac = foundry.utils.expandObject(formData).ac;
|
||||||
|
return this.object.update({"data.attributes.ac": ac});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Event Listeners and Handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onChangeInput(event) {
|
||||||
|
await super._onChangeInput(event);
|
||||||
|
|
||||||
|
// Reference actor data
|
||||||
|
let actorData = this.object.toObject(false);
|
||||||
|
let ac = actorData.data.attributes.ac;
|
||||||
|
|
||||||
|
// Reference form data
|
||||||
|
const calc = this.form["ac.calc"].value;
|
||||||
|
const cfg = CONFIG.SW5E.armorClasses[calc];
|
||||||
|
const enableFlat = ["flat", "natural"].includes(calc);
|
||||||
|
|
||||||
|
// Handle changes to the calculation mode specifically
|
||||||
|
let formula = this.form["ac.formula"].value;
|
||||||
|
let flat = this.form["ac.flat"].value;
|
||||||
|
if (event.currentTarget.name === "ac.calc") {
|
||||||
|
formula = calc === "custom" ? ac.formula : cfg.formula;
|
||||||
|
if (enableFlat) flat = ac.flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recompute effective AC
|
||||||
|
actorData = foundry.utils.mergeObject(actorData, {"data.attributes.ac": {calc, formula}});
|
||||||
|
if (enableFlat) actorData.data.attributes.ac.flat = flat;
|
||||||
|
ac = this.object._computeArmorClass(actorData.data);
|
||||||
|
|
||||||
|
// Update fields
|
||||||
|
this.form["ac.formula"].value = ac.formula;
|
||||||
|
this.form["ac.formula"].disabled = calc !== "custom";
|
||||||
|
this.form["ac.flat"].value = enableFlat ? ac.flat : ac.value;
|
||||||
|
this.form["ac.flat"].disabled = !enableFlat;
|
||||||
|
}
|
||||||
|
}
|
|
@ -99,7 +99,7 @@ export default class ActorSheetFlags extends DocumentSheet {
|
||||||
{name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"}
|
{name: "data.bonuses.power.techDC", label: "SW5E.BonusTechPowerDC"}
|
||||||
];
|
];
|
||||||
for (let b of bonuses) {
|
for (let b of bonuses) {
|
||||||
b.value = getProperty(this.object._data, b.name) || "";
|
b.value = getProperty(this.object.data._source, b.name) || "";
|
||||||
}
|
}
|
||||||
return bonuses;
|
return bonuses;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ export default class ActorSheetFlags extends DocumentSheet {
|
||||||
for (let [k, v] of Object.entries(flags)) {
|
for (let [k, v] of Object.entries(flags)) {
|
||||||
if ([undefined, null, "", false, 0].includes(v)) {
|
if ([undefined, null, "", false, 0].includes(v)) {
|
||||||
delete flags[k];
|
delete flags[k];
|
||||||
if (hasProperty(actor._data.flags, `sw5e.${k}`)) {
|
if (hasProperty(actor.data._source.flags, `sw5e.${k}`)) {
|
||||||
unset = true;
|
unset = true;
|
||||||
flags[`-=${k}`] = null;
|
flags[`-=${k}`] = null;
|
||||||
}
|
}
|
||||||
|
|
120
module/apps/proficiency-selector.js
Normal file
120
module/apps/proficiency-selector.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import TraitSelector from "./trait-selector.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An application for selecting proficiencies with categories that can contain children.
|
||||||
|
*
|
||||||
|
* @extends {TraitSelector}
|
||||||
|
*/
|
||||||
|
export default class ProficiencySelector extends TraitSelector {
|
||||||
|
/** @inheritdoc */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
title: "Actor Proficiency Selection",
|
||||||
|
type: "",
|
||||||
|
sortCategories: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async getData() {
|
||||||
|
const attr = foundry.utils.getProperty(this.object.data, this.attribute);
|
||||||
|
const value = this.options.valueKey ? foundry.utils.getProperty(attr, this.options.valueKey) ?? [] : attr;
|
||||||
|
|
||||||
|
this.options.choices = CONFIG.SW5E[`${this.options.type}Proficiencies`];
|
||||||
|
const data = super.getData();
|
||||||
|
|
||||||
|
const pack = game.packs.get(CONFIG.SW5E.sourcePacks.ITEMS);
|
||||||
|
const ids = CONFIG.SW5E[`${this.options.type}Ids`];
|
||||||
|
const map = CONFIG.SW5E[`${this.options.type}ProficienciesMap`];
|
||||||
|
if (ids !== undefined) {
|
||||||
|
const typeProperty = this.options.type !== "armor" ? `${this.options.type}Type` : `armor.type`;
|
||||||
|
for (const [key, id] of Object.entries(ids)) {
|
||||||
|
const item = await pack.getDocument(id);
|
||||||
|
let type = foundry.utils.getProperty(item.data.data, typeProperty);
|
||||||
|
if (map && map[type]) type = map[type];
|
||||||
|
const entry = {
|
||||||
|
label: item.name,
|
||||||
|
chosen: attr ? value.includes(key) : false
|
||||||
|
};
|
||||||
|
if (data.choices[type] === undefined) {
|
||||||
|
data.choices[key] = entry;
|
||||||
|
} else {
|
||||||
|
if (data.choices[type].children === undefined) {
|
||||||
|
data.choices[type].children = {};
|
||||||
|
}
|
||||||
|
data.choices[type].children[key] = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.type === "tool") {
|
||||||
|
data.choices["vehicle"].children = Object.entries(CONFIG.SW5E.vehicleTypes).reduce((obj, [key, label]) => {
|
||||||
|
obj[key] = {
|
||||||
|
label: label,
|
||||||
|
chosen: attr ? value.includes(key) : false
|
||||||
|
};
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.sortCategories) data.choices = this._sortObject(data.choices);
|
||||||
|
for (const category of Object.values(data.choices)) {
|
||||||
|
if (!category.children) continue;
|
||||||
|
category.children = this._sortObject(category.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the provided object and sort by the "label" property.
|
||||||
|
*
|
||||||
|
* @param {object} object Object to be sorted.
|
||||||
|
* @return {object} Sorted object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_sortObject(object) {
|
||||||
|
return Object.fromEntries(Object.entries(object).sort((lhs, rhs) => lhs[1].label.localeCompare(rhs[1].label)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
|
||||||
|
for (const checkbox of html[0].querySelectorAll("input[type='checkbox']")) {
|
||||||
|
if (checkbox.checked) this._onToggleCategory(checkbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onChangeInput(event) {
|
||||||
|
super._onChangeInput(event);
|
||||||
|
|
||||||
|
if (event.target.tagName === "INPUT") this._onToggleCategory(event.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable all children when a category is checked.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} checkbox Checkbox that was changed.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onToggleCategory(checkbox) {
|
||||||
|
const children = checkbox.closest("li")?.querySelector("ol");
|
||||||
|
if (!children) return;
|
||||||
|
|
||||||
|
for (const child of children.querySelectorAll("input[type='checkbox']")) {
|
||||||
|
child.checked = child.disabled = checkbox.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
module/apps/property-attribution.js
Normal file
92
module/apps/property-attribution.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/**
|
||||||
|
* Description for a single part of a property attribution.
|
||||||
|
*
|
||||||
|
* @typedef {object} AttributionDescription
|
||||||
|
* @property {string} label Descriptive label that will be displayed. If the label is in the form
|
||||||
|
* of an @ property, the system will try to turn it into a human-readable label.
|
||||||
|
* @property {number} mode Application mode for this step as defined in
|
||||||
|
* [CONST.ACTIVE_EFFECT_MODES](https://foundryvtt.com/api/module-constants.html#.ACTIVE_EFFECT_MODES).
|
||||||
|
* @property {number} value Value of this step.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for viewing what factors went into determining a specific property.
|
||||||
|
*
|
||||||
|
* @extends {DocumentSheet}
|
||||||
|
*/
|
||||||
|
export default class PropertyAttribution extends Application {
|
||||||
|
/**
|
||||||
|
* @param {Document} object - Object containing the property to be attributed.
|
||||||
|
* @param {object.<string, AttributionDescription[]>} attribution - Object containing all of the attribution data.
|
||||||
|
* @param {string} property - Dot separated path to the property.
|
||||||
|
*/
|
||||||
|
constructor(object, attribution, property, options = {}) {
|
||||||
|
super(options);
|
||||||
|
this.object = object;
|
||||||
|
this.attribution = attribution;
|
||||||
|
this.property = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
id: "property-attribution",
|
||||||
|
classes: ["sw5e", "property-attribution"],
|
||||||
|
template: "systems/sw5e/templates/apps/property-attribution.html",
|
||||||
|
width: 320,
|
||||||
|
height: "auto"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render this view as a tooltip rather than a whole window.
|
||||||
|
* @return {jQuery} HTML of the rendered tooltip.
|
||||||
|
*/
|
||||||
|
async renderTooltip() {
|
||||||
|
const data = this.getData(this.options);
|
||||||
|
let html = await this._renderInner(data);
|
||||||
|
html[0].classList.add("tooltip");
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
const property = foundry.utils.getProperty(this.object.data.data, this.property);
|
||||||
|
let total;
|
||||||
|
if (Number.isNumeric(property)) {
|
||||||
|
total = property;
|
||||||
|
} else if (typeof property === "object" && Number.isNumeric(property.value)) {
|
||||||
|
total = property.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sources = foundry.utils.duplicate(this.attribution[this.property]);
|
||||||
|
return {
|
||||||
|
sources: sources.map((entry) => {
|
||||||
|
if (entry.label.startsWith("@")) {
|
||||||
|
entry.label = this.getPropertyLabel(entry.label.slice(1));
|
||||||
|
}
|
||||||
|
if (entry.mode === CONST.ACTIVE_EFFECT_MODES.ADD && entry.value < 0) {
|
||||||
|
entry.negative = true;
|
||||||
|
entry.value = entry.value * -1;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}),
|
||||||
|
total: total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
getPropertyLabel(property) {
|
||||||
|
const parts = property.split(".");
|
||||||
|
if (parts[0] === "abilities" && parts[1]) {
|
||||||
|
return CONFIG.SW5E.abilities[parts[1]];
|
||||||
|
}
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,13 @@ export default class TraitSelector extends DocumentSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
get title() {
|
||||||
|
return this.options.title || super.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a reference to the target attribute
|
* Return a reference to the target attribute
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@ -37,8 +44,8 @@ export default class TraitSelector extends DocumentSheet {
|
||||||
getData() {
|
getData() {
|
||||||
const attr = foundry.utils.getProperty(this.object.data, this.attribute);
|
const attr = foundry.utils.getProperty(this.object.data, this.attribute);
|
||||||
const o = this.options;
|
const o = this.options;
|
||||||
const value = o.valueKey ? attr[o.valueKey] ?? [] : attr;
|
const value = o.valueKey ? foundry.utils.getProperty(attr, o.valueKey) ?? [] : attr;
|
||||||
const custom = o.customKey ? attr[o.customKey] ?? "" : "";
|
const custom = o.customKey ? foundry.utils.getProperty(attr, o.customKey) ?? "" : "";
|
||||||
|
|
||||||
// Populate choices
|
// Populate choices
|
||||||
const choices = Object.entries(o.choices).reduce((obj, e) => {
|
const choices = Object.entries(o.choices).reduce((obj, e) => {
|
||||||
|
|
179
module/config.js
179
module/config.js
|
@ -166,11 +166,14 @@ SW5E.weaponIds = {
|
||||||
"whip": "QKTyxoO0YDnAsbYe"
|
"whip": "QKTyxoO0YDnAsbYe"
|
||||||
};
|
};
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
SW5E.toolProficiencies = {
|
/**
|
||||||
|
* The categories into which Tool items can be grouped.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
SW5E.toolTypes = {
|
||||||
armor: "SW5E.ToolArmormech",
|
armor: "SW5E.ToolArmormech",
|
||||||
arms: "SW5E.ToolArmstech",
|
arms: "SW5E.ToolArmstech",
|
||||||
arti: "SW5E.ToolArtificer",
|
arti: "SW5E.ToolArtificer",
|
||||||
|
@ -199,7 +202,16 @@ SW5E.toolProficiencies = {
|
||||||
secur: "SW5E.ToolSecurityKit",
|
secur: "SW5E.ToolSecurityKit",
|
||||||
slic: "SW5E.ToolSlicerKit",
|
slic: "SW5E.ToolSlicerKit",
|
||||||
spice: "SW5E.ToolSpiceKit",
|
spice: "SW5E.ToolSpiceKit",
|
||||||
music: "SW5E.ToolMusicalInstrument",
|
music: "SW5E.ToolMusicalInstrument"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The categories of tool proficiencies that a character can gain.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
SW5E.toolProficiencies = {
|
||||||
|
...SW5E.toolTypes,
|
||||||
vehicle: "SW5E.ToolVehicle"
|
vehicle: "SW5E.ToolVehicle"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -211,41 +223,7 @@ SW5E.toolProficiencies = {
|
||||||
*
|
*
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
SW5E.toolIds = {
|
SW5E.toolIds = {
|
||||||
"alchemist": "SztwZhbhZeCqyAes",
|
|
||||||
"bagpipes": "yxHi57T5mmVt0oDr",
|
|
||||||
"brewer": "Y9S75go1hLMXUD48",
|
|
||||||
"calligrapher": "jhjo20QoiD5exf09",
|
|
||||||
"card": "YwlHI3BVJapz4a3E",
|
|
||||||
"carpenter": "8NS6MSOdXtUqD7Ib",
|
|
||||||
"cartographer": "fC0lFK8P4RuhpfaU",
|
|
||||||
"cobbler": "hM84pZnpCqKfi8XH",
|
|
||||||
"cook": "Gflnp29aEv5Lc1ZM",
|
|
||||||
"dice": "iBuTM09KD9IoM5L8",
|
|
||||||
"disg": "IBhDAr7WkhWPYLVn",
|
|
||||||
"drum": "69Dpr25pf4BjkHKb",
|
|
||||||
"dulcimer": "NtdDkjmpdIMiX7I2",
|
|
||||||
"flute": "eJOrPcAz9EcquyRQ",
|
|
||||||
"forg": "cG3m4YlHfbQlLEOx",
|
|
||||||
"glassblower": "rTbVrNcwApnuTz5E",
|
|
||||||
"herb": "i89okN7GFTWHsvPy",
|
|
||||||
"horn": "aa9KuBy4dst7WIW9",
|
|
||||||
"jeweler": "YfBwELTgPFHmQdHh",
|
|
||||||
"leatherworker": "PUMfwyVUbtyxgYbD",
|
|
||||||
"lute": "qBydtUUIkv520DT7",
|
|
||||||
"lyre": "EwG1EtmbgR3bM68U",
|
|
||||||
"mason": "skUih6tBvcBbORzA",
|
|
||||||
"navg": "YHCmjsiXxZ9UdUhU",
|
|
||||||
"painter": "ccm5xlWhx74d6lsK",
|
|
||||||
"panflute": "G5m5gYIx9VAUWC3J",
|
|
||||||
"pois": "il2GNi8C0DvGLL9P",
|
|
||||||
"potter": "hJS8yEVkqgJjwfWa",
|
|
||||||
"shawm": "G3cqbejJpfB91VhP",
|
|
||||||
"smith": "KndVe2insuctjIaj",
|
|
||||||
"thief": "woWZ1sO5IUVGzo58",
|
|
||||||
"tinker": "0d08g1i5WXnNrCNA",
|
|
||||||
"viol": "baoe3U5BfMMMxhCU",
|
|
||||||
"weaver": "ap9prThUB2y9lDyj",
|
|
||||||
"woodcarver": "xKErqkLo4ASYr5EP",
|
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -372,6 +350,21 @@ SW5E.itemCapacityTypes = {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of various item rarities.
|
||||||
|
* @enum {String}
|
||||||
|
*/
|
||||||
|
SW5E.itemRarity = {
|
||||||
|
common: "SW5E.ItemRarityCommon",
|
||||||
|
uncommon: "SW5E.ItemRarityUncommon",
|
||||||
|
rare: "SW5E.ItemRarityRare",
|
||||||
|
veryRare: "SW5E.ItemRarityVeryRare",
|
||||||
|
legendary: "SW5E.ItemRarityLegendary",
|
||||||
|
artifact: "SW5E.ItemRarityArtifact"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumerate the lengths of time over which an item can have limited use ability
|
* Enumerate the lengths of time over which an item can have limited use ability
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
@ -387,25 +380,47 @@ SW5E.limitedUsePeriods = {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific equipment types that modify base AC
|
||||||
|
* @type {object}
|
||||||
|
*/
|
||||||
|
SW5E.armorTypes = {
|
||||||
|
light: "SW5E.EquipmentLight",
|
||||||
|
medium: "SW5E.EquipmentMedium",
|
||||||
|
heavy: "SW5E.EquipmentHeavy",
|
||||||
|
natural: "SW5E.EquipmentNatural",
|
||||||
|
shield: "SW5E.EquipmentShield"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of equipment types for armor, clothing, and other objects which can be worn by the character
|
* The set of equipment types for armor, clothing, and other objects which can be worn by the character
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
SW5E.equipmentTypes = {
|
SW5E.equipmentTypes = {
|
||||||
light: "SW5E.EquipmentLight",
|
|
||||||
medium: "SW5E.EquipmentMedium",
|
|
||||||
heavy: "SW5E.EquipmentHeavy",
|
|
||||||
hyper: "SW5E.EquipmentHyperdrive",
|
hyper: "SW5E.EquipmentHyperdrive",
|
||||||
bonus: "SW5E.EquipmentBonus",
|
bonus: "SW5E.EquipmentBonus",
|
||||||
natural: "SW5E.EquipmentNatural",
|
|
||||||
powerc: "SW5E.EquipmentPowerCoupling",
|
powerc: "SW5E.EquipmentPowerCoupling",
|
||||||
reactor: "SW5E.EquipmentReactor",
|
reactor: "SW5E.EquipmentReactor",
|
||||||
shield: "SW5E.EquipmentShield",
|
|
||||||
clothing: "SW5E.EquipmentClothing",
|
clothing: "SW5E.EquipmentClothing",
|
||||||
trinket: "SW5E.EquipmentTrinket",
|
trinket: "SW5E.EquipmentTrinket",
|
||||||
ssarmor: "SW5E.EquipmentStarshipArmor",
|
ssarmor: "SW5E.EquipmentStarshipArmor",
|
||||||
ssshield: "SW5E.EquipmentStarshipShield",
|
ssshield: "SW5E.EquipmentStarshipShield",
|
||||||
vehicle: "SW5E.EquipmentVehicle"
|
vehicle: "SW5E.EquipmentVehicle",
|
||||||
|
...SW5E.armorTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The various types of vehicles in which characters can be proficient.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
SW5E.vehicleTypes = {
|
||||||
|
air: "SW5E.VehicleTypeAir",
|
||||||
|
land: "SW5E.VehicleTypeLand",
|
||||||
|
water: "SW5E.VehicleTypeWater"
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -435,6 +450,61 @@ SW5E.armorProficienciesMap = {
|
||||||
shield: "shl"
|
shield: "shl"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Same as weapon IDs
|
||||||
|
/**
|
||||||
|
* The basic armor types in 5e. This enables specific armor proficiencies,
|
||||||
|
* automated AC calculation in NPCs, and starting equipment.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
SW5E.armorIds = {};
|
||||||
|
|
||||||
|
// TODO: Same as weapon IDs
|
||||||
|
/**
|
||||||
|
* The basic shield in 5e.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
SW5E.shieldIds = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common armor class calculations.
|
||||||
|
* @enum {object}
|
||||||
|
*/
|
||||||
|
SW5E.armorClasses = {
|
||||||
|
flat: {
|
||||||
|
label: "SW5E.ArmorClassFlat",
|
||||||
|
formula: "@attributes.ac.flat"
|
||||||
|
},
|
||||||
|
natural: {
|
||||||
|
label: "SW5E.ArmorClassNatural",
|
||||||
|
formula: "@attributes.ac.flat"
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
label: "SW5E.ArmorClassEquipment",
|
||||||
|
formula: "@attributes.ac.base + @abilities.dex.mod"
|
||||||
|
},
|
||||||
|
mage: {
|
||||||
|
label: "SW5E.ArmorClassMage",
|
||||||
|
formula: "13 + @abilities.dex.mod"
|
||||||
|
},
|
||||||
|
draconic: {
|
||||||
|
label: "SW5E.ArmorClassDraconic",
|
||||||
|
formula: "13 + @abilities.dex.mod"
|
||||||
|
},
|
||||||
|
unarmoredMonk: {
|
||||||
|
label: "SW5E.ArmorClassUnarmoredMonk",
|
||||||
|
formula: "10 + @abilities.dex.mod + @abilities.wis.mod"
|
||||||
|
},
|
||||||
|
unarmoredBarb: {
|
||||||
|
label: "SW5E.ArmorClassUnarmoredBarbarian",
|
||||||
|
formula: "10 + @abilities.dex.mod + @abilities.con.mod"
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
label: "SW5E.ArmorClassCustom"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -545,7 +615,9 @@ SW5E.movementTypes = {
|
||||||
*/
|
*/
|
||||||
SW5E.movementUnits = {
|
SW5E.movementUnits = {
|
||||||
ft: "SW5E.DistFt",
|
ft: "SW5E.DistFt",
|
||||||
mi: "SW5E.DistMi"
|
mi: "SW5E.DistMi",
|
||||||
|
m: "SW5E.DistM",
|
||||||
|
km: "SW5E.DistKm"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -571,9 +643,18 @@ for (let [k, v] of Object.entries(SW5E.movementUnits)) {
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
SW5E.encumbrance = {
|
SW5E.encumbrance = {
|
||||||
currencyPerWeight: 50,
|
currencyPerWeight: {
|
||||||
strMultiplier: 15,
|
imperial: 50,
|
||||||
vehicleWeightMultiplier: 2000 // 2000 lbs in a ton
|
metric: 110
|
||||||
|
},
|
||||||
|
strMultiplier: {
|
||||||
|
imperial: 15,
|
||||||
|
metric: 6.8
|
||||||
|
},
|
||||||
|
vehicleWeightMultiplier: {
|
||||||
|
imperial: 2000, // 2000 lbs in an imperial ton
|
||||||
|
metric: 1000 // 1000 kg in a metric ton
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
@ -751,7 +751,7 @@ export default class Item5e extends Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that a consumed resource is available
|
// Verify that a consumed resource is available
|
||||||
if (!resource) {
|
if (resource === undefined) {
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoSource", {name: this.name, type: typeLabel}));
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoSource", {name: this.name, type: typeLabel}));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -796,7 +796,7 @@ export default class Item5e extends Item {
|
||||||
// Render the chat card template
|
// Render the chat card template
|
||||||
const token = this.actor.token;
|
const token = this.actor.token;
|
||||||
const templateData = {
|
const templateData = {
|
||||||
actor: this.actor,
|
actor: this.actor.data,
|
||||||
tokenId: token?.uuid || null,
|
tokenId: token?.uuid || null,
|
||||||
item: this.data,
|
item: this.data,
|
||||||
data: this.getChatData(),
|
data: this.getChatData(),
|
||||||
|
@ -1025,24 +1025,22 @@ export default class Item5e extends Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compose roll options
|
// Compose roll options
|
||||||
const rollConfig = mergeObject(
|
let rollConfig = {
|
||||||
{
|
parts: parts,
|
||||||
parts: parts,
|
actor: this.actor,
|
||||||
actor: this.actor,
|
data: rollData,
|
||||||
data: rollData,
|
title: title,
|
||||||
title: title,
|
flavor: title,
|
||||||
flavor: title,
|
dialogOptions: {
|
||||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
width: 400,
|
||||||
dialogOptions: {
|
top: options.event ? options.event.clientY - 80 : null,
|
||||||
width: 400,
|
left: window.innerWidth - 710
|
||||||
top: options.event ? options.event.clientY - 80 : null,
|
|
||||||
left: window.innerWidth - 710
|
|
||||||
},
|
|
||||||
messageData: {"flags.sw5e.roll": {type: "attack", itemId: this.id}}
|
|
||||||
},
|
},
|
||||||
options
|
messageData: {
|
||||||
);
|
"flags.sw5e.roll": {type: "attack", itemId: this.id},
|
||||||
rollConfig.event = options.event;
|
"speaker": ChatMessage.getSpeaker({actor: this.actor})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Expanded critical hit thresholds
|
// Expanded critical hit thresholds
|
||||||
if (this.data.type === "weapon" && flags.weaponCriticalThreshold) {
|
if (this.data.type === "weapon" && flags.weaponCriticalThreshold) {
|
||||||
|
@ -1059,6 +1057,9 @@ export default class Item5e extends Item {
|
||||||
// Apply Halfling Lucky
|
// Apply Halfling Lucky
|
||||||
if (flags.halflingLucky) rollConfig.halflingLucky = true;
|
if (flags.halflingLucky) rollConfig.halflingLucky = true;
|
||||||
|
|
||||||
|
// Compose calculated roll options with passed-in roll options
|
||||||
|
rollConfig = mergeObject(rollConfig, options);
|
||||||
|
|
||||||
// Invoke the d20 roll helper
|
// Invoke the d20 roll helper
|
||||||
const roll = await d20Roll(rollConfig);
|
const roll = await d20Roll(rollConfig);
|
||||||
if (!roll) return null;
|
if (!roll) return null;
|
||||||
|
@ -1121,8 +1122,10 @@ export default class Item5e extends Item {
|
||||||
// Scale damage from up-casting powers
|
// Scale damage from up-casting powers
|
||||||
if (this.data.type === "power") {
|
if (this.data.type === "power") {
|
||||||
if (itemData.scaling.mode === "atwill") {
|
if (itemData.scaling.mode === "atwill") {
|
||||||
const level =
|
let level;
|
||||||
this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
|
if (this.actor.type === "character") level = actorData.details.level;
|
||||||
|
else if (itemData.preparation.mode === "innate") level = Math.ceil(actorData.details.cr);
|
||||||
|
else level = actorData.details.powerLevel;
|
||||||
this._scaleAtWillDamage(parts, itemData.scaling.formula, level, rollData);
|
this._scaleAtWillDamage(parts, itemData.scaling.formula, level, rollData);
|
||||||
} else if (powerLevel && itemData.scaling.mode === "level" && itemData.scaling.formula) {
|
} else if (powerLevel && itemData.scaling.mode === "level" && itemData.scaling.formula) {
|
||||||
const scaling = itemData.scaling.formula;
|
const scaling = itemData.scaling.formula;
|
||||||
|
@ -1651,7 +1654,7 @@ export default class Item5e extends Item {
|
||||||
*/
|
*/
|
||||||
static async createScrollFromPower(power) {
|
static async createScrollFromPower(power) {
|
||||||
// Get power data
|
// Get power data
|
||||||
const itemData = power instanceof Item5e ? power.data : power;
|
const itemData = power instanceof Item5e ? power.toObject() : power;
|
||||||
const {actionType, description, source, activation, duration, target, range, damage, save, level} =
|
const {actionType, description, source, activation, duration, target, range, damage, save, level} =
|
||||||
itemData.data;
|
itemData.data;
|
||||||
|
|
||||||
|
@ -1672,7 +1675,7 @@ export default class Item5e extends Item {
|
||||||
const desc = `${scrollIntro}<hr/><h3>${itemData.name} (Level ${level})</h3><hr/>${description.value}<hr/><h3>Scroll Details</h3><hr/>${scrollDetails}`;
|
const desc = `${scrollIntro}<hr/><h3>${itemData.name} (Level ${level})</h3><hr/>${description.value}<hr/><h3>Scroll Details</h3><hr/>${scrollDetails}`;
|
||||||
|
|
||||||
// Create the power scroll data
|
// Create the power scroll data
|
||||||
const powerScrollData = mergeObject(scrollData, {
|
const powerScrollData = foundry.utils.mergeObject(scrollData, {
|
||||||
name: `${game.i18n.localize("SW5E.PowerScroll")}: ${itemData.name}`,
|
name: `${game.i18n.localize("SW5E.PowerScroll")}: ${itemData.name}`,
|
||||||
img: itemData.img,
|
img: itemData.img,
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import TraitSelector from "../apps/trait-selector.js";
|
import TraitSelector from "../apps/trait-selector.js";
|
||||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../effects.js";
|
import ActiveEffect5e from "../active-effect.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override and extend the core ItemSheet implementation to handle specific item types
|
* Override and extend the core ItemSheet implementation to handle specific item types
|
||||||
|
@ -71,7 +71,7 @@ export default class ItemSheet5e extends ItemSheet {
|
||||||
data.isMountable = this._isItemMountable(itemData);
|
data.isMountable = this._isItemMountable(itemData);
|
||||||
|
|
||||||
// Prepare Active Effects
|
// Prepare Active Effects
|
||||||
data.effects = prepareActiveEffectCategories(this.item.effects);
|
data.effects = ActiveEffect5e.prepareActiveEffectCategories(this.item.effects);
|
||||||
|
|
||||||
// Re-define the template data references (backwards compatible)
|
// Re-define the template data references (backwards compatible)
|
||||||
data.item = itemData;
|
data.item = itemData;
|
||||||
|
@ -347,12 +347,15 @@ export default class ItemSheet5e extends ItemSheet {
|
||||||
options.choices = CONFIG.SW5E.abilities;
|
options.choices = CONFIG.SW5E.abilities;
|
||||||
options.valueKey = null;
|
options.valueKey = null;
|
||||||
break;
|
break;
|
||||||
|
case "skills.choices":
|
||||||
|
options.choices = CONFIG.SW5E.skills;
|
||||||
|
options.valueKey = null;
|
||||||
|
break;
|
||||||
case "skills":
|
case "skills":
|
||||||
const skills = this.item.data.data.skills;
|
const skills = this.item.data.data.skills;
|
||||||
const choiceSet =
|
const choiceSet = skills.choices?.length ? skills.choices : Object.keys(CONFIG.SW5E.skills);
|
||||||
skills.choices && skills.choices.length ? skills.choices : Object.keys(CONFIG.SW5E.skills);
|
|
||||||
options.choices = Object.fromEntries(
|
options.choices = Object.fromEntries(
|
||||||
Object.entries(CONFIG.SW5E.skills).filter((skill) => choiceSet.includes(skill[0]))
|
Object.entries(CONFIG.SW5E.skills).filter(([skill]) => choiceSet.includes(skill))
|
||||||
);
|
);
|
||||||
options.maximum = skills.number;
|
options.maximum = skills.number;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const migrateWorld = async function () {
|
||||||
for await (let a of game.actors.contents) {
|
for await (let a of game.actors.contents) {
|
||||||
try {
|
try {
|
||||||
console.log(`Checking Actor entity ${a.name} for migration needs`);
|
console.log(`Checking Actor entity ${a.name} for migration needs`);
|
||||||
const updateData = await migrateActorData(a.data);
|
const updateData = await migrateActorData(a.toObject());
|
||||||
if (!foundry.utils.isObjectEmpty(updateData)) {
|
if (!foundry.utils.isObjectEmpty(updateData)) {
|
||||||
console.log(`Migrating Actor entity ${a.name}`);
|
console.log(`Migrating Actor entity ${a.name}`);
|
||||||
await a.update(updateData, {enforceTypes: false});
|
await a.update(updateData, {enforceTypes: false});
|
||||||
|
@ -91,7 +91,7 @@ export const migrateCompendium = async function (pack) {
|
||||||
try {
|
try {
|
||||||
switch (entity) {
|
switch (entity) {
|
||||||
case "Actor":
|
case "Actor":
|
||||||
updateData = await migrateActorData(doc.data);
|
updateData = await migrateActorData(doc.toObject());
|
||||||
break;
|
break;
|
||||||
case "Item":
|
case "Item":
|
||||||
updateData = migrateItemData(doc.toObject());
|
updateData = migrateItemData(doc.toObject());
|
||||||
|
@ -117,6 +117,49 @@ export const migrateCompendium = async function (pack) {
|
||||||
console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
|
console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply 'smart' AC migration to a given Actor compendium. This will perform the normal AC migration but additionally
|
||||||
|
* check to see if the actor has armor already equipped, and opt to use that instead.
|
||||||
|
* @param pack
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
export const migrateArmorClass = async function (pack) {
|
||||||
|
if (typeof pack === "string") pack = game.packs.get(pack);
|
||||||
|
if (pack.metadata.entity !== "Actor") return;
|
||||||
|
const wasLocked = pack.locked;
|
||||||
|
await pack.configure({locked: false});
|
||||||
|
const actors = await pack.getDocuments();
|
||||||
|
const updates = [];
|
||||||
|
const armor = new Set(Object.keys(CONFIG.SW5E.armorTypes));
|
||||||
|
|
||||||
|
for (const actor of actors) {
|
||||||
|
try {
|
||||||
|
console.log(`Migrating ${actor.name}...`);
|
||||||
|
const src = actor.toObject();
|
||||||
|
const update = {_id: actor.id};
|
||||||
|
|
||||||
|
// Perform the normal migration.
|
||||||
|
_migrateActorAC(src, update);
|
||||||
|
updates.push(update);
|
||||||
|
|
||||||
|
// CASE 1: Armor is equipped
|
||||||
|
const hasArmorEquipped = actor.itemTypes.equipment.some((e) => {
|
||||||
|
return armor.has(e.data.data.armor?.type) && e.data.data.equipped;
|
||||||
|
});
|
||||||
|
if (hasArmorEquipped) update["data.attributes.ac.calc"] = "default";
|
||||||
|
// CASE 2: NPC Natural Armor
|
||||||
|
else if (src.type === "npc") update["data.attributes.ac.calc"] = "natural";
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Failed to migrate armor class for Actor ${actor.name}`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Actor.implementation.updateDocuments(updates, {pack: pack.collection});
|
||||||
|
await pack.getDocuments(); // Force a re-prepare of all actors.
|
||||||
|
await pack.configure({locked: wasLocked});
|
||||||
|
console.log(`Migrated the AC of all Actors from Compendium ${pack.collection}`);
|
||||||
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Entity Type Migration Helpers */
|
/* Entity Type Migration Helpers */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -135,6 +178,7 @@ export const migrateActorData = async function (actor) {
|
||||||
_migrateActorMovement(actor, updateData);
|
_migrateActorMovement(actor, updateData);
|
||||||
_migrateActorSenses(actor, updateData);
|
_migrateActorSenses(actor, updateData);
|
||||||
_migrateActorType(actor, updateData);
|
_migrateActorType(actor, updateData);
|
||||||
|
_migrateActorAC(actor, updateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate Owned Items
|
// Migrate Owned Items
|
||||||
|
@ -215,6 +259,7 @@ export const migrateItemData = function (item) {
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
_migrateItemClassPowerCasting(item, updateData);
|
_migrateItemClassPowerCasting(item, updateData);
|
||||||
_migrateItemAttunement(item, updateData);
|
_migrateItemAttunement(item, updateData);
|
||||||
|
_migrateItemRarity(item, updateData);
|
||||||
return updateData;
|
return updateData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,6 +274,7 @@ export const migrateActorItemData = async function (item, actor) {
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
_migrateItemClassPowerCasting(item, updateData);
|
_migrateItemClassPowerCasting(item, updateData);
|
||||||
_migrateItemAttunement(item, updateData);
|
_migrateItemAttunement(item, updateData);
|
||||||
|
_migrateItemRarity(item, updateData);
|
||||||
await _migrateItemPower(item, actor, updateData);
|
await _migrateItemPower(item, actor, updateData);
|
||||||
return updateData;
|
return updateData;
|
||||||
};
|
};
|
||||||
|
@ -597,6 +643,20 @@ function _migrateItemClassPowerCasting(item, updateData) {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate the actor attributes.ac.value to the new ac.flat override field.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _migrateActorAC(actorData, updateData) {
|
||||||
|
const ac = actorData.data?.attributes?.ac;
|
||||||
|
if (!Number.isNumeric(ac?.value)) return;
|
||||||
|
updateData["data.attributes.ac.flat"] = ac.value;
|
||||||
|
updateData["data.attributes.ac.-=value"] = null;
|
||||||
|
return updateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an Power Item's data based on compendium
|
* Update an Power Item's data based on compendium
|
||||||
* @param {Object} item The data object for an item
|
* @param {Object} item The data object for an item
|
||||||
|
@ -664,6 +724,26 @@ function _migrateItemAttunement(item, updateData) {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to migrate item rarity from freeform string to enum value.
|
||||||
|
*
|
||||||
|
* @param {object} item Item data to migrate
|
||||||
|
* @param {object} updateData Existing update to expand upon
|
||||||
|
* @return {object} The updateData to apply
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _migrateItemRarity(item, updateData) {
|
||||||
|
if (item.data?.rarity === undefined) return updateData;
|
||||||
|
const rarity = Object.keys(CONFIG.SW5E.itemRarity).find(
|
||||||
|
(key) =>
|
||||||
|
CONFIG.SW5E.itemRarity[key].toLowerCase() === item.data.rarity.toLowerCase() || key === item.data.rarity
|
||||||
|
);
|
||||||
|
updateData["data.rarity"] = rarity ?? "";
|
||||||
|
return updateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A general tool to purge flags from all entities in a Compendium pack.
|
* A general tool to purge flags from all entities in a Compendium pack.
|
||||||
* @param {Compendium} pack The compendium pack to clean
|
* @param {Compendium} pack The compendium pack to clean
|
||||||
|
|
|
@ -141,4 +141,16 @@ export const registerSystemSettings = function () {
|
||||||
dark: "SETTINGS.SWColorDark"
|
dark: "SETTINGS.SWColorDark"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option to replace imperial weight units with metric weight units.
|
||||||
|
*/
|
||||||
|
game.settings.register("sw5e", "metricWeightUnits", {
|
||||||
|
name: "SETTINGS.5eMetricN",
|
||||||
|
hint: "SETTINGS.5eMetricL",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
"systems/sw5e/templates/actors/oldActor/parts/actor-features.html",
|
"systems/sw5e/templates/actors/oldActor/parts/actor-features.html",
|
||||||
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
|
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
|
||||||
"systems/sw5e/templates/actors/oldActor/parts/actor-notes.html",
|
"systems/sw5e/templates/actors/oldActor/parts/actor-notes.html",
|
||||||
|
"systems/sw5e/templates/actors/oldActor/parts/actor-warnings.html",
|
||||||
|
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.html",
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-core.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-core.html",
|
||||||
|
@ -25,6 +26,7 @@ export const preloadHandlebarsTemplates = async function () {
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-tech-powerbook.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-tech-powerbook.html",
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",
|
||||||
|
"systems/sw5e/templates/actors/newActor/parts/swalt-warnings.html",
|
||||||
|
|
||||||
// Item Sheet Partials
|
// Item Sheet Partials
|
||||||
"systems/sw5e/templates/items/parts/item-action.html",
|
"systems/sw5e/templates/items/parts/item-action.html",
|
||||||
|
|
25
sw5e.js
25
sw5e.js
|
@ -41,6 +41,7 @@ import * as chat from "./module/chat.js";
|
||||||
import * as dice from "./module/dice.js";
|
import * as dice from "./module/dice.js";
|
||||||
import * as macros from "./module/macros.js";
|
import * as macros from "./module/macros.js";
|
||||||
import * as migrations from "./module/migration.js";
|
import * as migrations from "./module/migration.js";
|
||||||
|
import ActiveEffect5e from "./module/active-effect.js";
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Foundry VTT Initialization */
|
/* Foundry VTT Initialization */
|
||||||
|
@ -83,6 +84,7 @@ Hooks.once("init", function () {
|
||||||
|
|
||||||
// Record Configuration Values
|
// Record Configuration Values
|
||||||
CONFIG.SW5E = SW5E;
|
CONFIG.SW5E = SW5E;
|
||||||
|
CONFIG.ActiveEffect.documentClass = ActiveEffect5e;
|
||||||
CONFIG.Actor.documentClass = Actor5e;
|
CONFIG.Actor.documentClass = Actor5e;
|
||||||
CONFIG.Item.documentClass = Item5e;
|
CONFIG.Item.documentClass = Item5e;
|
||||||
CONFIG.Token.documentClass = TokenDocument5e;
|
CONFIG.Token.documentClass = TokenDocument5e;
|
||||||
|
@ -193,6 +195,7 @@ Hooks.once("setup", function () {
|
||||||
"abilityConsumptionTypes",
|
"abilityConsumptionTypes",
|
||||||
"actorSizes",
|
"actorSizes",
|
||||||
"alignments",
|
"alignments",
|
||||||
|
"armorClasses",
|
||||||
"armorProficiencies",
|
"armorProficiencies",
|
||||||
"armorPropertiesTypes",
|
"armorPropertiesTypes",
|
||||||
"conditionTypes",
|
"conditionTypes",
|
||||||
|
@ -205,6 +208,7 @@ Hooks.once("setup", function () {
|
||||||
"equipmentTypes",
|
"equipmentTypes",
|
||||||
"healingTypes",
|
"healingTypes",
|
||||||
"itemActionTypes",
|
"itemActionTypes",
|
||||||
|
"itemRarity",
|
||||||
"languages",
|
"languages",
|
||||||
"limitedUsePeriods",
|
"limitedUsePeriods",
|
||||||
"movementTypes",
|
"movementTypes",
|
||||||
|
@ -227,6 +231,8 @@ Hooks.once("setup", function () {
|
||||||
"targetTypes",
|
"targetTypes",
|
||||||
"timePeriods",
|
"timePeriods",
|
||||||
"toolProficiencies",
|
"toolProficiencies",
|
||||||
|
"toolTypes",
|
||||||
|
"vehicleTypes",
|
||||||
"weaponProficiencies",
|
"weaponProficiencies",
|
||||||
"weaponProperties",
|
"weaponProperties",
|
||||||
"weaponSizes",
|
"weaponSizes",
|
||||||
|
@ -237,24 +243,30 @@ Hooks.once("setup", function () {
|
||||||
const noSort = [
|
const noSort = [
|
||||||
"abilities",
|
"abilities",
|
||||||
"alignments",
|
"alignments",
|
||||||
|
"armorClasses",
|
||||||
|
"armorProficiencies",
|
||||||
"currencies",
|
"currencies",
|
||||||
"distanceUnits",
|
"distanceUnits",
|
||||||
"movementUnits",
|
"movementUnits",
|
||||||
"itemActionTypes",
|
"itemActionTypes",
|
||||||
|
"itemRarity",
|
||||||
"proficiencyLevels",
|
"proficiencyLevels",
|
||||||
"limitedUsePeriods",
|
"limitedUsePeriods",
|
||||||
"powerComponents",
|
"powerComponents",
|
||||||
"powerLevels",
|
"powerLevels",
|
||||||
"powerPreparationModes",
|
"powerPreparationModes",
|
||||||
|
"weaponProficiencies",
|
||||||
"weaponTypes"
|
"weaponTypes"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Localize and sort CONFIG objects
|
// Localize and sort CONFIG objects
|
||||||
for (let o of toLocalize) {
|
for (let o of toLocalize) {
|
||||||
const localized = Object.entries(CONFIG.SW5E[o]).map((e) => {
|
const localized = Object.entries(CONFIG.SW5E[o]).map(([k, v]) => {
|
||||||
return [e[0], game.i18n.localize(e[1])];
|
if (v.label) v.label = game.i18n.localize(v.label);
|
||||||
|
if (typeof v === "string") return [k, game.i18n.localize(v)];
|
||||||
|
return [k, v];
|
||||||
});
|
});
|
||||||
if (!noSort.includes(o)) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
if (!noSort.includes(o)) localized.sort((a, b) => (a[1].label ?? a[1]).localeCompare(b[1].label ?? b[1]));
|
||||||
CONFIG.SW5E[o] = localized.reduce((obj, e) => {
|
CONFIG.SW5E[o] = localized.reduce((obj, e) => {
|
||||||
obj[e[0]] = e[1];
|
obj[e[0]] = e[1];
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -278,10 +290,13 @@ Hooks.once("ready", function () {
|
||||||
// Determine whether a system migration is required and feasible
|
// Determine whether a system migration is required and feasible
|
||||||
if (!game.user.isGM) return;
|
if (!game.user.isGM) return;
|
||||||
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
|
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
|
||||||
const NEEDS_MIGRATION_VERSION = "1.3.5.R1-A6";
|
const NEEDS_MIGRATION_VERSION = "1.4.1.R1-A8";
|
||||||
// Check for R1 SW5E versions
|
// Check for R1 SW5E versions
|
||||||
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A6";
|
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A8";
|
||||||
const COMPATIBLE_MIGRATION_VERSION = 0.8;
|
const COMPATIBLE_MIGRATION_VERSION = 0.8;
|
||||||
|
const totalDocuments = game.actors.size + game.scenes.size + game.items.size;
|
||||||
|
if (!currentVersion && totalDocuments === 0)
|
||||||
|
return game.settings.set("sw5e", "systemMigrationVersion", game.system.data.version);
|
||||||
const needsMigration =
|
const needsMigration =
|
||||||
currentVersion &&
|
currentVersion &&
|
||||||
(isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) ||
|
(isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) ||
|
||||||
|
|
315
system.json
315
system.json
|
@ -1,160 +1,159 @@
|
||||||
{
|
{
|
||||||
"name": "sw5e",
|
"name": "sw5e",
|
||||||
"title": "SW 5th Edition",
|
"title": "SW 5th Edition",
|
||||||
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.",
|
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.",
|
||||||
"version": "1.3.5.R1-A7",
|
"version": "1.4.1.R1-A8",
|
||||||
"author": "Dev Team",
|
"author": "Dev Team",
|
||||||
"scripts": [],
|
"scripts": [],
|
||||||
"esmodules": ["sw5e.js"],
|
"esmodules": ["sw5e.js"],
|
||||||
"styles": ["sw5e.css", "sw5e-global.css", "sw5e-light.css", "sw5e-dark.css"],
|
"styles": ["sw5e.css", "sw5e-global.css", "sw5e-light.css", "sw5e-dark.css"],
|
||||||
"packs": [
|
"packs": [
|
||||||
{
|
{
|
||||||
"name": "adventuringgear",
|
"name": "adventuringgear",
|
||||||
"label": "Adventuring Gear",
|
"label": "Adventuring Gear",
|
||||||
"path": "./packs/packs/adventuringgear.db",
|
"path": "./packs/packs/adventuringgear.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "archetypes",
|
"name": "archetypes",
|
||||||
"label": "Archetypes",
|
"label": "Archetypes",
|
||||||
"path": "./packs/packs/archetypes.db",
|
"path": "./packs/packs/archetypes.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "armor",
|
"name": "armor",
|
||||||
"label": "Armor",
|
"label": "Armor",
|
||||||
"path": "./packs/packs/armor.db",
|
"path": "./packs/packs/armor.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "backgrounds",
|
"name": "backgrounds",
|
||||||
"label": "Backgrounds",
|
"label": "Backgrounds",
|
||||||
"path": "./packs/packs/backgrounds.db",
|
"path": "./packs/packs/backgrounds.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "classes",
|
"name": "classes",
|
||||||
"label": "Classes",
|
"label": "Classes",
|
||||||
"path": "./packs/packs/classes.db",
|
"path": "./packs/packs/classes.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "classfeatures",
|
"name": "classfeatures",
|
||||||
"label": "Class Features",
|
"label": "Class Features",
|
||||||
"path": "./packs/packs/classfeatures.db",
|
"path": "./packs/packs/classfeatures.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "conditions",
|
"name": "conditions",
|
||||||
"label": "Conditions",
|
"label": "Conditions",
|
||||||
"path": "./packs/packs/conditions.db",
|
"path": "./packs/packs/conditions.db",
|
||||||
"entity": "JournalEntry"
|
"entity": "JournalEntry"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "enhanceditems",
|
"name": "enhanceditems",
|
||||||
"label": "Enhanced Items",
|
"label": "Enhanced Items",
|
||||||
"path": "./packs/packs/enhanceditems.db",
|
"path": "./packs/packs/enhanceditems.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "feats",
|
"name": "feats",
|
||||||
"label": "Feats",
|
"label": "Feats",
|
||||||
"path": "./packs/packs/feats.db",
|
"path": "./packs/packs/feats.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fightingstyles",
|
"name": "fightingstyles",
|
||||||
"label": "Fighting Styles",
|
"label": "Fighting Styles",
|
||||||
"path": "./packs/packs/fightingstyles.db",
|
"path": "./packs/packs/fightingstyles.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fightingmasteries",
|
"name": "fightingmasteries",
|
||||||
"label": "Fighting Masteries",
|
"label": "Fighting Masteries",
|
||||||
"path": "./packs/packs/fightingmasteries.db",
|
"path": "./packs/packs/fightingmasteries.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "forcepowers",
|
"name": "forcepowers",
|
||||||
"label": "Force Powers",
|
"label": "Force Powers",
|
||||||
"path": "./packs/packs/forcepowers.db",
|
"path": "./packs/packs/forcepowers.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "gamingset",
|
"name": "gamingset",
|
||||||
"label": "Gaming Sets",
|
"label": "Gaming Sets",
|
||||||
"path": "./packs/packs/gamingset.db",
|
"path": "./packs/packs/gamingset.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "lightsaberform",
|
"name": "lightsaberform",
|
||||||
"label": "Lightsaber Forms",
|
"label": "Lightsaber Forms",
|
||||||
"path": "./packs/packs/lightsaberforms.db",
|
"path": "./packs/packs/lightsaberforms.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "monsters",
|
"name": "monsters",
|
||||||
"label": "Monsters",
|
"label": "Monsters",
|
||||||
"path": "./packs/packs/monsters.db",
|
"path": "./packs/packs/monsters.db",
|
||||||
"entity": "Actor"
|
"entity": "Actor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "species",
|
"name": "species",
|
||||||
"label": "Species",
|
"label": "Species",
|
||||||
"path": "./packs/packs/species.db",
|
"path": "./packs/packs/species.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "speciestraits",
|
"name": "speciestraits",
|
||||||
"label": "Species Traits",
|
"label": "Species Traits",
|
||||||
"path": "./packs/packs/speciestraits.db",
|
"path": "./packs/packs/speciestraits.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tables",
|
"name": "tables",
|
||||||
"label": "Tables",
|
"label": "Tables",
|
||||||
"path": "./packs/packs/tables.db",
|
"path": "./packs/packs/tables.db",
|
||||||
"entity": "RollTable"
|
"entity": "RollTable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "techpowers",
|
"name": "techpowers",
|
||||||
"label": "Tech Powers",
|
"label": "Tech Powers",
|
||||||
"path": "./packs/packs/techpowers.db",
|
"path": "./packs/packs/techpowers.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "weapons",
|
"name": "weapons",
|
||||||
"label": "Weapons",
|
"label": "Weapons",
|
||||||
"path": "./packs/packs/weapons.db",
|
"path": "./packs/packs/weapons.db",
|
||||||
"entity": "Item"
|
"entity": "Item"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"name": "English",
|
"name": "English",
|
||||||
"path": "lang/en.json"
|
"path": "lang/en.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"lang": "fr",
|
"lang": "fr",
|
||||||
"name": "French",
|
"name": "French",
|
||||||
"path": "lang/fr.json"
|
"path": "lang/fr.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"lang": "it",
|
"lang": "it",
|
||||||
"name": "Italian",
|
"name": "Italian",
|
||||||
"path": "lang/it.json"
|
"path": "lang/it.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"gridDistance": 5,
|
"gridDistance": 5,
|
||||||
"gridUnits": "ft",
|
"gridUnits": "ft",
|
||||||
"primaryTokenAttribute": "attributes.hp",
|
"primaryTokenAttribute": "attributes.hp",
|
||||||
"secondaryTokenAttribute": null,
|
"secondaryTokenAttribute": null,
|
||||||
"minimumCoreVersion": "0.8.2",
|
"minimumCoreVersion": "0.8.2",
|
||||||
"compatibleCoreVersion": "0.8.8",
|
"compatibleCoreVersion": "0.8.8",
|
||||||
"url": "https://github.com/unrealkakeman89/sw5e",
|
"url": "https://github.com/unrealkakeman89/sw5e",
|
||||||
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
|
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
|
||||||
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"
|
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
2042
template.json
2042
template.json
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,7 @@
|
||||||
{{!-- Sheet Header --}}
|
{{!-- Sheet Header --}}
|
||||||
<header class="panel">
|
<header class="panel">
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img" />
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img" />
|
||||||
|
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-warnings.html"}}
|
||||||
<h1 class="character-name">
|
<h1 class="character-name">
|
||||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}" />
|
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}" />
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -36,11 +37,16 @@
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
{{!-- ARMOR CLASS --}}
|
{{!-- ARMOR CLASS --}}
|
||||||
<section>
|
<section>
|
||||||
<h1>{{ localize "SW5E.ArmorClass" }}</h1>
|
|
||||||
<div class="attribute-value">
|
<li class="attribute armor">
|
||||||
<input class="ac-display" name="data.attributes.ac.value" type="text"
|
<h4 class="attribute-name box-title">
|
||||||
value="{{data.attributes.ac.value}}" data-dtype="Number" placeholder="10" />
|
{{ localize "SW5E.ArmorClass" }}
|
||||||
</div>
|
<a class="config-button" data-action="armor" title="{{localize 'SW5E.ArmorConfig'}}"><i class="fas fa-cog"></i></a>
|
||||||
|
</h4>
|
||||||
|
<div class="attribute-value attributable" data-property="attributes.ac">
|
||||||
|
<span>{{data.attributes.ac.value}}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{!-- HIT POINTS --}}
|
{{!-- HIT POINTS --}}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
{{!-- NPC Sheet Header --}}
|
{{!-- NPC Sheet Header --}}
|
||||||
<header class="panel">
|
<header class="panel">
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img" />
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img" />
|
||||||
|
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-warnings.html"}}
|
||||||
|
|
||||||
<h1 class="character-name">
|
<h1 class="character-name">
|
||||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}" />
|
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}" />
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -15,8 +17,12 @@
|
||||||
<div class="experience">
|
<div class="experience">
|
||||||
<span class="max">{{data.details.xp.value}} XP</span>
|
<span class="max">{{data.details.xp.value}} XP</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="proficiency">
|
||||||
</div>
|
<span>
|
||||||
|
{{ localize "SW5E.Proficiency" }} {{numberFormat data.attributes.prof decimals=0 sign=true}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
<span class="npc-size">{{lookup config.actorSizes data.traits.size}}</span>
|
<span class="npc-size">{{lookup config.actorSizes data.traits.size}}</span>
|
||||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}"
|
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}"
|
||||||
|
@ -31,15 +37,18 @@
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
{{!-- ARMOR CLASS --}}
|
{{!-- ARMOR CLASS --}}
|
||||||
<section>
|
<section>
|
||||||
<h1>{{ localize "SW5E.ArmorClass" }}</h1>
|
<li class="attribute armor">
|
||||||
<div class="attribute-value">
|
<h4 class="attribute-name box-title">
|
||||||
<input class="ac-display" name="data.attributes.ac.value" type="text"
|
{{ localize "SW5E.ArmorClass" }}
|
||||||
value="{{data.attributes.ac.value}}" data-dtype="Number" placeholder="10" />
|
<a class="config-button" data-action="armor" title="{{localize 'SW5E.ArmorConfig'}}"><i class="fas fa-cog"></i></a>
|
||||||
</div>
|
</h4>
|
||||||
<footer class="attribute-footer proficiency">
|
<div class="attribute-value attributable" data-property="attributes.ac">
|
||||||
{{ localize "SW5E.Proficiency" }}
|
<span>{{data.attributes.ac.value}}</span>
|
||||||
{{numberFormat data.attributes.prof decimals=0 sign=true}}
|
</div>
|
||||||
</footer>
|
<footer class="attribute-footer">
|
||||||
|
<span>{{labels.armorType}}</span>
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{!-- HIT POINTS --}}
|
{{!-- HIT POINTS --}}
|
||||||
|
@ -99,7 +108,7 @@
|
||||||
<span class="ability-mod"
|
<span class="ability-mod"
|
||||||
title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
||||||
<input type="hidden" name="data.abilities.{{id}}.proficient"
|
<input type="hidden" name="data.abilities.{{id}}.proficient"
|
||||||
value="{{ability.proficient}}" data-dtype="Number" />
|
value="{{ability.baseProf}}" data-dtype="Number" />
|
||||||
<button class="proficiency-toggle ability-proficiency"
|
<button class="proficiency-toggle ability-proficiency"
|
||||||
title="Proficiency">{{{ability.icon}}}</button>
|
title="Proficiency">{{{ability.icon}}}</button>
|
||||||
<span class="ability-save"
|
<span class="ability-save"
|
||||||
|
@ -116,7 +125,7 @@
|
||||||
{{#each config.skills as |label s|}}
|
{{#each config.skills as |label s|}}
|
||||||
{{#with (lookup ../data.skills s) as |skill|}}
|
{{#with (lookup ../data.skills s) as |skill|}}
|
||||||
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}"
|
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.baseValue}}"
|
||||||
data-dtype="Number" />
|
data-dtype="Number" />
|
||||||
<button class="proficiency-toggle skill-proficiency"
|
<button class="proficiency-toggle skill-proficiency"
|
||||||
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<span class="ability-mod"
|
<span class="ability-mod"
|
||||||
title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
||||||
<input type="hidden" name="data.abilities.{{id}}.proficient"
|
<input type="hidden" name="data.abilities.{{id}}.proficient"
|
||||||
value="{{ability.proficient}}" data-dtype="Number" />
|
value="{{ability.baseProf}}" data-dtype="Number" />
|
||||||
<button class="proficiency-toggle ability-proficiency"
|
<button class="proficiency-toggle ability-proficiency"
|
||||||
title="Proficiency">{{{ability.icon}}}</button>
|
title="Proficiency">{{{ability.icon}}}</button>
|
||||||
<span class="ability-save"
|
<span class="ability-save"
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
{{#each config.skills as |label s|}}
|
{{#each config.skills as |label s|}}
|
||||||
{{#with (lookup ../data.skills s) as |skill|}}
|
{{#with (lookup ../data.skills s) as |skill|}}
|
||||||
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}"
|
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.baseValue}}"
|
||||||
data-dtype="Number" />
|
data-dtype="Number" />
|
||||||
<button class="proficiency-toggle skill-proficiency"
|
<button class="proficiency-toggle skill-proficiency"
|
||||||
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
{{#if ../../isCharacter}}
|
{{#if ../../isCharacter}}
|
||||||
<div class="item-detail item-weight">
|
<div class="item-detail item-weight">
|
||||||
{{#if item.totalWeight}}
|
{{#if item.totalWeight}}
|
||||||
{{ item.totalWeight }} {{localize "SW5E.AbbreviationLbs"}}
|
{{ item.totalWeight }} {{ @root.weightUnit }}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -112,7 +112,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
||||||
<a class="trait-selector" data-options="armorProficiencies" data-target="data.traits.armorProf">
|
<a class="proficiency-selector" data-type="armor" data-target="data.traits.armorProf">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
||||||
<a class="trait-selector" data-options="toolProficiencies" data-target="data.traits.toolProf">
|
<a class="proficiency-selector" data-type="tool" data-target="data.traits.toolProf">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
|
@ -134,7 +134,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||||
<a class="trait-selector" data-options="weaponProficiencies" data-target="data.traits.weaponProf">
|
<a class="proficiency-selector" data-type="weapon" data-target="data.traits.weaponProf">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
|
|
5
templates/actors/newActor/parts/swalt-warnings.html
Normal file
5
templates/actors/newActor/parts/swalt-warnings.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<ol class="warnings">
|
||||||
|
{{#each warnings}}
|
||||||
|
<li class="notification warning">{{localize this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
|
@ -3,7 +3,9 @@
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
||||||
data-edit="img">
|
data-edit="img">
|
||||||
<section class="header-details flexrow">
|
<section class="header-details flexrow">
|
||||||
<h1 class="charnam">
|
{{> "systems/sw5e/templates/actors/parts/actor-warnings.html"}}
|
||||||
|
|
||||||
|
<h1 class="charname">
|
||||||
<input name="name" type="text" value="{{actor.name}}"
|
<input name="name" type="text" value="{{actor.name}}"
|
||||||
placeholder="{{localize 'SW5E.Name'}}">
|
placeholder="{{localize 'SW5E.Name'}}">
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -12,8 +14,10 @@
|
||||||
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span>{{localize 'SW5E.Vehicle'}}</span>
|
<select name="data.vehicleType">
|
||||||
</li>
|
{{selectOptions config.vehicleTypes selected=data.vehicleType}}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.traits.dimensions"
|
<input type="text" name="data.traits.dimensions"
|
||||||
value="{{data.traits.dimensions}}"
|
value="{{data.traits.dimensions}}"
|
||||||
|
@ -47,8 +51,8 @@
|
||||||
<li class="attribute">
|
<li class="attribute">
|
||||||
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
||||||
<div class="attribute-value">
|
<div class="attribute-value">
|
||||||
<input name="data.attributes.ac.value" type="text" placeholder="—"
|
<input name="data.attributes.ac.flat" type="text" placeholder="—"
|
||||||
value="{{data.attributes.ac.value}}" data-dtype="Number">
|
value="{{data.attributes.ac.flat}}" data-dtype="Number">
|
||||||
</div>
|
</div>
|
||||||
<footer class="attribute-footer">
|
<footer class="attribute-footer">
|
||||||
<input type="text" name="data.attributes.ac.motionless"
|
<input type="text" name="data.attributes.ac.motionless"
|
||||||
|
@ -155,7 +159,9 @@
|
||||||
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
||||||
{{> 'systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html' sections=cargo}}
|
{{> 'systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html' sections=cargo}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
|
||||||
|
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
|
||||||
|
</div>
|
||||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||||
{{editor content=data.details.biography.value target='data.details.biography.value'
|
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||||
button=true owner=owner editable=editable rollData=rollData}}
|
button=true owner=owner editable=editable rollData=rollData}}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||||
|
|
||||||
<section class="header-details flexrow">
|
<section class="header-details flexrow">
|
||||||
|
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-warnings.html"}}
|
||||||
|
|
||||||
<h1 class="charname">
|
<h1 class="charname">
|
||||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -74,10 +76,13 @@
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="attribute">
|
<li class="attribute armor">
|
||||||
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
<h4 class="attribute-name box-title">
|
||||||
<div class="attribute-value">
|
{{ localize "SW5E.ArmorClass" }}
|
||||||
<input name="data.attributes.ac.value" type="number" value="{{data.attributes.ac.value}}" placeholder="10"/>
|
<a class="config-button" data-action="armor" title="{{localize 'SW5E.ArmorConfig'}}"><i class="fas fa-cog"></i></a>
|
||||||
|
</h4>
|
||||||
|
<div class="attribute-value attributable" data-property="attributes.ac">
|
||||||
|
<span>{{data.attributes.ac.value}}</span>
|
||||||
</div>
|
</div>
|
||||||
<footer class="attribute-footer">
|
<footer class="attribute-footer">
|
||||||
<span class="power-dc">{{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}</span>
|
<span class="power-dc">{{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}</span>
|
||||||
|
@ -136,7 +141,7 @@
|
||||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="number" value="{{ability.value}}" placeholder="10"/>
|
<input class="ability-score" name="data.abilities.{{id}}.value" type="number" value="{{ability.value}}" placeholder="10"/>
|
||||||
<div class="ability-modifiers flexrow">
|
<div class="ability-modifiers flexrow">
|
||||||
<span class="ability-mod" title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
<span class="ability-mod" title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
||||||
<input type="hidden" name="data.abilities.{{id}}.proficient" value="{{ability.proficient}}" data-dtype="Number"/>
|
<input type="hidden" name="data.abilities.{{id}}.proficient" value="{{ability.baseProf}}" data-dtype="Number"/>
|
||||||
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
||||||
<span class="ability-save" title="{{ localize 'SW5E.SavingThrow' }}">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
<span class="ability-save" title="{{ localize 'SW5E.SavingThrow' }}">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,7 +154,7 @@
|
||||||
{{#each config.skills as |label s|}}
|
{{#each config.skills as |label s|}}
|
||||||
{{#with (lookup ../data.skills s) as |skill|}}
|
{{#with (lookup ../data.skills s) as |skill|}}
|
||||||
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}" data-dtype="Number"/>
|
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.baseValue}}" data-dtype="Number"/>
|
||||||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||||
<h4 class="skill-name rollable">{{label}}</h4>
|
<h4 class="skill-name rollable">{{label}}</h4>
|
||||||
<span class="skill-ability">{{skill.ability}}</span>
|
<span class="skill-ability">{{skill.ability}}</span>
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||||
|
|
||||||
<section class="header-details flexrow">
|
<section class="header-details flexrow">
|
||||||
|
{{> "systems/sw5e/templates/actors/oldActor/parts/actor-warnings.html"}}
|
||||||
|
|
||||||
<h1 class="charname">
|
<h1 class="charname">
|
||||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -17,6 +19,11 @@
|
||||||
<div class="experience">
|
<div class="experience">
|
||||||
<span>{{data.details.xp.value}} XP</span>
|
<span>{{data.details.xp.value}} XP</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="proficiency">
|
||||||
|
<span>
|
||||||
|
{{ localize "SW5E.Proficiency" }} {{numberFormat data.attributes.prof decimals=0 sign=true}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{{!-- Character Summary --}}
|
{{!-- Character Summary --}}
|
||||||
|
@ -51,14 +58,16 @@
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="attribute">
|
<li class="attribute armor">
|
||||||
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
<h4 class="attribute-name box-title">
|
||||||
<div class="attribute-value">
|
{{ localize "SW5E.ArmorClass" }}
|
||||||
<input name="data.attributes.ac.value" type="number" value="{{data.attributes.ac.value}}" placeholder="10"/>
|
<a class="config-button" data-action="armor" title="{{localize 'SW5E.ArmorConfig'}}"><i class="fas fa-cog"></i></a>
|
||||||
|
</h4>
|
||||||
|
<div class="attribute-value attributable" data-property="attributes.ac">
|
||||||
|
<span>{{data.attributes.ac.value}}</span>
|
||||||
</div>
|
</div>
|
||||||
<footer class="attribute-footer">
|
<footer class="attribute-footer">
|
||||||
<span>{{ localize "SW5E.Proficiency" }}</span>
|
<span>{{labels.armorType}}</span>
|
||||||
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -99,7 +108,7 @@
|
||||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="number" value="{{ability.value}}" placeholder="10"/>
|
<input class="ability-score" name="data.abilities.{{id}}.value" type="number" value="{{ability.value}}" placeholder="10"/>
|
||||||
<div class="ability-modifiers flexrow">
|
<div class="ability-modifiers flexrow">
|
||||||
<span class="ability-mod" title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
<span class="ability-mod" title="{{ localize 'SW5E.Modifier' }}">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
||||||
<input type="hidden" name="data.abilities.{{id}}.proficient" value="{{ability.proficient}}" data-dtype="Number"/>
|
<input type="hidden" name="data.abilities.{{id}}.proficient" value="{{ability.baseProf}}" data-dtype="Number"/>
|
||||||
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
||||||
<span class="ability-save" title="{{ localize 'SW5E.SavingThrow' }}">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
<span class="ability-save" title="{{ localize 'SW5E.SavingThrow' }}">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,7 +121,7 @@
|
||||||
{{#each config.skills as |label s|}}
|
{{#each config.skills as |label s|}}
|
||||||
{{#with (lookup ../data.skills s) as |skill|}}
|
{{#with (lookup ../data.skills s) as |skill|}}
|
||||||
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}" data-dtype="Number"/>
|
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.baseValue}}" data-dtype="Number"/>
|
||||||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||||
<h4 class="skill-name rollable">{{label}}</h4>
|
<h4 class="skill-name rollable">{{label}}</h4>
|
||||||
<span class="skill-ability">{{skill.ability}}</span>
|
<span class="skill-ability">{{skill.ability}}</span>
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
{{#if section.columns}}
|
{{#if section.columns}}
|
||||||
{{#each section.columns}}
|
{{#each section.columns}}
|
||||||
<div class="item-detail {{css}}">
|
<div class="item-detail {{css}}">
|
||||||
{{#with (lookup item property)}}
|
{{#with (getProperty item property)}}
|
||||||
{{#if ../editable}}
|
{{#if ../editable}}
|
||||||
<input type="text" value="{{this}}" placeholder="—"
|
<input type="text" value="{{this}}" placeholder="—"
|
||||||
data-dtype="{{../editable}}">
|
data-dtype="{{../editable}}">
|
||||||
|
|
|
@ -89,7 +89,7 @@
|
||||||
<div class="item-detail item-weight">
|
<div class="item-detail item-weight">
|
||||||
{{#if item.totalWeight}}
|
{{#if item.totalWeight}}
|
||||||
<div class="item-detail">
|
<div class="item-detail">
|
||||||
{{ item.totalWeight }} {{localize "SW5E.AbbreviationLbs"}}
|
{{ item.totalWeight }} {{ @root.weightUnit }}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
{{#if isCharacter}}
|
{{#if isCharacter}}
|
||||||
<div class="form-group {{data.traits.weaponProf.cssClass}}">
|
<div class="form-group {{data.traits.weaponProf.cssClass}}">
|
||||||
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||||
<a class="trait-selector" data-options="weaponProficiencies" data-target="data.traits.weaponProf">
|
<a class="proficiency-selector" data-type="weapon" data-target="data.traits.weaponProf">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
|
|
||||||
<div class="form-group {{data.traits.armorProf.cssClass}}">
|
<div class="form-group {{data.traits.armorProf.cssClass}}">
|
||||||
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
||||||
<a class="trait-selector" data-options="armorProficiencies" data-target="data.traits.armorProf">
|
<a class="proficiency-selector" data-type="armor" data-target="data.traits.armorProf">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
|
|
||||||
<div class="form-group {{data.traits.toolProf.cssClass}}">
|
<div class="form-group {{data.traits.toolProf.cssClass}}">
|
||||||
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
||||||
<a class="trait-selector" data-options="toolProficiencies" data-target="data.traits.toolProf">
|
<a class="proficiency-selector" data-type="tool" data-target="data.traits.toolProf">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
|
|
5
templates/actors/oldActor/parts/actor-warnings.html
Normal file
5
templates/actors/oldActor/parts/actor-warnings.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<ol class="warnings">
|
||||||
|
{{#each warnings}}
|
||||||
|
<li class="notification warning">{{localize this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
|
@ -3,7 +3,9 @@
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
||||||
data-edit="img">
|
data-edit="img">
|
||||||
<section class="header-details flexrow">
|
<section class="header-details flexrow">
|
||||||
<h1 class="charnam">
|
{{> "systems/sw5e/templates/actors/parts/actor-warnings.html"}}
|
||||||
|
|
||||||
|
<h1 class="charname">
|
||||||
<input name="name" type="text" value="{{actor.name}}"
|
<input name="name" type="text" value="{{actor.name}}"
|
||||||
placeholder="{{localize 'SW5E.Name'}}">
|
placeholder="{{localize 'SW5E.Name'}}">
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -12,8 +14,10 @@
|
||||||
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span>{{localize 'SW5E.Vehicle'}}</span>
|
<select name="data.vehicleType">
|
||||||
</li>
|
{{selectOptions config.vehicleTypes selected=data.vehicleType}}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.traits.dimensions"
|
<input type="text" name="data.traits.dimensions"
|
||||||
value="{{data.traits.dimensions}}"
|
value="{{data.traits.dimensions}}"
|
||||||
|
@ -47,8 +51,8 @@
|
||||||
<li class="attribute">
|
<li class="attribute">
|
||||||
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
||||||
<div class="attribute-value">
|
<div class="attribute-value">
|
||||||
<input name="data.attributes.ac.value" type="text" placeholder="—"
|
<input name="data.attributes.ac.flat" type="text" placeholder="—"
|
||||||
value="{{data.attributes.ac.value}}" data-dtype="Number">
|
value="{{data.attributes.ac.flat}}" data-dtype="Number">
|
||||||
</div>
|
</div>
|
||||||
<footer class="attribute-footer">
|
<footer class="attribute-footer">
|
||||||
<input type="text" name="data.attributes.ac.motionless"
|
<input type="text" name="data.attributes.ac.motionless"
|
||||||
|
@ -155,7 +159,9 @@
|
||||||
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
||||||
{{> 'systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html' sections=cargo}}
|
{{> 'systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html' sections=cargo}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
|
||||||
|
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
|
||||||
|
</div>
|
||||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||||
{{editor content=data.details.biography.value target='data.details.biography.value'
|
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||||
button=true owner=owner editable=editable rollData=rollData}}
|
button=true owner=owner editable=editable rollData=rollData}}
|
||||||
|
|
|
@ -1,38 +1,48 @@
|
||||||
<ol class="items-list effects-list">
|
<ol class="items-list effects-list">
|
||||||
{{#each effects as |section sid|}}
|
{{#each effects as |section sid|}}
|
||||||
<li class="items-header flexrow" data-effect-type="{{section.type}}">
|
{{#unless section.hidden}}
|
||||||
<h3 class="item-name effect-name flexrow">{{localize section.label}}</h3>
|
<li class="items-header flexrow" data-effect-type="{{section.type}}">
|
||||||
<div class="effect-source">{{localize "SW5E.Source"}}</div>
|
<h3 class="item-name effect-name flexrow">{{localize section.label}}</h3>
|
||||||
<div class="effect-source">{{localize "SW5E.Duration"}}</div>
|
<div class="effect-source">{{localize "SW5E.Source"}}</div>
|
||||||
<div class="item-controls effect-controls flexrow">
|
<div class="effect-source">{{localize "SW5E.Duration"}}</div>
|
||||||
<a class="effect-control" data-action="create" title="{{localize 'SW5E.EffectCreate'}}">
|
<div class="item-controls effect-controls flexrow">
|
||||||
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
|
<a class="effect-control" data-action="create" title="{{localize 'SW5E.EffectCreate'}}">
|
||||||
</a>
|
<i class="fas fa-plus"></i> {{localize "SW5E.Add"}}
|
||||||
</div>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<ol class="item-list">
|
{{#if section.info}}
|
||||||
{{#each section.effects as |effect|}}
|
<ol class="info">
|
||||||
<li class="item effect flexrow" data-effect-id="{{effect.id}}">
|
{{#each section.info}}
|
||||||
<div class="item-name effect-name flexrow">
|
<li class="notification info">{{this}}</li>
|
||||||
<img class="item-image" src="{{effect.data.icon}}"/>
|
{{/each}}
|
||||||
<h4>{{effect.data.label}}</h4>
|
</ol>
|
||||||
</div>
|
{{/if}}
|
||||||
<div class="effect-source">{{effect.sourceName}}</div>
|
|
||||||
<div class="effect-duration">{{effect.duration.label}}</div>
|
<ol class="item-list">
|
||||||
<div class="item-controls effect-controls flexrow">
|
{{#each section.effects as |effect|}}
|
||||||
<a class="effect-control" data-action="toggle" title="{{localize 'SW5E.EffectToggle'}}">
|
<li class="item effect flexrow" data-effect-id="{{effect.id}}">
|
||||||
<i class="fas {{#if effect.data.disabled}}fa-check{{else}}fa-times{{/if}}"></i>
|
<div class="item-name effect-name flexrow">
|
||||||
</a>
|
<img class="item-image" src="{{effect.data.icon}}"/>
|
||||||
<a class="effect-control" data-action="edit" title="{{localize 'SW5E.EffectEdit'}}">
|
<h4>{{effect.data.label}}</h4>
|
||||||
<i class="fas fa-edit"></i>
|
</div>
|
||||||
</a>
|
<div class="effect-source">{{effect.sourceName}}</div>
|
||||||
<a class="effect-control" data-action="delete" title="{{localize 'SW5E.EffectDelete'}}">
|
<div class="effect-duration">{{effect.duration.label}}</div>
|
||||||
<i class="fas fa-trash"></i>
|
<div class="item-controls effect-controls flexrow">
|
||||||
</a>
|
<a class="effect-control" data-action="toggle" title="{{localize 'SW5E.EffectToggle'}}">
|
||||||
</div>
|
<i class="fas {{#if effect.data.disabled}}fa-check{{else}}fa-times{{/if}}"></i>
|
||||||
</li>
|
</a>
|
||||||
|
<a class="effect-control" data-action="edit" title="{{localize 'SW5E.EffectEdit'}}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="effect-control" data-action="delete" title="{{localize 'SW5E.EffectDelete'}}">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
||||||
|
{{/unless}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ol>
|
|
||||||
{{/each}}
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
21
templates/apps/actor-armor.html
Normal file
21
templates/apps/actor-armor.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<form>
|
||||||
|
<div class="ac-field form-group">
|
||||||
|
<input type="number" name="ac.flat" value="{{value}}" {{#if valueDisabled}}disabled{{/if}}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group select">
|
||||||
|
<label>{{localize "SW5E.ArmorClassCalculation"}}</label>
|
||||||
|
<select name="ac.calc">
|
||||||
|
{{selectOptions calculations selected=ac.calc labelAttr="label"}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group stacked">
|
||||||
|
<label>{{localize "SW5E.ArmorClassFormula"}}</label>
|
||||||
|
<input type="text" name="ac.formula" value="{{formula}}" {{#if formulaDisabled}}disabled{{/if}}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" name="submit" value="1">
|
||||||
|
<i class="far fa-save"></i> {{ localize "SW5E.TraitSave" }}
|
||||||
|
</button>
|
||||||
|
</form>
|
16
templates/apps/property-attribution.html
Normal file
16
templates/apps/property-attribution.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<div class="property-attribution">
|
||||||
|
<table>
|
||||||
|
{{#each sources as |source|}}
|
||||||
|
<tr>
|
||||||
|
<td class="attribution-value mode-{{source.mode}}{{#if source.negative}} negative{{/if}}">
|
||||||
|
{{source.value}}
|
||||||
|
</td>
|
||||||
|
<td class="attribution-label">{{source.label}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
<tr class="total">
|
||||||
|
<td class="attribution-value">{{total}}</td>
|
||||||
|
<td class="attribution-label">{{localize "SW5E.PropertyTotal"}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
|
@ -1,19 +1,27 @@
|
||||||
<form autocomplete="off" onsubmit="event.preventDefault();">
|
<form autocomplete="off" onsubmit="event.preventDefault();">
|
||||||
<ol class="trait-list">
|
|
||||||
{{#each choices as |choice key|}}
|
{{#*inline "traitList"}}
|
||||||
<li>
|
<ol class="trait-list">
|
||||||
<label class="checkbox">
|
{{#each choices as |choice key|}}
|
||||||
<input type="checkbox" name="{{key}}" data-dtype="Boolean" {{checked choice.chosen}}>
|
<li>
|
||||||
{{choice.label}}
|
<label class="checkbox">
|
||||||
</label>
|
<input type="checkbox" name="{{key}}" data-dtype="Boolean" {{checked choice.chosen}}>
|
||||||
</li>
|
{{choice.label}}
|
||||||
{{/each}}
|
</label>
|
||||||
</ol>
|
{{#if choice.children}}
|
||||||
|
{{> traitList choices=choice.children}}
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ol>
|
||||||
|
{{/inline}}
|
||||||
|
|
||||||
|
{{> traitList}}
|
||||||
{{#if allowCustom}}
|
{{#if allowCustom}}
|
||||||
<div class="form-group stacked">
|
<div class="form-group stacked">
|
||||||
<label>{{ localize "SW5E.TraitSelectorSpecial" }}</label>
|
<label>{{ localize "SW5E.TraitSelectorSpecial" }}</label>
|
||||||
<input type="text" name="custom" value="{{custom}}" data-dtype="String"/>
|
<input type="text" name="custom" value="{{custom}}" data-dtype="String"/>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.TraitSave"}}</button>
|
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.TraitSave"}}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="sw5e chat-card item-card" data-actor-id="{{actor.data._id}}" data-item-id="{{item._id}}"
|
<div class="sw5e chat-card item-card" data-actor-id="{{actor._id}}" data-item-id="{{item._id}}"
|
||||||
{{#if tokenId}}data-token-id="{{tokenId}}"{{/if}} {{#if isPower}}data-power-level="{{item.data.level}}"{{/if}}>
|
{{#if tokenId}}data-token-id="{{tokenId}}"{{/if}} {{#if isPower}}data-power-level="{{item.data.level}}"{{/if}}>
|
||||||
<header class="card-header flexrow">
|
<header class="card-header flexrow">
|
||||||
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/>
|
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if hasSave}}
|
{{#if hasSave}}
|
||||||
<button data-action="save" data-ability="{{data.save.ability}}" disabled>
|
<button data-action="save" data-ability="{{data.save.ability}}">
|
||||||
{{ localize "SW5E.SavingThrow" }} {{labels.save}}
|
{{ localize "SW5E.SavingThrow" }} {{labels.save}}
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -40,7 +40,6 @@
|
||||||
<button data-action="placeTemplate">{{ localize "SW5E.PlaceTemplate" }}</button>
|
<button data-action="placeTemplate">{{ localize "SW5E.PlaceTemplate" }}</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
||||||
{{#if isTool}}
|
{{#if isTool}}
|
||||||
<button data-action="toolCheck" data-ability="{{data.ability.value}}">{{ localize "SW5E.Use" }} {{item.name}}</button>
|
<button data-action="toolCheck" data-ability="{{data.ability.value}}">{{ localize "SW5E.Use" }} {{item.name}}</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary flexrow">
|
<ul class="summary flexrow">
|
||||||
<li></li>
|
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<select name="data.rarity">
|
||||||
</li>
|
{{selectOptions config.itemRarity selected=data.rarity blank=" "}}
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
</li>
|
</li>
|
||||||
|
@ -53,11 +54,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{localize 'SW5E.ItemContainerCapacityType'}}</label>
|
<label>{{localize 'SW5E.ItemContainerCapacityType'}}</label>
|
||||||
<select name="data.capacity.type">
|
<select name="data.capacity.type">
|
||||||
{{#select data.capacity.type}}
|
{{selectOptions config.itemCapacityTypes selected=data.capacity.type localize=true}}
|
||||||
{{#each config.itemCapacityTypes}}
|
|
||||||
<option value="{{@key}}">{{localize this}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -128,12 +128,31 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>
|
||||||
|
{{localize "SW5E.ClassSkillsEligible"}}
|
||||||
|
{{#if editable }}
|
||||||
|
<a class="trait-selector class-skills" data-target="data.skills.choices" data-options="skills.choices">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<ul class="traits-list">
|
||||||
|
{{#each data.skills.choices}}
|
||||||
|
<li class="tag {{this}}">{{lookup ../config.skills this}}</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>
|
<label>
|
||||||
{{localize "SW5E.ClassSkillsChosen"}}
|
{{localize "SW5E.ClassSkillsChosen"}}
|
||||||
{{#if editable }}
|
{{#if editable }}
|
||||||
<a class="trait-selector class-skills" data-target="data.skills" data-options="skills">
|
<a class="trait-selector class-skills" data-target="data.skills" data-options="skills">
|
||||||
<i class="fas fa-edit"></i></a>
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
{{lookup config.consumableTypes data.consumableType }}
|
{{lookup config.consumableTypes data.consumableType }}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<select name="data.rarity">
|
||||||
|
{{selectOptions config.itemRarity selected=data.rarity blank=" "}}
|
||||||
|
</select>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
|
@ -49,11 +51,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.ItemConsumableType" }}</label>
|
<label>{{ localize "SW5E.ItemConsumableType" }}</label>
|
||||||
<select name="data.consumableType">
|
<select name="data.consumableType">
|
||||||
{{#select data.consumableType}}
|
{{selectOptions config.consumableTypes selected=data.consumableType}}
|
||||||
{{#each config.consumableTypes as |name type|}}
|
|
||||||
<option value="{{type}}">{{name}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
{{lookup config.equipmentTypes data.armor.type }}
|
{{lookup config.equipmentTypes data.armor.type }}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<select name="data.rarity">
|
||||||
|
{{selectOptions config.itemRarity selected=data.rarity blank=" "}}
|
||||||
|
</select>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
|
@ -49,12 +51,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.ItemEquipmentType" }}</label>
|
<label>{{ localize "SW5E.ItemEquipmentType" }}</label>
|
||||||
<select name="data.armor.type">
|
<select name="data.armor.type">
|
||||||
{{#select data.armor.type}}
|
{{selectOptions config.equipmentTypes selected=data.armor.type blank=""}}
|
||||||
<option value=""></option>
|
|
||||||
{{#each config.equipmentTypes as |name type|}}
|
|
||||||
<option value="{{type}}">{{localize name}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
<ul class="summary flexrow">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<select name="data.rarity">
|
||||||
|
{{selectOptions config.itemRarity selected=data.rarity blank=" "}}
|
||||||
|
</select>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
<ul class="summary flexrow">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<select name="data.rarity">
|
||||||
|
{{selectOptions config.itemRarity selected=data.rarity blank=" "}}
|
||||||
|
</select>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
|
@ -40,27 +42,27 @@
|
||||||
{{!-- Details Tab --}}
|
{{!-- Details Tab --}}
|
||||||
<div class="tab details" data-group="primary" data-tab="details">
|
<div class="tab details" data-group="primary" data-tab="details">
|
||||||
|
|
||||||
|
{{!-- Tool Type --}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ localize "SW5E.ItemToolType" }}</label>
|
||||||
|
<select name="data.toolType">
|
||||||
|
{{selectOptions config.toolTypes selected=data.toolType blank=""}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{!-- Tool Proficiency --}}
|
{{!-- Tool Proficiency --}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.ItemToolProficiency" }}</label>
|
<label>{{ localize "SW5E.ItemToolProficiency" }}</label>
|
||||||
<select name="data.proficient" data-dtype="Number">
|
<select name="data.proficient" data-dtype="Number">
|
||||||
{{#select data.proficient}}
|
{{selectOptions config.proficiencyLevels selected=data.proficient}}
|
||||||
{{#each config.proficiencyLevels as |label p|}}
|
</select>
|
||||||
<option value="{{p}}">{{label}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- Ability Check --}}
|
{{!-- Ability Check --}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.DefaultAbilityCheck" }}</label>
|
<label>{{ localize "SW5E.DefaultAbilityCheck" }}</label>
|
||||||
<select name="data.ability">
|
<select name="data.ability">
|
||||||
{{#select data.ability}}
|
{{selectOptions config.abilities selected=data.ability}}
|
||||||
{{#each config.abilities as |name a|}}
|
|
||||||
<option value="{{a}}">{{name}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
{{lookup config.weaponTypes data.weaponType }}
|
{{lookup config.weaponTypes data.weaponType }}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<select name="data.rarity">
|
||||||
|
{{selectOptions config.itemRarity selected=data.rarity blank=" "}}
|
||||||
|
</select>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
|
@ -49,11 +51,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.ItemWeaponType" }}</label>
|
<label>{{ localize "SW5E.ItemWeaponType" }}</label>
|
||||||
<select name="data.weaponType">
|
<select name="data.weaponType">
|
||||||
{{#select data.weaponType}}
|
{{selectOptions config.weaponTypes selected=data.weaponType}}
|
||||||
{{#each config.weaponTypes as |name type|}}
|
|
||||||
<option value="{{type}}">{{name}}</option>
|
|
||||||
{{/each}}
|
|
||||||
{{/select}}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue