forked from GitHub-Mirrors/foundry-sw5e
Super VJ Update
This commit is contained in:
parent
442212bdea
commit
1983b74bde
59 changed files with 4640 additions and 2462 deletions
193
lang/en.json
193
lang/en.json
|
@ -5,25 +5,41 @@
|
||||||
"SW5E.AbbreviationConc": "Conc.",
|
"SW5E.AbbreviationConc": "Conc.",
|
||||||
"SW5E.AbbreviationDC": "DC",
|
"SW5E.AbbreviationDC": "DC",
|
||||||
"SW5E.AbbreviationLR": "LR",
|
"SW5E.AbbreviationLR": "LR",
|
||||||
|
"SW5E.AbbreviationLevel": "Lvl.",
|
||||||
"SW5E.AbbreviationLbs": "lbs.",
|
"SW5E.AbbreviationLbs": "lbs.",
|
||||||
"SW5E.AbbreviationSR": "SR",
|
"SW5E.AbbreviationSR": "SR",
|
||||||
"SW5E.Ability": "Ability",
|
"SW5E.Ability": "Ability",
|
||||||
"SW5E.AbilityCha": "Charisma",
|
|
||||||
"SW5E.AbilityCon": "Constitution",
|
|
||||||
"SW5E.AbilityDex": "Dexterity",
|
|
||||||
"SW5E.AbilityInt": "Intelligence",
|
|
||||||
"SW5E.AbilityModifier": "Ability Modifier",
|
|
||||||
"SW5E.AbilityStr": "Strength",
|
"SW5E.AbilityStr": "Strength",
|
||||||
"SW5E.AbilityUseCantUse": "You are not currently able to use this ability!",
|
"SW5E.AbilityStrAbbr": "str",
|
||||||
"SW5E.AbilityUseCharged": "charged",
|
"SW5E.AbilityCon": "Constitution",
|
||||||
"SW5E.AbilityUseConsume": "Consume Available Usage?",
|
"SW5E.AbilityConAbbr": "con",
|
||||||
"SW5E.AbilityUseDepleted": "depleted",
|
"SW5E.AbilityDex": "Dexterity",
|
||||||
"SW5E.AbilityUseHint": "Configure how you would like to use the",
|
"SW5E.AbilityDexAbbr": "dex",
|
||||||
"SW5E.AbilityUseRechargeHint": "This ability uses a recharge mechanic and is currently",
|
"SW5E.AbilityInt": "Intelligence",
|
||||||
"SW5E.AbilityUseWarnEnd": "available uses per",
|
"SW5E.AbilityIntAbbr": "int",
|
||||||
"SW5E.AbilityUseWarnStart": "This ability has",
|
|
||||||
"SW5E.AbilityWis": "Wisdom",
|
"SW5E.AbilityWis": "Wisdom",
|
||||||
|
"SW5E.AbilityWisAbbr": "wis",
|
||||||
|
"SW5E.AbilityCha": "Charisma",
|
||||||
|
"SW5E.AbilityChaAbbr": "cha",
|
||||||
|
"SW5E.AbilityModifier": "Ability Modifier",
|
||||||
|
"SW5E.AbilityPromptText": "What type of {ability} check?",
|
||||||
|
"SW5E.AbilityPromptTitle": "{ability} Ability Check",
|
||||||
|
"SW5E.AbilityUseHint": "Configure how you would like to use the {name} {type}.",
|
||||||
|
"SW5E.AbilityUseUnavailableHint": "There are no uses of this item remaining!",
|
||||||
|
"SW5E.AbilityUseChargedHint": "This {type} is charged and ready to use!",
|
||||||
|
"SW5E.AbilityUseRechargedHint": "This {type} is depleted and must be recharged!",
|
||||||
|
"SW5E.AbilityUseNormalHint": "This {type} has {value} of {max} uses per {per} remaining.",
|
||||||
|
"SW5E.AbilityUseConsumableChargeHint": "Using this {type} will consume 1 charge of {value} remaining.",
|
||||||
|
"SW5E.AbilityUseConsumableQuantityHint": "Using this {type} will consume 1 quantity of {quantity} remaining",
|
||||||
|
"SW5E.AbilityUseConsumableDestroyHint": "Using this {type} will consume its final charge and it will be destroyed.",
|
||||||
|
"SW5E.AbilityUseConsume": "Consume Available Usage?",
|
||||||
|
"SW5E.AbilityUseChargesLabel": "{value} Charges",
|
||||||
|
"SW5E.AbilityUseConsumableLabel": "{max} per {per}",
|
||||||
|
"SW5E.AbilityUseCast": "Cast Spell",
|
||||||
|
"SW5E.AbilityUseUse": "Use Ability",
|
||||||
|
|
||||||
"SW5E.Action": "Action",
|
"SW5E.Action": "Action",
|
||||||
|
"SW5E.ActionPl": "Actions",
|
||||||
"SW5E.ActionAbil": "Ability Check",
|
"SW5E.ActionAbil": "Ability Check",
|
||||||
"SW5E.ActionHeal": "Healing",
|
"SW5E.ActionHeal": "Healing",
|
||||||
"SW5E.ActionMPAK": "Melee Power Attack",
|
"SW5E.ActionMPAK": "Melee Power Attack",
|
||||||
|
@ -33,6 +49,8 @@
|
||||||
"SW5E.ActionRWAK": "Ranged Weapon Attack",
|
"SW5E.ActionRWAK": "Ranged Weapon Attack",
|
||||||
"SW5E.ActionSave": "Saving Throw",
|
"SW5E.ActionSave": "Saving Throw",
|
||||||
"SW5E.ActionUtil": "Utility",
|
"SW5E.ActionUtil": "Utility",
|
||||||
|
"SW5E.ActionWarningNoItem": "The requested item {item} no longer exists on Actor {name}",
|
||||||
|
"SW5E.ActionWarningNoToken": "You must have one or more controlled Tokens in order to use this option.",
|
||||||
"SW5E.Add": "Add",
|
"SW5E.Add": "Add",
|
||||||
"SW5E.Advantage": "Advantage",
|
"SW5E.Advantage": "Advantage",
|
||||||
"SW5E.Alignment": "Alignment",
|
"SW5E.Alignment": "Alignment",
|
||||||
|
@ -44,9 +62,10 @@
|
||||||
"SW5E.AlignmentLN": "Lawful Neutral",
|
"SW5E.AlignmentLN": "Lawful Neutral",
|
||||||
"SW5E.AlignmentND": "Neutral Dark",
|
"SW5E.AlignmentND": "Neutral Dark",
|
||||||
"SW5E.AlignmentNL": "Neutral Light",
|
"SW5E.AlignmentNL": "Neutral Light",
|
||||||
"SW5E.AlignmentBN": "Balenced Neutral",
|
"SW5E.AlignmentBN": "Balanced Neutral",
|
||||||
"SW5E.Archetypes": "Archetypes",
|
"SW5E.Archetypes": "Archetypes",
|
||||||
"SW5E.ArmorClass": "Armor Class",
|
"SW5E.ArmorClass": "Armor Class",
|
||||||
|
"SW5E.AC": "AC",
|
||||||
"SW5E.ArmorProperties": "Armor Properties",
|
"SW5E.ArmorProperties": "Armor Properties",
|
||||||
"SW5E.ArmorProperAbsorptive": "Absorptive",
|
"SW5E.ArmorProperAbsorptive": "Absorptive",
|
||||||
"SW5E.ArmorProperAgile": "Agile",
|
"SW5E.ArmorProperAgile": "Agile",
|
||||||
|
@ -76,6 +95,8 @@
|
||||||
"SW5E.ArmorProperSteadfast": "Steadfast",
|
"SW5E.ArmorProperSteadfast": "Steadfast",
|
||||||
"SW5E.ArmorProperVersatile": "Versatile",
|
"SW5E.ArmorProperVersatile": "Versatile",
|
||||||
"SW5E.Attack": "Attack",
|
"SW5E.Attack": "Attack",
|
||||||
|
"SW5E.AttackPl": "Attacks",
|
||||||
|
"SW5E.AttackRoll": "Attack Roll",
|
||||||
"SW5E.Attributes": "Attributes",
|
"SW5E.Attributes": "Attributes",
|
||||||
"SW5E.Attuned": "Attuned",
|
"SW5E.Attuned": "Attuned",
|
||||||
"SW5E.Background": "Background",
|
"SW5E.Background": "Background",
|
||||||
|
@ -84,12 +105,12 @@
|
||||||
"SW5E.BonusAbilitySave": "Global Saving Throw Bonus",
|
"SW5E.BonusAbilitySave": "Global Saving Throw Bonus",
|
||||||
"SW5E.BonusAbilitySkill": "Global Skill Check Bonus",
|
"SW5E.BonusAbilitySkill": "Global Skill Check Bonus",
|
||||||
"SW5E.BonusAction": "Bonus Action",
|
"SW5E.BonusAction": "Bonus Action",
|
||||||
"SW5E.BonusMSAttack": "Melee Power Attack Bonus",
|
"SW5E.BonusMPAttack": "Melee Power Attack Bonus",
|
||||||
"SW5E.BonusMSDamage": "Melee Power Damage Bonus",
|
"SW5E.BonusMPDamage": "Melee Power Damage Bonus",
|
||||||
"SW5E.BonusMWAttack": "Melee Weapon Attack Bonus",
|
"SW5E.BonusMWAttack": "Melee Weapon Attack Bonus",
|
||||||
"SW5E.BonusMWDamage": "Melee Weapon Damage Bonus",
|
"SW5E.BonusMWDamage": "Melee Weapon Damage Bonus",
|
||||||
"SW5E.BonusRSAttack": "Ranged Power Attack Bonus",
|
"SW5E.BonusRPAttack": "Ranged Power Attack Bonus",
|
||||||
"SW5E.BonusRSDamage": "Ranged Power Damage Bonus",
|
"SW5E.BonusRPDamage": "Ranged Power Damage Bonus",
|
||||||
"SW5E.BonusRWAttack": "Ranged Weapon Attack Bonus",
|
"SW5E.BonusRWAttack": "Ranged Weapon Attack Bonus",
|
||||||
"SW5E.BonusRWDamage": "Ranged Weapon Damage Bonus",
|
"SW5E.BonusRWDamage": "Ranged Weapon Damage Bonus",
|
||||||
"SW5E.BonusSaveForm": "Update Bonuses",
|
"SW5E.BonusSaveForm": "Update Bonuses",
|
||||||
|
@ -116,6 +137,7 @@
|
||||||
"SW5E.ConBlinded": "Blinded",
|
"SW5E.ConBlinded": "Blinded",
|
||||||
"SW5E.ConCharmed": "Charmed",
|
"SW5E.ConCharmed": "Charmed",
|
||||||
"SW5E.ConDeafened": "Deafened",
|
"SW5E.ConDeafened": "Deafened",
|
||||||
|
"SW5E.ConDiseased": "Diseased",
|
||||||
"SW5E.ConExhaustion": "Exhaustion",
|
"SW5E.ConExhaustion": "Exhaustion",
|
||||||
"SW5E.ConFrightened": "Frightened",
|
"SW5E.ConFrightened": "Frightened",
|
||||||
"SW5E.ConGrappled": "Grappled",
|
"SW5E.ConGrappled": "Grappled",
|
||||||
|
@ -128,9 +150,22 @@
|
||||||
"SW5E.ConProne": "Prone",
|
"SW5E.ConProne": "Prone",
|
||||||
"SW5E.ConRestrained": "Restrained",
|
"SW5E.ConRestrained": "Restrained",
|
||||||
"SW5E.ConShocked": "Shocked",
|
"SW5E.ConShocked": "Shocked",
|
||||||
|
"SW5E.ConSlowed": "Slowed",
|
||||||
"SW5E.ConStunned": "Stunned",
|
"SW5E.ConStunned": "Stunned",
|
||||||
"SW5E.ConUnconscious": "Unconscious",
|
"SW5E.ConUnconscious": "Unconscious",
|
||||||
"SW5E.Concentration": "Concentration",
|
"SW5E.Concentration": "Concentration",
|
||||||
|
|
||||||
|
"SW5E.ConsumeTitle": "Resource Consumption",
|
||||||
|
"SW5E.ConsumeAmmunition": "Ammunition",
|
||||||
|
"SW5E.ConsumeAttribute": "Attribute",
|
||||||
|
"SW5E.ConsumeMaterial": "Material",
|
||||||
|
"SW5E.ConsumeCharges": "Item Uses",
|
||||||
|
"SW5E.ConsumeWarningNoResource": "{name} is designated to consume {type} but no resource is specified!",
|
||||||
|
"SW5E.ConsumeWarningNoSource": "The designated {type} source that {name} consumes no longer exists!",
|
||||||
|
"SW5E.ConsumeWarningNoQuantity": "{name} has run out of its designated {type}!",
|
||||||
|
"SW5E.ConsumeWarningZeroAttribute": "{name} has run out of its designated attribute resource pool!",
|
||||||
|
|
||||||
|
"SW5E.ConsumableAmmunition": "Ammunition",
|
||||||
"SW5E.ConsumableFood": "Food",
|
"SW5E.ConsumableFood": "Food",
|
||||||
"SW5E.ConsumablePoison": "Poison",
|
"SW5E.ConsumablePoison": "Poison",
|
||||||
"SW5E.ConsumableAdrenal": "Adrenal",
|
"SW5E.ConsumableAdrenal": "Adrenal",
|
||||||
|
@ -139,8 +174,17 @@
|
||||||
"SW5E.ConsumableTrinket": "Trinket",
|
"SW5E.ConsumableTrinket": "Trinket",
|
||||||
"SW5E.ConsumableTechnology": "Technology",
|
"SW5E.ConsumableTechnology": "Technology",
|
||||||
"SW5E.ConsumableAmmunition": "Ammunition",
|
"SW5E.ConsumableAmmunition": "Ammunition",
|
||||||
|
"SW5E.ConsumableUseWarnStart": "This consumable has",
|
||||||
|
"SW5E.ConsumableUseWarnEnd": "of the current unit",
|
||||||
|
"SW5E.ConsumableUnitWarn": "units remaining",
|
||||||
|
"SW5E.ConsumableLastChargeWarn": "This is the last charge of this unit and consuming it will also reduce the item's quantity by 1",
|
||||||
|
"SW5E.ConsumableWithoutCharges": "available units to use",
|
||||||
"SW5E.Consumed": "Consumed",
|
"SW5E.Consumed": "Consumed",
|
||||||
"SW5E.CostGP": "Cost (GP)",
|
"SW5E.Cover": "Cover",
|
||||||
|
"SW5E.CoverHalf": "Half",
|
||||||
|
"SW5E.CoverThreeQuarters": "Three Quarters",
|
||||||
|
"SW5E.CoverTotal": "Total",
|
||||||
|
"SW5E.CostGP": "Cost (CR)",
|
||||||
"SW5E.Critical": "Critical",
|
"SW5E.Critical": "Critical",
|
||||||
"SW5E.CriticalHit": "Critical Hit",
|
"SW5E.CriticalHit": "Critical Hit",
|
||||||
"SW5E.Currency": "Currency",
|
"SW5E.Currency": "Currency",
|
||||||
|
@ -160,15 +204,22 @@
|
||||||
"SW5E.DamageKinetic": "Kinetic",
|
"SW5E.DamageKinetic": "Kinetic",
|
||||||
"SW5E.DamageLightning": "Lightning",
|
"SW5E.DamageLightning": "Lightning",
|
||||||
"SW5E.DamageNecrotic": "Necrotic",
|
"SW5E.DamageNecrotic": "Necrotic",
|
||||||
|
"SW5E.DamagePhysical": "Physical",
|
||||||
"SW5E.DamagePoison": "Poison",
|
"SW5E.DamagePoison": "Poison",
|
||||||
"SW5E.DamagePsychic": "Psychic",
|
"SW5E.DamagePsychic": "Psychic",
|
||||||
"SW5E.DamageSonic": "Sonic",
|
"SW5E.DamageSonic": "Sonic",
|
||||||
"SW5E.Day": "Day",
|
"SW5E.Day": "Day",
|
||||||
"SW5E.DeathSave": "Death Saves",
|
"SW5E.DeathSave": "Death Saves",
|
||||||
|
"SW5E.DeathSaveCriticalSuccess": "{name} critically succeeded on a death saving throw and has regained 1 Hit Point!",
|
||||||
|
"SW5E.DeathSaveSuccess": "{name} has survived with 3 death save successes and is now stable!",
|
||||||
|
"SW5E.DeathSaveFailure": "{name} has died with 3 death save failures!",
|
||||||
|
"SW5E.DeathSavingThrow": "Death Saving Throw",
|
||||||
|
"SW5E.DeathSaveUnnecessary": "You do not need to roll death saves because you have a positive number of hit points or have already reached 3 successes or failures.",
|
||||||
"SW5E.Default": "Default",
|
"SW5E.Default": "Default",
|
||||||
"SW5E.DefaultAbilityCheck": "Default Ability Check",
|
"SW5E.DefaultAbilityCheck": "Default Ability Check",
|
||||||
"SW5E.Description": "Description",
|
"SW5E.Description": "Description",
|
||||||
"SW5E.Details": "Details",
|
"SW5E.Details": "Details",
|
||||||
|
"SW5E.Dimensions": "Dimensions",
|
||||||
"SW5E.Disadvantage": "Disadvantage",
|
"SW5E.Disadvantage": "Disadvantage",
|
||||||
"SW5E.DistAny": "Any",
|
"SW5E.DistAny": "Any",
|
||||||
"SW5E.DistFt": "Feet",
|
"SW5E.DistFt": "Feet",
|
||||||
|
@ -185,6 +236,7 @@
|
||||||
"SW5E.EquipmentShield": "Shield",
|
"SW5E.EquipmentShield": "Shield",
|
||||||
"SW5E.EquipmentShieldProficiency": "Shields",
|
"SW5E.EquipmentShieldProficiency": "Shields",
|
||||||
"SW5E.EquipmentTrinket": "Trinket",
|
"SW5E.EquipmentTrinket": "Trinket",
|
||||||
|
"SW5E.EquipmentVehicle": "Vehicle Equipment",
|
||||||
"SW5E.Equipped": "Equipped",
|
"SW5E.Equipped": "Equipped",
|
||||||
"SW5E.Exhaustion": "Exhaustion",
|
"SW5E.Exhaustion": "Exhaustion",
|
||||||
"SW5E.Expertise": "Expertise",
|
"SW5E.Expertise": "Expertise",
|
||||||
|
@ -208,6 +260,7 @@
|
||||||
"SW5E.ItemTypePowerPl": "Powers",
|
"SW5E.ItemTypePowerPl": "Powers",
|
||||||
"SW5E.ItemTypeWeapon": "Weapon",
|
"SW5E.ItemTypeWeapon": "Weapon",
|
||||||
"SW5E.ItemTypeWeaponPl": "Weapons",
|
"SW5E.ItemTypeWeaponPl": "Weapons",
|
||||||
|
"SW5E.ItemNoUses": "{name} has no available uses remaining.",
|
||||||
|
|
||||||
"SW5E.FeatureActive": "Active Abilities",
|
"SW5E.FeatureActive": "Active Abilities",
|
||||||
"SW5E.FeatureAdd": "Create Feature",
|
"SW5E.FeatureAdd": "Create Feature",
|
||||||
|
@ -217,12 +270,37 @@
|
||||||
"SW5E.FeatureRechargeResult": "1d6 Result",
|
"SW5E.FeatureRechargeResult": "1d6 Result",
|
||||||
"SW5E.FeatureUsage": "Feature Usage",
|
"SW5E.FeatureUsage": "Feature Usage",
|
||||||
"SW5E.Features": "Features",
|
"SW5E.Features": "Features",
|
||||||
|
"SW5E.FeetAbbr": "ft.",
|
||||||
"SW5E.Filter": "Filter",
|
"SW5E.Filter": "Filter",
|
||||||
"SW5E.FilterNoPowers": "No powers found for this set of filters.",
|
"SW5E.FilterNoPowers": "No powers found for this set of filters.",
|
||||||
"SW5E.NoPowerLevels": "This character has no powercaster levels, but you may add powers manually.",
|
"SW5E.NoPowerLevels": "This character has no powercaster levels, but you may add powers manually.",
|
||||||
|
|
||||||
"SW5E.FlagsInstructions": "Configure character features and traits which fine-tune behaviors of the SW5e system.",
|
"SW5E.FlagsInstructions": "Configure character features and traits which fine-tune behaviors of the SW5e system.",
|
||||||
"SW5E.FlagsSave": "Update Special Traits",
|
"SW5E.FlagsSave": "Update Special Traits",
|
||||||
"SW5E.FlagsTitle": "Configure Special Traits",
|
"SW5E.FlagsTitle": "Configure Special Traits",
|
||||||
|
"SW5E.FlagsPowerfulBuild": "Powerful Build",
|
||||||
|
"SW5E.FlagsPowerfulBuildHint": "Provides increased carrying capacity.",
|
||||||
|
"SW5E.FlagsSavageAttacks": "Savage Attacks",
|
||||||
|
"SW5E.FlagsSavageAttacksHint": "Adds extra critical hit weapon dice.",
|
||||||
|
"SW5E.FlagsElvenAccuracy": "Elven Accuracy",
|
||||||
|
"SW5E.FlagsElvenAccuracyHint": "Roll an extra d20 with advantage to Dex, Int, Wis, or Cha.",
|
||||||
|
"SW5E.FlagsHalflingLucky": "Halfling Lucky",
|
||||||
|
"SW5E.FlagsHalflingLuckyHint": "Reroll ones when rolling d20 checks.",
|
||||||
|
"SW5E.FlagsInitiativeAdv": "Advantage on Initiative",
|
||||||
|
"SW5E.FlagsInitiativeAdvHint": "Provided by feats or magical items.",
|
||||||
|
"SW5E.FlagsAlert": "Alert Feat",
|
||||||
|
"SW5E.FlagsAlertHint": "Provides +5 to Initiative.",
|
||||||
|
"SW5E.FlagsJOAT": "Jack of All Trades",
|
||||||
|
"SW5E.FlagsJOATHint": "Half-Proficiency to Ability Checks in which you are not already Proficient.",
|
||||||
|
"SW5E.FlagsObservant": "Observant Feat",
|
||||||
|
"SW5E.FlagsObservantHint": "Provides a +5 to passive Perception and Investigation.",
|
||||||
|
"SW5E.FlagsReliableTalent": "Reliable Talent",
|
||||||
|
"SW5E.FlagsReliableTalentHint": "Rogues Reliable Talent Feature.",
|
||||||
|
"SW5E.FlagsRemarkableAthlete": "Remarkable Athlete.",
|
||||||
|
"SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
|
||||||
|
"SW5E.FlagsCritThreshold": "Critical Hit Threshold",
|
||||||
|
"SW5E.FlagsCritThresholdHint": "Allow for expanded critical range; for example Improved or Superior Critical",
|
||||||
|
|
||||||
"SW5E.Flat": "Flat",
|
"SW5E.Flat": "Flat",
|
||||||
"SW5E.Formula": "Formula",
|
"SW5E.Formula": "Formula",
|
||||||
"SW5E.GrantedAbilities": "Granted Abilities",
|
"SW5E.GrantedAbilities": "Granted Abilities",
|
||||||
|
@ -230,9 +308,12 @@
|
||||||
"SW5E.Healing": "Healing",
|
"SW5E.Healing": "Healing",
|
||||||
"SW5E.HealingTemp": "Healing (Temporary)",
|
"SW5E.HealingTemp": "Healing (Temporary)",
|
||||||
"SW5E.Health": "Health",
|
"SW5E.Health": "Health",
|
||||||
|
"SW5E.HealthConditions": "Health Conditions",
|
||||||
"SW5E.HealthFormula": "Health Formula",
|
"SW5E.HealthFormula": "Health Formula",
|
||||||
"SW5E.HitDice": "Hit Dice",
|
"SW5E.HitDice": "Hit Dice",
|
||||||
|
"SW5E.HitDiceRoll": "Roll Hit Dice",
|
||||||
"SW5E.HitDiceUsed": "Hit Dice Used",
|
"SW5E.HitDiceUsed": "Hit Dice Used",
|
||||||
|
"SW5E.HitDiceWarn": "{name} has no available {formula} Hit Dice remaining!",
|
||||||
"SW5E.Identified": "Identified",
|
"SW5E.Identified": "Identified",
|
||||||
"SW5E.Initiative": "Initiative",
|
"SW5E.Initiative": "Initiative",
|
||||||
"SW5E.Inspiration": "Inspiration",
|
"SW5E.Inspiration": "Inspiration",
|
||||||
|
@ -242,12 +323,10 @@
|
||||||
"SW5E.ItemActivationCost": "Activation Cost",
|
"SW5E.ItemActivationCost": "Activation Cost",
|
||||||
"SW5E.ItemAttackBonus": "Attack Roll Bonus",
|
"SW5E.ItemAttackBonus": "Attack Roll Bonus",
|
||||||
"SW5E.ItemConsumableActivation": "Consumable Activation",
|
"SW5E.ItemConsumableActivation": "Consumable Activation",
|
||||||
|
"SW5E.ItemConsumableUsage": "Consumable Usage",
|
||||||
"SW5E.ItemConsumableDetails": "Consumable Details",
|
"SW5E.ItemConsumableDetails": "Consumable Details",
|
||||||
|
|
||||||
"SW5E.ItemConsumableStatus": "Consumable Status",
|
"SW5E.ItemConsumableStatus": "Consumable Status",
|
||||||
"SW5E.ItemConsumableType": "Consumable Type",
|
"SW5E.ItemConsumableType": "Consumable Type",
|
||||||
"SW5E.ItemConsumableUsage": "Consumable Usage",
|
|
||||||
"SW5E.ItemConsumeOnUse": "Consume on Use",
|
|
||||||
"SW5E.ItemContainerCapacity": "Capacity",
|
"SW5E.ItemContainerCapacity": "Capacity",
|
||||||
"SW5E.ItemContainerCapacityItems": "Items",
|
"SW5E.ItemContainerCapacityItems": "Items",
|
||||||
"SW5E.ItemContainerCapacityType": "Capacity Type",
|
"SW5E.ItemContainerCapacityType": "Capacity Type",
|
||||||
|
@ -270,6 +349,10 @@
|
||||||
"SW5E.ItemEquipmentUsage": "Equipment Usage",
|
"SW5E.ItemEquipmentUsage": "Equipment Usage",
|
||||||
|
|
||||||
"SW5E.ItemName": "Item Name",
|
"SW5E.ItemName": "Item Name",
|
||||||
|
"SW5E.ItemNew": "New {type}",
|
||||||
|
"SW5E.ItemRechargeCheck": "{name} recharge check",
|
||||||
|
"SW5E.ItemRechargeFailure": "failure!",
|
||||||
|
"SW5E.ItemRechargeSuccess": "success!",
|
||||||
"SW5E.ItemRequiredStr": "Required Strength",
|
"SW5E.ItemRequiredStr": "Required Strength",
|
||||||
"SW5E.ItemToolProficiency": "Tool Proficiency",
|
"SW5E.ItemToolProficiency": "Tool Proficiency",
|
||||||
|
|
||||||
|
@ -281,7 +364,8 @@
|
||||||
"SW5E.ItemWeaponType": "Weapon Type",
|
"SW5E.ItemWeaponType": "Weapon Type",
|
||||||
"SW5E.ItemWeaponUsage": "Weapon Usage",
|
"SW5E.ItemWeaponUsage": "Weapon Usage",
|
||||||
"SW5E.JackOfAllTrades": "Jack of all Trades",
|
"SW5E.JackOfAllTrades": "Jack of all Trades",
|
||||||
"SW5E.LairAct": "Lair Action",
|
"SW5E.LairAct": "Uses Lair Action",
|
||||||
|
"SW5E.LairActionLabel": "Lair Action",
|
||||||
"SW5E.Languages": "Languages",
|
"SW5E.Languages": "Languages",
|
||||||
"SW5E.LanguagesAleena": "Aleena",
|
"SW5E.LanguagesAleena": "Aleena",
|
||||||
"SW5E.LanguagesAntarian": "Antarian",
|
"SW5E.LanguagesAntarian": "Antarian",
|
||||||
|
@ -383,12 +467,18 @@
|
||||||
"SW5E.LanguagesYevethan": "Yevethan",
|
"SW5E.LanguagesYevethan": "Yevethan",
|
||||||
"SW5E.LanguagesZabraki": "Zabraki",
|
"SW5E.LanguagesZabraki": "Zabraki",
|
||||||
"SW5E.LanguagesZygerrian": "Zygerrian",
|
"SW5E.LanguagesZygerrian": "Zygerrian",
|
||||||
"SW5E.LegAct": "Legd. Actions",
|
"SW5E.LegAct": "Legendary Actions",
|
||||||
"SW5E.LegRes": "Legd. Resistance",
|
"SW5E.LegendaryActionLabel": "Legendary Action",
|
||||||
|
"SW5E.LegRes": "Legendary Resistance",
|
||||||
"SW5E.Level": "Level",
|
"SW5E.Level": "Level",
|
||||||
"SW5E.LevelScaling": "Level Scaling",
|
"SW5E.LevelScaling": "Level Scaling",
|
||||||
"SW5E.LimitedUses": "Limited Uses",
|
"SW5E.LimitedUses": "Limited Uses",
|
||||||
"SW5E.LongRest": "Long Rest",
|
"SW5E.LongRest": "Long Rest",
|
||||||
|
"SW5E.LongRestNormal": "Long Rest (8 hours)",
|
||||||
|
"SW5E.LongRestGritty": "Long Rest (7 days)",
|
||||||
|
"SW5E.LongRestEpic": "Long Rest (1 hour)",
|
||||||
|
"SW5E.LongRestOvernight": "Long Rest (New Day)",
|
||||||
|
"SW5E.LongRestResult": "{name} takes a long rest and recovers {health} Hit Points and {dice} Hit Dice.",
|
||||||
"SW5E.Max": "Max",
|
"SW5E.Max": "Max",
|
||||||
"SW5E.Modifier": "Modifier",
|
"SW5E.Modifier": "Modifier",
|
||||||
"SW5E.Name": "Character Name",
|
"SW5E.Name": "Character Name",
|
||||||
|
@ -397,6 +487,8 @@
|
||||||
"SW5E.Normal": "Normal",
|
"SW5E.Normal": "Normal",
|
||||||
"SW5E.NotProficient": "Not Proficient",
|
"SW5E.NotProficient": "Not Proficient",
|
||||||
"SW5E.OtherFormula": "Other Formula",
|
"SW5E.OtherFormula": "Other Formula",
|
||||||
|
"SW5E.PactMagic": "Pact Magic",
|
||||||
|
"SW5E.Passive": "Passive",
|
||||||
"SW5E.PlaceTemplate": "Place Measured Template",
|
"SW5E.PlaceTemplate": "Place Measured Template",
|
||||||
"SW5E.Polymorph": "Polymorph",
|
"SW5E.Polymorph": "Polymorph",
|
||||||
"SW5E.PolymorphAcceptSettings": "Custom Settings",
|
"SW5E.PolymorphAcceptSettings": "Custom Settings",
|
||||||
|
@ -414,10 +506,12 @@
|
||||||
"SW5E.PolymorphMergeSkills": "Merge skill proficiencies (take the highest)",
|
"SW5E.PolymorphMergeSkills": "Merge skill proficiencies (take the highest)",
|
||||||
"SW5E.PolymorphPromptTitle": "Transforming Actor",
|
"SW5E.PolymorphPromptTitle": "Transforming Actor",
|
||||||
"SW5E.PolymorphRestoreTransformation": "Restore Transformation",
|
"SW5E.PolymorphRestoreTransformation": "Restore Transformation",
|
||||||
|
"SW5E.PolymorphRevertWarn": "You do not have permission to revert this Actor's polymorphed state.",
|
||||||
"SW5E.PolymorphTmpClass": "Temporary Class",
|
"SW5E.PolymorphTmpClass": "Temporary Class",
|
||||||
"SW5E.PolymorphTokens": "Transform all linked tokens?",
|
"SW5E.PolymorphTokens": "Transform all linked tokens?",
|
||||||
|
"SW5E.PolymorphWarn": "You are not allowed to polymorph this actor!",
|
||||||
"SW5E.PolymorphWildShape": "Wild Shape",
|
"SW5E.PolymorphWildShape": "Wild Shape",
|
||||||
"SW5E.Concentrated": "Concentrate",
|
"SW5E.Concentrate": "Concentrate",
|
||||||
"SW5E.Price": "Price",
|
"SW5E.Price": "Price",
|
||||||
"SW5E.Proficiency": "Proficiency",
|
"SW5E.Proficiency": "Proficiency",
|
||||||
"SW5E.Proficient": "Proficient",
|
"SW5E.Proficient": "Proficient",
|
||||||
|
@ -426,6 +520,8 @@
|
||||||
"SW5E.Range": "Range",
|
"SW5E.Range": "Range",
|
||||||
"SW5E.Rarity": "Rarity",
|
"SW5E.Rarity": "Rarity",
|
||||||
"SW5E.Reaction": "Reaction",
|
"SW5E.Reaction": "Reaction",
|
||||||
|
"SW5E.ReactionPl": "Reactions",
|
||||||
|
"SW5E.Recharge": "Recharge",
|
||||||
"SW5E.RequiredMaterials": "Required Materials",
|
"SW5E.RequiredMaterials": "Required Materials",
|
||||||
"SW5E.Requirements": "Requirements",
|
"SW5E.Requirements": "Requirements",
|
||||||
"SW5E.ResourcePrimary": "Resource 1",
|
"SW5E.ResourcePrimary": "Resource 1",
|
||||||
|
@ -438,7 +534,8 @@
|
||||||
"SW5E.RollExample": "e.g. +1d4",
|
"SW5E.RollExample": "e.g. +1d4",
|
||||||
"SW5E.RollMode": "Roll Mode",
|
"SW5E.RollMode": "Roll Mode",
|
||||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||||
"SW5E.Save": "Save",
|
"SW5E.SavingThrow": "Saving Throw",
|
||||||
|
"SW5E.SavePromptTitle": "{ability} Saving Throw",
|
||||||
"SW5E.ScalingFormula": "Scaling Formula",
|
"SW5E.ScalingFormula": "Scaling Formula",
|
||||||
"SW5E.SchoolLgt": "Light",
|
"SW5E.SchoolLgt": "Light",
|
||||||
"SW5E.SchoolUni": "Universal",
|
"SW5E.SchoolUni": "Universal",
|
||||||
|
@ -451,8 +548,13 @@
|
||||||
"SW5E.SenseTS": "Tremorsense",
|
"SW5E.SenseTS": "Tremorsense",
|
||||||
"SW5E.Senses": "Senses",
|
"SW5E.Senses": "Senses",
|
||||||
"SW5E.ShortRest": "Short Rest",
|
"SW5E.ShortRest": "Short Rest",
|
||||||
|
"SW5E.ShortRestNormal": "Short Rest (1 hour)",
|
||||||
|
"SW5E.ShortRestGritty": "Short Rest (8 hours)",
|
||||||
|
"SW5E.ShortRestEpic": "Short Rest (5 minutes)",
|
||||||
|
"SW5E.ShortRestOvernight": "Short Rest (New Day)",
|
||||||
"SW5E.ShortRestHint": "Take a short rest? On a short rest you may spend remaining Hit Dice and recover primary or secondary resources.",
|
"SW5E.ShortRestHint": "Take a short rest? On a short rest you may spend remaining Hit Dice and recover primary or secondary resources.",
|
||||||
"SW5E.ShortRestNoHD": "No Hit Dice remaining",
|
"SW5E.ShortRestNoHD": "No Hit Dice remaining",
|
||||||
|
"SW5E.ShortRestResult": "{name} takes a short rest spending {dice} Hit Dice to recover {health} Hit Points.",
|
||||||
"SW5E.ShortRestSelect": "Select Dice to Roll",
|
"SW5E.ShortRestSelect": "Select Dice to Roll",
|
||||||
"SW5E.Size": "Size",
|
"SW5E.Size": "Size",
|
||||||
"SW5E.SizeGargantuan": "Gargantuan",
|
"SW5E.SizeGargantuan": "Gargantuan",
|
||||||
|
@ -479,6 +581,7 @@
|
||||||
"SW5E.SkillSte": "Stealth",
|
"SW5E.SkillSte": "Stealth",
|
||||||
"SW5E.SkillSur": "Survival",
|
"SW5E.SkillSur": "Survival",
|
||||||
"SW5E.SkillTec": "Technology",
|
"SW5E.SkillTec": "Technology",
|
||||||
|
"SW5E.SkillPromptTitle": "{skill} Skill Check",
|
||||||
"SW5E.Slots": "Slots",
|
"SW5E.Slots": "Slots",
|
||||||
"SW5E.Source": "Source",
|
"SW5E.Source": "Source",
|
||||||
"SW5E.Special": "Special",
|
"SW5E.Special": "Special",
|
||||||
|
@ -511,7 +614,8 @@
|
||||||
"SW5E.PowerLevel7": "7th Level",
|
"SW5E.PowerLevel7": "7th Level",
|
||||||
"SW5E.PowerLevel8": "8th Level",
|
"SW5E.PowerLevel8": "8th Level",
|
||||||
"SW5E.PowerLevel9": "9th Level",
|
"SW5E.PowerLevel9": "9th Level",
|
||||||
"SW5E.PowerLevelPact": "Pact Slot",
|
"SW5E.PowerLevelSlot": "{level} ({n} Slots)",
|
||||||
|
"SW5E.PowerLevelPact": "Pact Slot [Level {level}] ({n} Slots)",
|
||||||
"SW5E.PowerMaterials": "Powercasting Materials",
|
"SW5E.PowerMaterials": "Powercasting Materials",
|
||||||
"SW5E.PowerName": "Power Name",
|
"SW5E.PowerName": "Power Name",
|
||||||
"SW5E.PowerNone": "None",
|
"SW5E.PowerNone": "None",
|
||||||
|
@ -532,6 +636,7 @@
|
||||||
"SW5E.Powerbook": "Powerbook",
|
"SW5E.Powerbook": "Powerbook",
|
||||||
"SW5E.SpeciesDescription": "Description",
|
"SW5E.SpeciesDescription": "Description",
|
||||||
"SW5E.SpeciesTraits": "Species Traits",
|
"SW5E.SpeciesTraits": "Species Traits",
|
||||||
|
"SW5E.StealthDisadvantage": "Stealth Disadvantage",
|
||||||
"SW5E.SubclassName": "Subclass Name",
|
"SW5E.SubclassName": "Subclass Name",
|
||||||
"SW5E.Supply": "Supply",
|
"SW5E.Supply": "Supply",
|
||||||
"SW5E.Target": "Target",
|
"SW5E.Target": "Target",
|
||||||
|
@ -552,6 +657,7 @@
|
||||||
"SW5E.TargetWall": "Wall",
|
"SW5E.TargetWall": "Wall",
|
||||||
"SW5E.TargetWeapon": "Weapon",
|
"SW5E.TargetWeapon": "Weapon",
|
||||||
"SW5E.Temp": "Temp",
|
"SW5E.Temp": "Temp",
|
||||||
|
"SW5E.Threshold": "Threshold",
|
||||||
"SW5E.TimeDay": "Days",
|
"SW5E.TimeDay": "Days",
|
||||||
"SW5E.TimeHour": "Hours",
|
"SW5E.TimeHour": "Hours",
|
||||||
"SW5E.TimeInst": "Instantaneous",
|
"SW5E.TimeInst": "Instantaneous",
|
||||||
|
@ -567,6 +673,7 @@
|
||||||
"SW5E.ToolArtist": "Artist's Tools",
|
"SW5E.ToolArtist": "Artist's Tools",
|
||||||
"SW5E.ToolAstrotech": "Astrotech's Tools",
|
"SW5E.ToolAstrotech": "Astrotech's Tools",
|
||||||
"SW5E.ToolBiotech": "Biotech's Tools",
|
"SW5E.ToolBiotech": "Biotech's Tools",
|
||||||
|
"SW5E.ToolCheck": "Tool Check",
|
||||||
"SW5E.ToolConstructor": "Constructor's Tools",
|
"SW5E.ToolConstructor": "Constructor's Tools",
|
||||||
"SW5E.ToolCybertech": "Cybertech's Tools",
|
"SW5E.ToolCybertech": "Cybertech's Tools",
|
||||||
"SW5E.ToolJeweler": "Jeweler's Tools",
|
"SW5E.ToolJeweler": "Jeweler's Tools",
|
||||||
|
@ -593,6 +700,7 @@
|
||||||
"SW5E.ToolMusicalInstrument": "Musical Instrument",
|
"SW5E.ToolMusicalInstrument": "Musical Instrument",
|
||||||
"SW5E.ToolVehicle": "Vehicle (Land or Water)",
|
"SW5E.ToolVehicle": "Vehicle (Land or Water)",
|
||||||
"SW5E.TraitArmorProf": "Armor Proficiencies",
|
"SW5E.TraitArmorProf": "Armor Proficiencies",
|
||||||
|
"SW5E.TraitSave": "Update",
|
||||||
"SW5E.TraitSelectorSpecial": "Special (Split with Semi-Colon)",
|
"SW5E.TraitSelectorSpecial": "Special (Split with Semi-Colon)",
|
||||||
"SW5E.TraitToolProf": "Tool Proficiencies",
|
"SW5E.TraitToolProf": "Tool Proficiencies",
|
||||||
"SW5E.TraitWeaponProf": "Weapon Proficiencies",
|
"SW5E.TraitWeaponProf": "Weapon Proficiencies",
|
||||||
|
@ -602,6 +710,20 @@
|
||||||
"SW5E.Usage": "Usage",
|
"SW5E.Usage": "Usage",
|
||||||
"SW5E.Use": "Use",
|
"SW5E.Use": "Use",
|
||||||
"SW5E.Uses": "Uses",
|
"SW5E.Uses": "Uses",
|
||||||
|
"SW5E.Vehicle": "Vehicle",
|
||||||
|
"SW5E.VehicleActionStations": "Action Stations",
|
||||||
|
"SW5E.VehicleActionThresholds": "Action Thresholds",
|
||||||
|
"SW5E.VehicleCargo": "Cargo",
|
||||||
|
"SW5E.VehicleCargoCapacity": "Cargo Capacity",
|
||||||
|
"SW5E.VehicleCargoCrew": "Cargo & Crew",
|
||||||
|
"SW5E.VehicleCreatureCapacity": "Creature Capacity",
|
||||||
|
"SW5E.VehicleCrew": "Crew",
|
||||||
|
"SW5E.VehicleCrewed": "Crewed",
|
||||||
|
"SW5E.VehicleCrewAction": "Crew Action",
|
||||||
|
"SW5E.VehicleEquipment": "Vehicle Equipment",
|
||||||
|
"SW5E.VehicleMishap": "Mishap",
|
||||||
|
"SW5E.VehiclePassengers": "Passengers",
|
||||||
|
"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.",
|
||||||
|
@ -648,7 +770,7 @@
|
||||||
"SW5E.WeaponSimpleLW": "Simple Lightweapon",
|
"SW5E.WeaponSimpleLW": "Simple Lightweapon",
|
||||||
"SW5E.Weight": "Weight",
|
"SW5E.Weight": "Weight",
|
||||||
"SW5E.available": "available",
|
"SW5E.available": "available",
|
||||||
"SW5E.description": "A comprehensive game system for running games of Dungeons & Dragons 5th Edition in the Foundry VTT environment.",
|
"SW5E.description": "A comprehensive game system for running games of Star Wars 5th Edition in the Foundry VTT environment.",
|
||||||
"SW5E.of": "of",
|
"SW5E.of": "of",
|
||||||
"SW5E.power": "power",
|
"SW5E.power": "power",
|
||||||
"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.",
|
"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.",
|
||||||
|
@ -659,12 +781,19 @@
|
||||||
"SETTINGS.5eAutoPowerTemplateN": "Always place Power Template",
|
"SETTINGS.5eAutoPowerTemplateN": "Always place Power Template",
|
||||||
"SETTINGS.5eCurWtL": "Carried currency affects character encumbrance following the rules on PHB pg. 143.",
|
"SETTINGS.5eCurWtL": "Carried currency affects character encumbrance following the rules on PHB pg. 143.",
|
||||||
"SETTINGS.5eCurWtN": "Apply Currency Weight",
|
"SETTINGS.5eCurWtN": "Apply Currency Weight",
|
||||||
"SETTINGS.5eDiagDMG": "Dungeon Master's Guide (5/10/5)",
|
|
||||||
|
"SETTINGS.5eDiagEuclidean": "Euclidean (7.07 ft. Diagonal)",
|
||||||
"SETTINGS.5eDiagL": "Configure which diagonal movement rule should be used for games within this system.",
|
"SETTINGS.5eDiagL": "Configure which diagonal movement rule should be used for games within this system.",
|
||||||
"SETTINGS.5eDiagN": "Diagonal Movement Rule",
|
"SETTINGS.5eDiagN": "Diagonal Movement Rule",
|
||||||
"SETTINGS.5eDiagPHB": "Player's Handbook (5/5/5)",
|
"SETTINGS.5eDiagPHB": "PHB: Equidistant (5/5/5)",
|
||||||
|
"SETTINGS.5eDiagDMG": "DMG: Alternating (5/10/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.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.5eRestL": "Configure which rest variant should be used for games within this system.",
|
||||||
|
"SETTINGS.5eRestN": "Rest Variant",
|
||||||
|
"SETTINGS.5eRestPHB": "Player's Handbook (LR: 8 hours, SR: 1 hour)",
|
||||||
|
"SETTINGS.5eRestGritty": "Gritty Realism (LR: 7 days, SR: 8 hours)",
|
||||||
|
"SETTINGS.5eRestEpic": "Epic Heroism (LR: 1 hour, SR: 1 min)"
|
||||||
}
|
}
|
||||||
|
|
349
less/actors.less
349
less/actors.less
|
@ -1,58 +1,73 @@
|
||||||
@import "./variables.less";
|
@import "./variables.less";
|
||||||
|
|
||||||
|
@actorNameHeight: 60px;
|
||||||
|
|
||||||
.sw5e.sheet.actor {
|
.sw5e.sheet.actor {
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Sheet Header */
|
/* Sheet Header */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.sheet-header {
|
.sheet-header {
|
||||||
.charlevel {
|
|
||||||
|
// Portrait Artwork
|
||||||
|
img.profile {
|
||||||
flex: 0 0 160px;
|
flex: 0 0 160px;
|
||||||
height: 60px;
|
max-width: 160px;
|
||||||
margin: 0;
|
height: 160px;
|
||||||
padding: 5px;
|
}
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
.level {
|
|
||||||
width: 100%;
|
|
||||||
height: 30px;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 30px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: inline;
|
|
||||||
font-size: 24px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: inline;
|
|
||||||
width: 36px;
|
|
||||||
font-size: 24px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the Experience Bar is disabled
|
|
||||||
&.noxp {
|
|
||||||
margin-top: 10px;
|
|
||||||
> label {
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
> input {
|
|
||||||
font-size: 32px;
|
|
||||||
flex: 0 0 40px;
|
|
||||||
height: 44px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Character Name
|
||||||
|
h1.charname {
|
||||||
|
flex: 1;
|
||||||
|
height: @actorNameHeight;
|
||||||
|
padding: 0;
|
||||||
|
input {
|
||||||
|
height: @actorNameHeight - 20px;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.experience {
|
// Character Level
|
||||||
width: 100%;
|
.header-exp {
|
||||||
height: 20px;
|
flex: 0 0 150px;
|
||||||
padding-right: 5px;
|
margin-right: 3px;
|
||||||
font-size: 16px;
|
height: @actorNameHeight;
|
||||||
color: @colorOlive;
|
justify-content: flex-end;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character Summary
|
||||||
|
.summary {
|
||||||
|
height: 30px;
|
||||||
|
border-bottom: @borderGroove;
|
||||||
|
font-size: 18px;
|
||||||
|
input, span {
|
||||||
|
display: block;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary Attributes
|
||||||
|
.attributes {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.attribute {
|
||||||
|
height: 70px;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
border-right: @borderGroove;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-value {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,55 +84,55 @@
|
||||||
|
|
||||||
// Box Headers
|
// Box Headers
|
||||||
h4.box-title {
|
h4.box-title {
|
||||||
|
height: 18px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
margin: 4px 8px 2px;
|
margin: 4px 8px 2px;
|
||||||
|
.modesto();
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
border-bottom: 1px solid @colorBeige;
|
color: @colorOlive;
|
||||||
|
border-bottom: 1px solid @colorFaint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Attributes */
|
/* Attributes */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
|
.tab.attributes {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
ul.attributes {
|
ul.attributes {
|
||||||
|
flex: 0 0 60px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 5px 0 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li.attribute {
|
li.attribute {
|
||||||
height: 70px;
|
height: 60px;
|
||||||
margin: 0 5px;
|
margin: 0 5px 0 0;
|
||||||
border: @borderGroove;
|
border: @borderGroove;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
.nodesto();
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.attribute-name {
|
&:last-child {
|
||||||
flex: 0 0 18px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attribute-value {
|
.attribute-value {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
|
.modesto();
|
||||||
|
|
||||||
input {
|
> * {
|
||||||
display: inline;
|
font-weight: 400;
|
||||||
max-width: 80%;
|
|
||||||
height: 28px;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.sep {
|
&.multiple input {
|
||||||
display: inline;
|
flex: 0 0 33%;
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
font-size: 28px;
|
|
||||||
color: @colorTan;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.multiple {
|
|
||||||
input { max-width: 33%; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,8 +140,9 @@
|
||||||
flex: 0 0 18px;
|
flex: 0 0 18px;
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
font-family: "Signika", "Palatino Linotype", serif;
|
font-family: "Signika", sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,22 +152,28 @@
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.ability-scores {
|
.ability-scores {
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100px;
|
||||||
|
height: 440px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
.nodesto();
|
.modesto();
|
||||||
|
border: @borderGroove;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
.ability {
|
.ability {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
margin: 0 5px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: @borderGroove;
|
border-bottom: @borderGroove;
|
||||||
border-radius: 3px;
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
input.ability-score {
|
input.ability-score {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 50px;
|
width: 36px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
@ -159,12 +181,14 @@
|
||||||
|
|
||||||
.ability-modifiers {
|
.ability-modifiers {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin: -10px 0 0;
|
margin: -8px 0 0;
|
||||||
|
|
||||||
span.ability-mod,
|
span.ability-mod,
|
||||||
span.ability-save {
|
span.ability-save {
|
||||||
flex: 0 0 24px;
|
flex: 0 0 24px;
|
||||||
height: 24px;
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
font-size: 16px;
|
||||||
border-top: @borderGroove;
|
border-top: @borderGroove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,12 +204,6 @@
|
||||||
border-left: @borderGroove;
|
border-left: @borderGroove;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide modifier box on hover */
|
|
||||||
|
|
||||||
input.ability-score:hover + .ability-modifiers {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,16 +227,21 @@
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
ul.skills-list {
|
ul.skills-list {
|
||||||
flex: 0 0 192px;
|
flex: 0 0 180px;
|
||||||
|
height: 440px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 5px 5px 0;
|
margin: 0 5px 0;
|
||||||
padding: 2px 2px 0;
|
padding: 3px 0 2px;
|
||||||
border: @borderGroove;
|
border: @borderGroove;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
li.skill {
|
li.skill {
|
||||||
height: 22px;
|
height: 24px;
|
||||||
padding: 3px 0;
|
padding: 3px 2px;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
flex: 1px;
|
flex: 1px;
|
||||||
|
@ -234,6 +257,7 @@
|
||||||
|
|
||||||
.skill-ability {
|
.skill-ability {
|
||||||
flex: 0 0 26px;
|
flex: 0 0 26px;
|
||||||
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skill-mod {
|
.skill-mod {
|
||||||
|
@ -253,23 +277,25 @@
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.counters {
|
.counters {
|
||||||
flex: 0 0 100%;
|
flex: none;
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: 0;
|
||||||
border-bottom: @borderGroove;
|
border-bottom: @borderGroove;
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
.counter {
|
.counter {
|
||||||
padding: 0 3px;
|
height: 20px;
|
||||||
line-height: 32px;
|
line-height: 20px;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
.nodesto();
|
font-size: 13px;
|
||||||
font-size: 14px;
|
font-weight: bold;
|
||||||
|
color: @colorOlive;
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-value {
|
.counter-value {
|
||||||
flex: 0 0 50px;
|
flex: none;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
> * {
|
> * {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
@ -286,12 +312,13 @@
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
top: 6px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.sep {
|
span.sep {
|
||||||
margin: 0 -2px;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,28 +328,31 @@
|
||||||
/* Traits */
|
/* Traits */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.traits {
|
.center-pane {
|
||||||
margin: 0 5px;
|
height: 100%;
|
||||||
|
padding: 0 5px 0 3px;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traits {
|
||||||
.form-group, .form-group-stacked {
|
.form-group, .form-group-stacked {
|
||||||
margin: 0 0 4px 0;
|
margin: 0 0 3px 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.configure-flags {
|
.configure-flags {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.actor-size {
|
|
||||||
flex: 0 0 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
flex: 0 0 150px;
|
flex: none;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0;
|
margin: 0 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
max-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
@ -360,18 +390,10 @@
|
||||||
.inventory-filters {
|
.inventory-filters {
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
flex: 0 0 20px;
|
flex: 0 0 20px;
|
||||||
h3, .filter-title {
|
justify-content: flex-end;
|
||||||
.nodesto();
|
|
||||||
color: @colorOlive;
|
|
||||||
font-size: 18px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.powerbook-filters {
|
|
||||||
flex: 0 0 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.currency {
|
.currency {
|
||||||
|
flex: 0 0 100%;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 4px 0 8px;
|
margin: 4px 0 8px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -398,6 +420,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
// Inventory Item
|
// Inventory Item
|
||||||
.item {
|
.item {
|
||||||
|
@ -424,12 +447,12 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.rollable .item-image:hover {
|
|
||||||
background-image: url("/icons/svg/d20-black.svg") !important;
|
|
||||||
}
|
|
||||||
&.rollable:hover .item-image {
|
&.rollable:hover .item-image {
|
||||||
background-image: url("/icons/svg/d20-grey.svg") !important;
|
background-image: url("/icons/svg/d20-grey.svg") !important;
|
||||||
}
|
}
|
||||||
|
&.rollable .item-image:hover {
|
||||||
|
background-image: url("/icons/svg/d20-black.svg") !important;
|
||||||
|
}
|
||||||
|
|
||||||
i.attuned {
|
i.attuned {
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
|
@ -453,6 +476,7 @@
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,8 +492,8 @@
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 -5px 0 0;
|
margin: 0 -5px 0 0;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
font-size: 13px;
|
.modesto();
|
||||||
font-weight: bold;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-controls a.item-create {
|
.item-controls a.item-create {
|
||||||
|
@ -484,6 +508,10 @@
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-right: 1px solid @colorFaint;
|
border-right: 1px solid @colorFaint;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:last-child { border-right: none; }
|
&:last-child { border-right: none; }
|
||||||
&.item-action {flex: 0 0 100px}
|
&.item-action {flex: 0 0 100px}
|
||||||
}
|
}
|
||||||
|
@ -524,15 +552,77 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Encumbrance Bar */
|
||||||
|
.encumbrance {
|
||||||
|
flex: 0 0 12px;
|
||||||
|
background: @colorTan;
|
||||||
|
margin: 1px 15px 0 1px;
|
||||||
|
border: 1px solid @colorDark;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.encumbrance-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
background: #6c8aa5;
|
||||||
|
height: 8px;
|
||||||
|
border: 1px solid #cde4ff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.encumbrance-label {
|
||||||
|
height: 10px;
|
||||||
|
padding: 0 5px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 12px;
|
||||||
|
text-align: right;
|
||||||
|
color: #EEE;
|
||||||
|
text-shadow: 0 0 5px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.encumbrance-breakpoint {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
&.encumbrance-33 { left: 33% }
|
||||||
|
&.encumbrance-66 { left: 66% }
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-up {
|
||||||
|
bottom: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-bottom: 4px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-down {
|
||||||
|
top: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
border-top: 4px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.encumbered {
|
||||||
|
.arrow-up { border-bottom: 4px solid #000; }
|
||||||
|
.arrow-down { border-top: 4px solid #000; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Powerbook */
|
/* Powerbook */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.powercasting-ability {
|
.powercasting-ability {
|
||||||
h3 {
|
flex: 0 0 240px;
|
||||||
flex: none;
|
margin: 0;
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
input, span {
|
input, span {
|
||||||
flex: 0 0 32px;
|
flex: 0 0 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -549,7 +639,7 @@
|
||||||
|
|
||||||
.power-slots,
|
.power-slots,
|
||||||
.power-comps {
|
.power-comps {
|
||||||
flex: 0 0 72px;
|
flex: 0 0 75px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -558,10 +648,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.power-slots {
|
.power-slots {
|
||||||
input, span.sep {
|
input {
|
||||||
display: inline;
|
display: inline;
|
||||||
max-width: 20px;
|
max-width: 20px;
|
||||||
text-align: center;
|
}
|
||||||
|
.sep {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
@detailsHeight: 40px;
|
@detailsHeight: 40px;
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* All SW5e Apps */
|
/* All DnD5e Apps */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.sw5e {
|
.sw5e {
|
||||||
.window-content {
|
.window-content {
|
||||||
background: @sheetBackground;
|
background: @sheetBackground;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: @colorDark
|
color: @colorDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
@ -56,20 +56,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkbox Labels
|
// Checkbox Labels
|
||||||
|
// TODO: THIS CAN BE MOSTLY REMOVED NOW THAT IT IS IN CORE, see core forms.less
|
||||||
label.checkbox {
|
label.checkbox {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 20px;
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: right;
|
> input[type="checkbox"] {
|
||||||
input[type="checkbox"] {
|
width: 16px;
|
||||||
height: auto;
|
height: 16px;
|
||||||
margin: 0 5px 0;
|
margin: 0 2px 0 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 4px;
|
||||||
|
}
|
||||||
|
&.right > input[type="checkbox"] {
|
||||||
|
margin: 0 0 0 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Form Groups */
|
/* Form Groups */
|
||||||
.form-group {
|
.form-group {
|
||||||
label {
|
label {
|
||||||
|
@ -101,11 +108,10 @@
|
||||||
// Form Headers
|
// Form Headers
|
||||||
.form-header {
|
.form-header {
|
||||||
margin: 0 0 0.25em 0;
|
margin: 0 0 0.25em 0;
|
||||||
padding: 0 2px;
|
padding: 2px 0;
|
||||||
.nodesto();
|
|
||||||
font-size: 24px;
|
|
||||||
border-top: @borderGroove;
|
border-top: @borderGroove;
|
||||||
border-bottom: @borderGroove;
|
border-bottom: @borderGroove;
|
||||||
|
.modesto();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tags */
|
/* Tags */
|
||||||
|
@ -175,6 +181,16 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Separators
|
||||||
|
span.sep {
|
||||||
|
flex: none;
|
||||||
|
margin: 0 1px;
|
||||||
|
display: inline;
|
||||||
|
position: relative;
|
||||||
|
color: @colorTan;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* TinyMCE */
|
/* TinyMCE */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
@ -190,10 +206,14 @@
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Sheet Header */
|
/* Sheet Header */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.sheet-header {
|
.sheet-header {
|
||||||
flex: 0 0 @headerHeight;
|
flex: 0 0 @headerHeight;
|
||||||
border-bottom: @borderGroove;
|
border-bottom: @borderGroove;
|
||||||
.nodesto();
|
|
||||||
|
.header-details {
|
||||||
|
.modesto();
|
||||||
|
}
|
||||||
|
|
||||||
/* Character Name */
|
/* Character Name */
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -204,8 +224,10 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
display: block;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
font-size: 36px;
|
font-size: 32px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +236,7 @@
|
||||||
flex: 0 0 @headerHeight;
|
flex: 0 0 @headerHeight;
|
||||||
max-width: @headerHeight;
|
max-width: @headerHeight;
|
||||||
height: @headerHeight;
|
height: @headerHeight;
|
||||||
object-fit: cover;
|
object-fit: contain;
|
||||||
border: none;
|
border: none;
|
||||||
border-right: @borderGroove;
|
border-right: @borderGroove;
|
||||||
}
|
}
|
||||||
|
@ -230,11 +252,10 @@
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
width: 33.33%;
|
height: calc(100% - 6px);
|
||||||
float: left;
|
float: left;
|
||||||
height: @detailsHeight - 6px;
|
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
padding: 0 3px;
|
padding: 0;
|
||||||
border-right: @borderGroove;
|
border-right: @borderGroove;
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
color: @colorOlive;
|
color: @colorOlive;
|
||||||
|
@ -253,7 +274,7 @@
|
||||||
.sheet-navigation {
|
.sheet-navigation {
|
||||||
flex: 0 0 @navHeight;
|
flex: 0 0 @navHeight;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
.nodesto();
|
.modesto();
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
@ -285,18 +306,25 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
|
max-width: 70%;
|
||||||
|
|
||||||
.filter-title {
|
.filter-icon {
|
||||||
flex: 3;
|
flex: none;
|
||||||
|
font-size: 14px;
|
||||||
|
color: @colorTan;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item {
|
.filter-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin: 0 6px;
|
margin: 0 6px 0 0;
|
||||||
border-bottom: 3px solid @colorBeige;
|
border-bottom: 3px solid @colorBeige;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-shadow: 0 0 4px red;
|
text-shadow: 0 0 4px red;
|
||||||
border-bottom: 3px solid @colorTan;
|
border-bottom: 3px solid @colorTan;
|
||||||
|
@ -312,19 +340,23 @@
|
||||||
/* Trait Lists */
|
/* Trait Lists */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.trait-selector {
|
.traits {
|
||||||
flex: 0 0 16px;
|
margin: 5px 0 0;
|
||||||
padding: 2px 0;
|
|
||||||
color: #999;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.traits-list {
|
.trait-selector {
|
||||||
line-height: 20px;
|
flex: 0 0 16px;
|
||||||
list-style: none;
|
padding: 2px 0;
|
||||||
margin: 0;
|
color: #999;
|
||||||
padding: 0;
|
font-size: 10px;
|
||||||
text-align: right;
|
}
|
||||||
|
|
||||||
|
.traits-list {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
line-height: 20px;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
/* Basic Structure */
|
/* Basic Structure */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
.sw5e.sheet.actor.character {
|
.sw5e.sheet.actor.character {
|
||||||
min-width: 680px;
|
min-width: 720px;
|
||||||
min-height: 736px;
|
min-height: 680px;
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Sheet Header */
|
/* Sheet Header */
|
||||||
|
@ -13,93 +13,73 @@
|
||||||
|
|
||||||
.sheet-header {
|
.sheet-header {
|
||||||
|
|
||||||
// Character Profile image (larger than usual)
|
// Character Profile image (larger)
|
||||||
img.profile {
|
img.profile {
|
||||||
flex: 0 0 180px;
|
flex: 0 0 160px;
|
||||||
max-width: 180px;
|
max-width: 160px;
|
||||||
height: 180px;
|
height: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Character level and experience bar
|
// Character Level
|
||||||
.charlevel {
|
.charlevel {
|
||||||
flex: 0 0 180px;
|
flex: 0 0 20px;
|
||||||
padding: 0 5px 2px;
|
height: 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: @colorTan;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.level {
|
// Experience Tracking
|
||||||
height: 28px;
|
.experience {
|
||||||
|
flex: 0 0 32px;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
span.max {
|
||||||
|
color: @colorTan;
|
||||||
|
flex: none;
|
||||||
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.experience {
|
.xpbar {
|
||||||
input[type="text"] {
|
flex: 0 0 8px;
|
||||||
width: 100px;
|
width: 100%;
|
||||||
}
|
margin-bottom: 5px;
|
||||||
}
|
background: @colorTan;
|
||||||
|
border: 1px solid #000;
|
||||||
.xpbar {
|
border-radius: 3px;
|
||||||
width: 100%;
|
.bar {
|
||||||
flex: 0 0 8px;
|
height: 4px;
|
||||||
background: #666;
|
margin: 1px;
|
||||||
|
display: block;
|
||||||
|
background: #afebff;
|
||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
border-radius: 3px;
|
border-radius: 2px;
|
||||||
|
|
||||||
.bar {
|
|
||||||
height: 4px;
|
|
||||||
margin: 1px;
|
|
||||||
display: block;
|
|
||||||
background: #afebff;
|
|
||||||
border: 1px solid #000;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Character Summary
|
// Header Attributes
|
||||||
.summary {
|
|
||||||
border-bottom: @borderGroove;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Primary Attributes
|
|
||||||
.attributes {
|
.attributes {
|
||||||
height: 80px;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
.attribute {
|
|
||||||
height: 80px;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
border-right: @borderGroove;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-value {
|
|
||||||
margin: 5px 0 0;
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-name {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-footer {
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.rest {
|
a.rest {
|
||||||
border: 1px solid @colorBeige;
|
border: 1px solid @colorBeige;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: rgba(0, 0, 0, 0.05);
|
background: rgba(0, 0, 0, 0.05);
|
||||||
padding: 1px 3px;
|
padding: 0 3px;
|
||||||
margin: 0 6px;
|
margin: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hit-dice {
|
.hit-dice {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.initiative .attribute-footer input {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary .proficiency {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,26 +87,24 @@
|
||||||
/* Sheet Body */
|
/* Sheet Body */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.attributes {
|
// Custom Resources
|
||||||
.resource {
|
.resource .attribute-value {
|
||||||
.attribute-name {
|
input {
|
||||||
margin: 0 8px;
|
flex: 0 0 25%;
|
||||||
input[type="text"] {
|
|
||||||
height: 20px;
|
|
||||||
margin: 2px 0 -2px;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label.checkbox {
|
|
||||||
margin: 0 3px;
|
|
||||||
input[type="checkbox"] {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
label.recharge {
|
||||||
.initiative .attribute-footer input {
|
height: 32px;
|
||||||
width: 32px;
|
position: relative;
|
||||||
|
font-family: "Signika", sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
color: @colorOlive;
|
||||||
|
input[type="checkbox"] {
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
margin: 0;
|
||||||
|
top: -6px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,88 +121,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.counters {
|
|
||||||
.death-saves {
|
|
||||||
flex: 2;
|
|
||||||
.counter-value {
|
|
||||||
flex: 0 0 90px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-detail.player-class {
|
.item-detail.player-class {
|
||||||
flex: 0 0 180px;
|
flex: 0 0 180px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
|
||||||
/* Inventory */
|
|
||||||
/* ----------------------------------------- */
|
|
||||||
|
|
||||||
/* Encumbrance Bar */
|
|
||||||
.encumbrance {
|
|
||||||
flex: 0 0 12px;
|
|
||||||
background: @colorTan;
|
|
||||||
margin: 1px 15px 0 1px;
|
|
||||||
border: 1px solid @colorDark;
|
|
||||||
border-radius: 3px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.encumbrance-bar {
|
|
||||||
position: absolute;
|
|
||||||
top: 1px;
|
|
||||||
left: 1px;
|
|
||||||
background: #6c8aa5;
|
|
||||||
height: 8px;
|
|
||||||
border: 1px solid #cde4ff;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.encumbrance-label {
|
|
||||||
height: 10px;
|
|
||||||
padding: 0 5px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 12px;
|
|
||||||
text-align: right;
|
|
||||||
color: #EEE;
|
|
||||||
text-shadow: 0 0 5px #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.encumbrance-breakpoint {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
&.encumbrance-33 { left: 33% }
|
|
||||||
&.encumbrance-66 { left: 66% }
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-up {
|
|
||||||
bottom: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-bottom: 4px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-down {
|
|
||||||
top: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 4px solid transparent;
|
|
||||||
border-right: 4px solid transparent;
|
|
||||||
border-top: 4px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.encumbered {
|
|
||||||
.arrow-up { border-bottom: 4px solid #000; }
|
|
||||||
.arrow-down { border-top: 4px solid #000; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Item Controls */
|
/* Item Controls */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
.nodesto();
|
.bungeeInline();
|
||||||
color: @colorOlive;
|
color: @colorOlive;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #111;
|
color: #111;
|
||||||
|
|
|
@ -192,7 +192,26 @@
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recharge.form-group {
|
||||||
|
span {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 3px;
|
||||||
|
}
|
||||||
|
input[type="text"] {
|
||||||
|
flex: 0 0 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
label.checkbox {
|
||||||
|
flex: none;
|
||||||
|
input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h4.armorproperties-header {
|
h4.armorproperties-header {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -263,16 +282,6 @@
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recharge {
|
|
||||||
span {
|
|
||||||
flex: 0 0 80px;
|
|
||||||
}
|
|
||||||
label.checkbox {
|
|
||||||
flex: 0 0 80px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Item Actions */
|
/* Item Actions */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
|
@ -5,5 +5,37 @@
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
.sw5e.sheet.actor.npc {
|
.sw5e.sheet.actor.npc {
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
min-height: 658px;
|
min-height: 680px;
|
||||||
|
|
||||||
|
.header-exp {
|
||||||
|
flex: 0 0 80px;
|
||||||
|
justify-content: center;
|
||||||
|
.cr {
|
||||||
|
flex: 0 0 32px;
|
||||||
|
line-height: 28px;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
font-size: 24px;
|
||||||
|
input {
|
||||||
|
width: 32px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.experience {
|
||||||
|
flex: 0 0 18px;
|
||||||
|
color: @colorTan;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powercasting-ability {
|
||||||
|
label {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -5,3 +5,4 @@
|
||||||
@import "chat.less";
|
@import "chat.less";
|
||||||
@import "character.less";
|
@import "character.less";
|
||||||
@import "npc.less";
|
@import "npc.less";
|
||||||
|
@import "vehicle.less";
|
||||||
|
|
|
@ -7,13 +7,41 @@
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
|
/* russo-one-regular - latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Nodesto";
|
font-family: 'Russo One';
|
||||||
src: url("fonts/NodestoCapsCondensed.otf");
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./fonts/RussoOne.ttf');
|
||||||
}
|
}
|
||||||
.nodesto {
|
.russoOne {
|
||||||
font-family: "Nodesto", "Signika", "Palatino Linotype", serif;
|
font-family: 'Russo One';
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
/* bungee-inline-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Bungee Inline';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./fonts/BungeeInline.ttf');
|
||||||
|
}
|
||||||
|
.bungeeInline {
|
||||||
|
font-family: 'Bungee Inline';
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
/* open-sans-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./fonts/OpenSans-Regular.ttf');
|
||||||
|
}
|
||||||
|
.openSans {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
32
less/vehicle.less
Normal file
32
less/vehicle.less
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
.sw5e.sheet.actor.vehicle {
|
||||||
|
.features {
|
||||||
|
.item-controls {
|
||||||
|
flex: 0 0 68px;
|
||||||
|
.item-toggle {
|
||||||
|
color: #b5b3a4;
|
||||||
|
&.active {
|
||||||
|
color: #4b4a44;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.counters {
|
||||||
|
.counter.creature-cap {
|
||||||
|
.counter-value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
max-width: none;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter.cargo-cap {
|
||||||
|
input {
|
||||||
|
max-width: 40px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,14 @@
|
||||||
import {TraitSelector} from "../../apps/trait-selector.js";
|
import Item5e from "../../item/entity.js";
|
||||||
import {ActorSheetFlags} from "../../apps/actor-flags.js";
|
import TraitSelector from "../../apps/trait-selector.js";
|
||||||
|
import ActorSheetFlags from "../../apps/actor-flags.js";
|
||||||
import {SW5E} from '../../config.js';
|
import {SW5E} from '../../config.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the basic ActorSheet class to do all the SW5e things!
|
* Extend the basic ActorSheet class to do all the SW5e things!
|
||||||
* This sheet is an Abstract layer which is not used.
|
* This sheet is an Abstract layer which is not used.
|
||||||
*
|
* @extends {ActorSheet}
|
||||||
* @type {ActorSheet}
|
|
||||||
*/
|
*/
|
||||||
export class ActorSheet5e extends ActorSheet {
|
export default class ActorSheet5e extends ActorSheet {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
|
|
||||||
|
@ -37,6 +37,13 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
get template() {
|
||||||
|
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/limited-sheet.html";
|
||||||
|
return `systems/sw5e/templates/actors/${this.actor.data.type}-sheet.html`;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
@ -53,6 +60,7 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
cssClass: isOwner ? "editable" : "locked",
|
cssClass: isOwner ? "editable" : "locked",
|
||||||
isCharacter: this.entity.data.type === "character",
|
isCharacter: this.entity.data.type === "character",
|
||||||
isNPC: this.entity.data.type === "npc",
|
isNPC: this.entity.data.type === "npc",
|
||||||
|
isVehicle: this.entity.data.type === 'vehicle',
|
||||||
config: CONFIG.SW5E,
|
config: CONFIG.SW5E,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,11 +83,13 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update skill labels
|
// Update skill labels
|
||||||
for ( let [s, skl] of Object.entries(data.actor.data.skills)) {
|
if (data.actor.data.skills) {
|
||||||
skl.ability = data.actor.data.abilities[skl.ability].label.substring(0, 3);
|
for ( let [s, skl] of Object.entries(data.actor.data.skills)) {
|
||||||
skl.icon = this._getProficiencyIcon(skl.value);
|
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
skl.icon = this._getProficiencyIcon(skl.value);
|
||||||
skl.label = CONFIG.SW5E.skills[s];
|
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||||
|
skl.label = CONFIG.SW5E.skills[s];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update traits
|
// Update traits
|
||||||
|
@ -96,9 +106,9 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
_prepareTraits(traits) {
|
_prepareTraits(traits) {
|
||||||
const map = {
|
const map = {
|
||||||
"dr": CONFIG.SW5E.damageTypes,
|
"dr": CONFIG.SW5E.damageResistanceTypes,
|
||||||
"di": CONFIG.SW5E.damageTypes,
|
"di": CONFIG.SW5E.damageResistanceTypes,
|
||||||
"dv": CONFIG.SW5E.damageTypes,
|
"dv": CONFIG.SW5E.damageResistanceTypes,
|
||||||
"ci": CONFIG.SW5E.conditionTypes,
|
"ci": CONFIG.SW5E.conditionTypes,
|
||||||
"languages": CONFIG.SW5E.languages,
|
"languages": CONFIG.SW5E.languages,
|
||||||
"armorProf": CONFIG.SW5E.armorProficiencies,
|
"armorProf": CONFIG.SW5E.armorProficiencies,
|
||||||
|
@ -200,7 +210,7 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
if ( mode in sections ) {
|
if ( mode in sections ) {
|
||||||
s = sections[mode];
|
s = sections[mode];
|
||||||
if ( !powerbook[s] ){
|
if ( !powerbook[s] ){
|
||||||
registerSection(sl, s, CONFIG.SW5E.powerPreparationModes[mode], levels[mode]);
|
registerSection(mode, s, CONFIG.SW5E.powerPreparationModes[mode], levels[mode]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +262,7 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
|
|
||||||
// Equipment-specific filters
|
// Equipment-specific filters
|
||||||
if ( filters.has("equipped") ) {
|
if ( filters.has("equipped") ) {
|
||||||
if (data.equipped && data.equipped !== true) return false;
|
if ( data.equipped !== true ) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -295,8 +305,10 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
// Editable Only Listeners
|
// Editable Only Listeners
|
||||||
if ( this.isEditable ) {
|
if ( this.isEditable ) {
|
||||||
|
|
||||||
// Relative updates for numeric fields
|
// Input focus and update
|
||||||
html.find('input[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
|
const inputs = html.find("input");
|
||||||
|
inputs.focus(ev => ev.currentTarget.select());
|
||||||
|
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
|
||||||
|
|
||||||
// Ability Proficiency
|
// Ability Proficiency
|
||||||
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this));
|
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this));
|
||||||
|
@ -328,14 +340,6 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
// Roll Skill Checks
|
// Roll Skill Checks
|
||||||
html.find('.skill-name').click(this._onRollSkillCheck.bind(this));
|
html.find('.skill-name').click(this._onRollSkillCheck.bind(this));
|
||||||
|
|
||||||
// Item Dragging
|
|
||||||
let handler = ev => this._onDragItemStart(ev);
|
|
||||||
html.find('li.item').each((i, li) => {
|
|
||||||
if ( li.classList.contains("inventory-header") ) return;
|
|
||||||
li.setAttribute("draggable", true);
|
|
||||||
li.addEventListener("dragstart", handler, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Item Rolling
|
// Item Rolling
|
||||||
html.find('.item .item-image').click(event => this._onItemRoll(event));
|
html.find('.item .item-image').click(event => this._onItemRoll(event));
|
||||||
html.find('.item .item-recharge').click(event => this._onItemRecharge(event));
|
html.find('.item .item-recharge').click(event => this._onItemRecharge(event));
|
||||||
|
@ -424,37 +428,9 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _onDrop (event) {
|
async _onDropActor(event, data) {
|
||||||
event.preventDefault();
|
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||||
|
if ( !canPolymorph ) return false;
|
||||||
// Get dropped data
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(event.dataTransfer.getData('text/plain'));
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a polymorph
|
|
||||||
if (data && (data.type === "Actor")) {
|
|
||||||
if (game.user.isGM || (game.settings.get('sw5e', 'allowPolymorphing') && this.actor.owner)) {
|
|
||||||
return this._onDropPolymorph(event, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call parent on drop logic
|
|
||||||
return super._onDrop(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle dropping an Actor on the sheet to trigger a Polymorph workflow
|
|
||||||
* @param {DragEvent} event The drop event
|
|
||||||
* @param {Object} data The data transfer
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _onDropPolymorph(event, data) {
|
|
||||||
|
|
||||||
// Get the target actor
|
// Get the target actor
|
||||||
let sourceActor = null;
|
let sourceActor = null;
|
||||||
|
@ -521,6 +497,30 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
}).render(true);
|
}).render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _onDropItemCreate(itemData) {
|
||||||
|
|
||||||
|
// Create a Consumable power scroll on the Inventory tab
|
||||||
|
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) {
|
||||||
|
const scroll = await Item5e.createScrollFromPower(itemData);
|
||||||
|
itemData = scroll.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade the number of class levels a character has
|
||||||
|
if ( (itemData.type === "class") && ( this.actor.itemTypes.class.find(c => c.name === itemData.name)) ) {
|
||||||
|
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||||
|
const lvl = cls.data.data.levels;
|
||||||
|
return cls.update({"data.levels": Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level)})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the owned item as normal
|
||||||
|
// TODO remove conditional logic in 0.7.x
|
||||||
|
if (isNewerVersion(game.data.version, "0.6.9")) return super._onDropItemCreate(itemData);
|
||||||
|
else return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -634,7 +634,7 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
const header = event.currentTarget;
|
const header = event.currentTarget;
|
||||||
const type = header.dataset.type;
|
const type = header.dataset.type;
|
||||||
const itemData = {
|
const itemData = {
|
||||||
name: `New ${type.capitalize()}`,
|
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||||
type: type,
|
type: type,
|
||||||
data: duplicate(header.dataset)
|
data: duplicate(header.dataset)
|
||||||
};
|
};
|
||||||
|
@ -736,11 +736,8 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const a = event.currentTarget;
|
const a = event.currentTarget;
|
||||||
const label = a.parentElement.querySelector("label");
|
const label = a.parentElement.querySelector("label");
|
||||||
const options = {
|
const choices = CONFIG.SW5E[a.dataset.options];
|
||||||
name: label.getAttribute("for"),
|
const options = { name: a.dataset.target, title: label.innerText, choices };
|
||||||
title: label.innerText,
|
|
||||||
choices: CONFIG.SW5E[a.dataset.options]
|
|
||||||
};
|
|
||||||
new TraitSelector(this.actor, options).render(true)
|
new TraitSelector(this.actor, options).render(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -760,4 +757,90 @@ export class ActorSheet5e extends ActorSheet {
|
||||||
});
|
});
|
||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* DEPRECATED */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Remove once 0.7.x is release
|
||||||
|
* @deprecated since 0.7.0
|
||||||
|
*/
|
||||||
|
async _onDrop (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Get dropped data
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(event.dataTransfer.getData('text/plain'));
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( !data ) return false;
|
||||||
|
|
||||||
|
// Handle the drop with a Hooked function
|
||||||
|
const allowed = Hooks.call("dropActorSheetData", this.actor, this, data);
|
||||||
|
if ( allowed === false ) return;
|
||||||
|
|
||||||
|
// Case 1 - Dropped Item
|
||||||
|
if ( data.type === "Item" ) {
|
||||||
|
return this._onDropItem(event, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2 - Dropped Actor
|
||||||
|
if ( data.type === "Actor" ) {
|
||||||
|
return this._onDropActor(event, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Remove once 0.7.x is release
|
||||||
|
* @deprecated since 0.7.0
|
||||||
|
*/
|
||||||
|
async _onDropItem(event, data) {
|
||||||
|
if ( !this.actor.owner ) return false;
|
||||||
|
let itemData = await this._getItemDropData(event, data);
|
||||||
|
|
||||||
|
// Handle item sorting within the same Actor
|
||||||
|
const actor = this.actor;
|
||||||
|
let sameActor = (data.actorId === actor._id) || (actor.isToken && (data.tokenId === actor.token.id));
|
||||||
|
if (sameActor) return this._onSortItem(event, itemData);
|
||||||
|
|
||||||
|
// Create a new item
|
||||||
|
this._onDropItemCreate(itemData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Remove once 0.7.x is release
|
||||||
|
* @deprecated since 0.7.0
|
||||||
|
*/
|
||||||
|
async _getItemDropData(event, data) {
|
||||||
|
let itemData = null;
|
||||||
|
|
||||||
|
// Case 1 - Import from a Compendium pack
|
||||||
|
if (data.pack) {
|
||||||
|
const pack = game.packs.get(data.pack);
|
||||||
|
if (pack.metadata.entity !== "Item") return;
|
||||||
|
itemData = await pack.getEntry(data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2 - Data explicitly provided
|
||||||
|
else if (data.data) {
|
||||||
|
itemData = data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3 - Import from World entity
|
||||||
|
else {
|
||||||
|
let item = game.items.get(data.id);
|
||||||
|
if (!item) return;
|
||||||
|
itemData = item.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a copy of the extracted data
|
||||||
|
return duplicate(itemData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { ActorSheet5e } from "./base.js";
|
import ActorSheet5e from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Actor sheet for player character type actors in the SW5E system.
|
* An Actor sheet for player character type actors in the SW5E system.
|
||||||
* Extends the base ActorSheet5e class.
|
* Extends the base ActorSheet5e class.
|
||||||
* @type {ActorSheet5e}
|
* @type {ActorSheet5e}
|
||||||
*/
|
*/
|
||||||
export class ActorSheet5eCharacter extends ActorSheet5e {
|
export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define default rendering options for the NPC sheet
|
* Define default rendering options for the NPC sheet
|
||||||
|
@ -14,24 +14,11 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
return mergeObject(super.defaultOptions, {
|
return mergeObject(super.defaultOptions, {
|
||||||
classes: ["sw5e", "sheet", "actor", "character"],
|
classes: ["sw5e", "sheet", "actor", "character"],
|
||||||
width: 672,
|
width: 720,
|
||||||
height: 736
|
height: 736
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Rendering */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the correct HTML template path to use for rendering this particular sheet
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
get template() {
|
|
||||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/limited-sheet.html";
|
|
||||||
return "systems/sw5e/templates/actors/character-sheet.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,6 +44,7 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
|
|
||||||
// Experience Tracking
|
// Experience Tracking
|
||||||
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
||||||
|
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
|
||||||
|
|
||||||
// Return data for rendering
|
// Return data for rendering
|
||||||
return sheetData;
|
return sheetData;
|
||||||
|
@ -80,13 +68,12 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Partition items by category
|
// Partition items by category
|
||||||
let [items, powers, feats, classes, species] = data.items.reduce((arr, item) => {
|
let [items, powers, feats, classes, species] = data.items.reduce((arr, item) => {
|
||||||
|
|
||||||
// Item details
|
// Item details
|
||||||
item.img = item.img || DEFAULT_TOKEN;
|
item.img = item.img || DEFAULT_TOKEN;
|
||||||
item.isStack = item.data.quantity ? item.data.quantity > 1 : false;
|
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||||
|
|
||||||
// Item usage
|
// Item usage
|
||||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||||
|
@ -101,7 +88,7 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
if ( item.type === "power" ) arr[1].push(item);
|
if ( item.type === "power" ) arr[1].push(item);
|
||||||
else if ( item.type === "feat" ) arr[2].push(item);
|
else if ( item.type === "feat" ) arr[2].push(item);
|
||||||
else if ( item.type === "class" ) arr[3].push(item);
|
else if ( item.type === "class" ) arr[3].push(item);
|
||||||
else if ( item.type === "species" ) arr[4].push(item);
|
else if ( item.type === "species" ) arr[4].push(item);
|
||||||
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
||||||
return arr;
|
return arr;
|
||||||
}, [[], [], [], [], []]);
|
}, [[], [], [], [], []]);
|
||||||
|
@ -111,27 +98,24 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
powers = this._filterItems(powers, this._filters.powerbook);
|
powers = this._filterItems(powers, this._filters.powerbook);
|
||||||
feats = this._filterItems(feats, this._filters.features);
|
feats = this._filterItems(feats, this._filters.features);
|
||||||
|
|
||||||
|
// Organize items
|
||||||
|
for ( let i of items ) {
|
||||||
|
i.data.quantity = i.data.quantity || 0;
|
||||||
|
i.data.weight = i.data.weight || 0;
|
||||||
|
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
|
||||||
|
inventory[i.type].items.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
// Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...)
|
// Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...)
|
||||||
const powerbook = this._preparePowerbook(data, powers);
|
const powerbook = this._preparePowerbook(data, powers);
|
||||||
const nPrepared = powers.filter(s => {
|
const nPrepared = powers.filter(s => {
|
||||||
return (s.data.level > 0) && (s.data.preparation.mode === "prepared") && s.data.preparation.prepared;
|
return (s.data.level > 0) && (s.data.preparation.mode === "prepared") && s.data.preparation.prepared;
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
// Organize Inventory
|
|
||||||
let totalWeight = 0;
|
|
||||||
for ( let i of items ) {
|
|
||||||
i.data.quantity = i.data.quantity || 0;
|
|
||||||
i.data.weight = i.data.weight || 0;
|
|
||||||
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
|
|
||||||
inventory[i.type].items.push(i);
|
|
||||||
totalWeight += i.totalWeight;
|
|
||||||
}
|
|
||||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
|
||||||
|
|
||||||
// Organize Features
|
// Organize Features
|
||||||
const features = {
|
const features = {
|
||||||
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
||||||
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true},
|
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true},
|
||||||
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||||
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
||||||
};
|
};
|
||||||
|
@ -141,7 +125,7 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
}
|
}
|
||||||
classes.sort((a, b) => b.levels - a.levels);
|
classes.sort((a, b) => b.levels - a.levels);
|
||||||
features.classes.items = classes;
|
features.classes.items = classes;
|
||||||
features.species.items = species;
|
features.species.items = species;
|
||||||
|
|
||||||
// Assign and return
|
// Assign and return
|
||||||
data.inventory = Object.values(inventory);
|
data.inventory = Object.values(inventory);
|
||||||
|
@ -174,51 +158,6 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the level and percentage of encumbrance for an Actor.
|
|
||||||
*
|
|
||||||
* Optionally include the weight of carried currency across all denominations by applying the standard rule
|
|
||||||
* from the PHB pg. 143
|
|
||||||
*
|
|
||||||
* @param {Number} totalWeight The cumulative item weight from inventory items
|
|
||||||
* @param {Object} actorData The data object for the Actor being rendered
|
|
||||||
* @return {Object} An object describing the character's encumbrance level
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_computeEncumbrance(totalWeight, actorData) {
|
|
||||||
|
|
||||||
// Encumbrance classes
|
|
||||||
let mod = {
|
|
||||||
tiny: 0.5,
|
|
||||||
sm: 1,
|
|
||||||
med: 1,
|
|
||||||
lg: 2,
|
|
||||||
huge: 4,
|
|
||||||
grg: 8
|
|
||||||
}[actorData.data.traits.size] || 1;
|
|
||||||
|
|
||||||
// Apply Powerful Build feat
|
|
||||||
if ( this.actor.getFlag("sw5e", "powerfulBuild") ) mod = Math.min(mod * 2, 8);
|
|
||||||
|
|
||||||
// Add Currency Weight
|
|
||||||
if ( game.settings.get("sw5e", "currencyWeight") ) {
|
|
||||||
const currency = actorData.data.currency;
|
|
||||||
const numCoins = Object.values(currency).reduce((val, denom) => val += denom, 0);
|
|
||||||
totalWeight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute Encumbrance percentage
|
|
||||||
const enc = {
|
|
||||||
max: actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod,
|
|
||||||
value: Math.round(totalWeight * 10) / 10,
|
|
||||||
};
|
|
||||||
enc.pct = Math.min(enc.value * 100 / enc.max, 99);
|
|
||||||
enc.encumbered = enc.pct > (2/3);
|
|
||||||
return enc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Event Listeners and Handlers
|
/* Event Listeners and Handlers
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
@ -1,37 +1,21 @@
|
||||||
import { ActorSheet5e } from "../sheets/base.js";
|
import ActorSheet5e from "../sheets/base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Actor sheet for NPC type characters in the SW5E system.
|
* An Actor sheet for NPC type characters in the SW5E system.
|
||||||
* Extends the base ActorSheet5e class.
|
* Extends the base ActorSheet5e class.
|
||||||
* @type {ActorSheet5e}
|
* @extends {ActorSheet5e}
|
||||||
*/
|
*/
|
||||||
export class ActorSheet5eNPC extends ActorSheet5e {
|
export default class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
|
|
||||||
/**
|
/** @override */
|
||||||
* Define default rendering options for the NPC sheet
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
return mergeObject(super.defaultOptions, {
|
return mergeObject(super.defaultOptions, {
|
||||||
classes: ["sw5e", "sheet", "actor", "npc"],
|
classes: ["sw5e", "sheet", "actor", "npc"],
|
||||||
width: 600,
|
width: 600,
|
||||||
height: 658
|
height: 680
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Rendering */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the correct HTML template path to use for rendering this particular sheet
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
get template() {
|
|
||||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/limited-sheet.html";
|
|
||||||
return "systems/sw5e/templates/actors/npc-sheet.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,16 +26,16 @@ export class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
|
|
||||||
// Categorize Items as Features and Powers
|
// Categorize Items as Features and Powers
|
||||||
const features = {
|
const features = {
|
||||||
weapons: { label: "Attacks", items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} },
|
weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} },
|
||||||
actions: { label: "Actions", items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||||
passive: { label: "Features", items: [], dataset: {type: "feat"} },
|
passive: { label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"} },
|
||||||
equipment: { label: "Inventory", items: [], dataset: {type: "loot"}}
|
equipment: { label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start by classifying items into groups for rendering
|
// Start by classifying items into groups for rendering
|
||||||
let [powers, other] = data.items.reduce((arr, item) => {
|
let [powers, other] = data.items.reduce((arr, item) => {
|
||||||
item.img = item.img || DEFAULT_TOKEN;
|
item.img = item.img || DEFAULT_TOKEN;
|
||||||
item.isStack = item.data.quantity ? item.data.quantity > 1 : false;
|
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
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.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||||
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0));
|
item.isDepleted = item.isOnCooldown && (item.data.uses.per && (item.data.uses.value > 0));
|
||||||
|
@ -86,9 +70,7 @@ export class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/** @override */
|
||||||
* Add some extra data when rendering the sheet to reduce the amount of logic required within the template.
|
|
||||||
*/
|
|
||||||
getData() {
|
getData() {
|
||||||
const data = super.getData();
|
const data = super.getData();
|
||||||
|
|
||||||
|
@ -103,12 +85,7 @@ export class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
/* Object Updates */
|
/* Object Updates */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/** @override */
|
||||||
* This method is called upon form submission after form data is validated
|
|
||||||
* @param event {Event} The initial triggering submission event
|
|
||||||
* @param formData {Object} The object of validated form data with which to update the object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_updateObject(event, formData) {
|
_updateObject(event, formData) {
|
||||||
|
|
||||||
// Format NPC Challenge Rating
|
// Format NPC Challenge Rating
|
||||||
|
@ -126,14 +103,9 @@ export class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
/* Event Listeners and Handlers */
|
/* Event Listeners and Handlers */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/** @override */
|
||||||
* Activate event listeners using the prepared sheet HTML
|
|
||||||
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
|
|
||||||
*/
|
|
||||||
activateListeners(html) {
|
activateListeners(html) {
|
||||||
super.activateListeners(html);
|
super.activateListeners(html);
|
||||||
|
|
||||||
// Rollable Health Formula
|
|
||||||
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
|
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,4 +124,4 @@ export class ActorSheet5eNPC extends ActorSheet5e {
|
||||||
AudioHelper.play({src: CONFIG.sounds.dice});
|
AudioHelper.play({src: CONFIG.sounds.dice});
|
||||||
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
381
module/actor/sheets/vehicle.js
Normal file
381
module/actor/sheets/vehicle.js
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
import ActorSheet5e from "./base.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Actor sheet for Vehicle type actors.
|
||||||
|
* Extends the base ActorSheet5e class.
|
||||||
|
* @type {ActorSheet5e}
|
||||||
|
*/
|
||||||
|
export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
/**
|
||||||
|
* Define default rendering options for the Vehicle sheet.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||||
|
width: 605,
|
||||||
|
height: 680
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new cargo entry for a vehicle Actor.
|
||||||
|
*/
|
||||||
|
static get newCargo() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
quantity: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the total weight of the vehicle's cargo.
|
||||||
|
* @param {Number} totalWeight The cumulative item weight from inventory items
|
||||||
|
* @param {Object} actorData The data object for the Actor being rendered
|
||||||
|
* @returns {{max: number, value: number, pct: number}}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_computeEncumbrance(totalWeight, actorData) {
|
||||||
|
|
||||||
|
// Compute currency weight
|
||||||
|
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
||||||
|
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||||
|
|
||||||
|
// Vehicle weights are an order of magnitude greater.
|
||||||
|
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
||||||
|
|
||||||
|
// Compute overall encumbrance
|
||||||
|
const enc = {
|
||||||
|
max: actorData.data.attributes.capacity.cargo,
|
||||||
|
value: Math.round(totalWeight * 10) / 10
|
||||||
|
};
|
||||||
|
enc.pct = Math.min(enc.value * 100 / enc.max, 99);
|
||||||
|
return enc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare items that are mounted to a vehicle and require one or more crew
|
||||||
|
* to operate.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareCrewedItem(item) {
|
||||||
|
|
||||||
|
// Determine crewed status
|
||||||
|
const isCrewed = item.data.crewed;
|
||||||
|
item.toggleClass = isCrewed ? 'active' : '';
|
||||||
|
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`);
|
||||||
|
|
||||||
|
// Handle crew actions
|
||||||
|
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'}`);
|
||||||
|
if (item.data.cover === .5) item.cover = '½';
|
||||||
|
else if (item.data.cover === .75) item.cover = '¾';
|
||||||
|
else if (item.data.cover === null) item.cover = '—';
|
||||||
|
if (item.crew < 1 || item.crew === null) item.crew = '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare vehicle weapons
|
||||||
|
if (item.type === 'equipment' || item.type === 'weapon') {
|
||||||
|
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organize Owned Items for rendering the Vehicle sheet.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareItems(data) {
|
||||||
|
const cargoColumns = [{
|
||||||
|
label: game.i18n.localize('SW5E.Quantity'),
|
||||||
|
css: 'item-qty',
|
||||||
|
property: 'quantity',
|
||||||
|
editable: 'Number'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const equipmentColumns = [{
|
||||||
|
label: game.i18n.localize('SW5E.Quantity'),
|
||||||
|
css: 'item-qty',
|
||||||
|
property: 'data.quantity'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.AC'),
|
||||||
|
css: 'item-ac',
|
||||||
|
property: 'data.armor.value'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.HP'),
|
||||||
|
css: 'item-hp',
|
||||||
|
property: 'data.hp.value',
|
||||||
|
editable: 'Number'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Threshold'),
|
||||||
|
css: 'item-threshold',
|
||||||
|
property: 'threshold'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const features = {
|
||||||
|
actions: {
|
||||||
|
label: game.i18n.localize('SW5E.ActionPl'),
|
||||||
|
items: [],
|
||||||
|
crewable: true,
|
||||||
|
dataset: {type: 'feat', 'activation.type': 'crew'},
|
||||||
|
columns: [{
|
||||||
|
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||||
|
css: 'item-crew',
|
||||||
|
property: 'crew'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Cover'),
|
||||||
|
css: 'item-cover',
|
||||||
|
property: 'cover'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
equipment: {
|
||||||
|
label: game.i18n.localize('SW5E.ItemTypeEquipment'),
|
||||||
|
items: [],
|
||||||
|
crewable: true,
|
||||||
|
dataset: {type: 'equipment', 'armor.type': 'vehicle'},
|
||||||
|
columns: equipmentColumns
|
||||||
|
},
|
||||||
|
passive: {
|
||||||
|
label: game.i18n.localize('SW5E.Features'),
|
||||||
|
items: [],
|
||||||
|
dataset: {type: 'feat'}
|
||||||
|
},
|
||||||
|
reactions: {
|
||||||
|
label: game.i18n.localize('SW5E.ReactionPl'),
|
||||||
|
items: [],
|
||||||
|
dataset: {type: 'feat', 'activation.type': 'reaction'}
|
||||||
|
},
|
||||||
|
weapons: {
|
||||||
|
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'),
|
||||||
|
items: [],
|
||||||
|
crewable: true,
|
||||||
|
dataset: {type: 'weapon', 'weapon-type': 'siege'},
|
||||||
|
columns: equipmentColumns
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cargo = {
|
||||||
|
crew: {
|
||||||
|
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||||
|
items: data.data.cargo.crew,
|
||||||
|
css: 'cargo-row crew',
|
||||||
|
editableName: true,
|
||||||
|
dataset: {type: 'crew'},
|
||||||
|
columns: cargoColumns
|
||||||
|
},
|
||||||
|
passengers: {
|
||||||
|
label: game.i18n.localize('SW5E.VehiclePassengers'),
|
||||||
|
items: data.data.cargo.passengers,
|
||||||
|
css: 'cargo-row passengers',
|
||||||
|
editableName: true,
|
||||||
|
dataset: {type: 'passengers'},
|
||||||
|
columns: cargoColumns
|
||||||
|
},
|
||||||
|
cargo: {
|
||||||
|
label: game.i18n.localize('SW5E.VehicleCargo'),
|
||||||
|
items: [],
|
||||||
|
dataset: {type: 'loot'},
|
||||||
|
columns: [{
|
||||||
|
label: game.i18n.localize('SW5E.Quantity'),
|
||||||
|
css: 'item-qty',
|
||||||
|
property: 'data.quantity',
|
||||||
|
editable: 'Number'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Price'),
|
||||||
|
css: 'item-price',
|
||||||
|
property: 'data.price',
|
||||||
|
editable: 'Number'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Weight'),
|
||||||
|
css: 'item-weight',
|
||||||
|
property: 'data.weight',
|
||||||
|
editable: 'Number'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let totalWeight = 0;
|
||||||
|
for (const item of data.items) {
|
||||||
|
this._prepareCrewedItem(item);
|
||||||
|
if (item.type === 'weapon') features.weapons.items.push(item);
|
||||||
|
else if (item.type === 'equipment') features.equipment.items.push(item);
|
||||||
|
else if (item.type === 'loot') {
|
||||||
|
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||||
|
cargo.cargo.items.push(item);
|
||||||
|
}
|
||||||
|
else if (item.type === 'feat') {
|
||||||
|
if (!item.data.activation.type || item.data.activation.type === 'none') {
|
||||||
|
features.passive.items.push(item);
|
||||||
|
}
|
||||||
|
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
|
||||||
|
else features.actions.items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.features = Object.values(features);
|
||||||
|
data.cargo = Object.values(cargo);
|
||||||
|
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Event Listeners and Handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
if (!this.options.editable) return;
|
||||||
|
|
||||||
|
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||||
|
html.find('.item-hp input')
|
||||||
|
.click(evt => evt.target.select())
|
||||||
|
.change(this._onHPChange.bind(this));
|
||||||
|
|
||||||
|
html.find('.item:not(.cargo-row) input[data-property]')
|
||||||
|
.click(evt => evt.target.select())
|
||||||
|
.change(this._onEditInSheet.bind(this));
|
||||||
|
|
||||||
|
html.find('.cargo-row input')
|
||||||
|
.click(evt => evt.target.select())
|
||||||
|
.change(this._onCargoRowChange.bind(this));
|
||||||
|
|
||||||
|
if (this.actor.data.data.attributes.actions.stations) {
|
||||||
|
html.find('.counter.actions, .counter.action-thresholds').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle saving a cargo row (i.e. crew or passenger) in-sheet.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Actor>|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onCargoRowChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const row = target.closest('.item');
|
||||||
|
const idx = Number(row.dataset.itemId);
|
||||||
|
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||||
|
|
||||||
|
// Get the cargo entry
|
||||||
|
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||||
|
const entry = cargo[idx];
|
||||||
|
if (!entry) return null;
|
||||||
|
|
||||||
|
// Update the cargo value
|
||||||
|
const key = target.dataset.property || 'name';
|
||||||
|
const type = target.dataset.dtype;
|
||||||
|
let value = target.value;
|
||||||
|
if (type === 'Number') value = Number(value);
|
||||||
|
entry[key] = value;
|
||||||
|
|
||||||
|
// Perform the Actor update
|
||||||
|
return this.actor.update({[`data.cargo.${property}`]: cargo});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle editing certain values like quantity, price, and weight in-sheet.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onEditInSheet(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const property = event.currentTarget.dataset.property;
|
||||||
|
const type = event.currentTarget.dataset.dtype;
|
||||||
|
let value = event.currentTarget.value;
|
||||||
|
switch (type) {
|
||||||
|
case 'Number': value = parseInt(value); break;
|
||||||
|
case 'Boolean': value = value === 'true'; break;
|
||||||
|
}
|
||||||
|
return item.update({[`${property}`]: value});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle creating a new crew or passenger row.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Actor|Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemCreate(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const type = target.dataset.type;
|
||||||
|
if (type === 'crew' || type === 'passengers') {
|
||||||
|
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||||
|
cargo.push(this.constructor.newCargo);
|
||||||
|
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||||
|
}
|
||||||
|
return super._onItemCreate(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle deleting a crew or passenger row.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Actor|Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemDelete(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const row = event.currentTarget.closest('.item');
|
||||||
|
if (row.classList.contains('cargo-row')) {
|
||||||
|
const idx = Number(row.dataset.itemId);
|
||||||
|
const type = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||||
|
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||||
|
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._onItemDelete(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special handling for editing HP to clamp it within appropriate range.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onHPChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
|
||||||
|
event.currentTarget.value = hp;
|
||||||
|
return item.update({'data.hp.value': hp});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggling an item's crewed status.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onToggleItem(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const crewed = !!item.data.data.crewed;
|
||||||
|
return item.update({'data.crewed': !crewed});
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,7 +2,7 @@
|
||||||
* A specialized Dialog subclass for ability usage
|
* A specialized Dialog subclass for ability usage
|
||||||
* @type {Dialog}
|
* @type {Dialog}
|
||||||
*/
|
*/
|
||||||
export class AbilityUseDialog extends Dialog {
|
export default class AbilityUseDialog extends Dialog {
|
||||||
constructor(item, dialogData={}, options={}) {
|
constructor(item, dialogData={}, options={}) {
|
||||||
super(dialogData, options);
|
super(dialogData, options);
|
||||||
this.options.classes = ["sw5e", "dialog"];
|
this.options.classes = ["sw5e", "dialog"];
|
||||||
|
@ -25,40 +25,150 @@ export class AbilityUseDialog extends Dialog {
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
static async create(item) {
|
static async create(item) {
|
||||||
|
if ( !item.isOwned ) throw new Error("You cannot display an ability usage dialog for an unowned item");
|
||||||
|
|
||||||
const uses = item.data.data.uses;
|
// Prepare data
|
||||||
const recharge = item.data.data.recharge;
|
const actorData = item.actor.data.data;
|
||||||
|
const itemData = item.data.data;
|
||||||
|
const uses = itemData.uses || {};
|
||||||
|
const quantity = itemData.quantity || 0;
|
||||||
|
const recharge = itemData.recharge || {};
|
||||||
const recharges = !!recharge.value;
|
const recharges = !!recharge.value;
|
||||||
|
|
||||||
// Render the ability usage template
|
// Prepare dialog form data
|
||||||
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", {
|
const data = {
|
||||||
item: item.data,
|
item: item.data,
|
||||||
canUse: recharges ? recharge.charged : uses.value > 0,
|
title: game.i18n.format("SW5E.AbilityUseHint", item.data),
|
||||||
consume: true,
|
note: this._getAbilityUseNote(item.data, uses, recharge),
|
||||||
uses: uses,
|
hasLimitedUses: uses.max || recharges,
|
||||||
recharges: !!recharge.value,
|
canUse: recharges ? recharge.charged : (quantity > 0 && !uses.value) || uses.value > 0,
|
||||||
isCharged: recharge.charged,
|
|
||||||
hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||||
perLabel: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
errors: []
|
||||||
});
|
};
|
||||||
|
if ( item.data.type === "power" ) this._getPowerData(actorData, itemData, data);
|
||||||
|
|
||||||
|
// Render the ability usage template
|
||||||
|
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
|
||||||
|
|
||||||
// Create the Dialog and return as a Promise
|
// Create the Dialog and return as a Promise
|
||||||
|
const icon = data.hasPowerSlots ? "fa-magic" : "fa-fist-raised";
|
||||||
|
const label = game.i18n.localize("SW5E.AbilityUse" + (data.hasPowerSlots ? "Cast" : "Use"));
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let formData = null;
|
|
||||||
const dlg = new this(item, {
|
const dlg = new this(item, {
|
||||||
title: `${item.name}: Ability Configuration`,
|
title: `${item.name}: Usage Configuration`,
|
||||||
content: html,
|
content: html,
|
||||||
buttons: {
|
buttons: {
|
||||||
use: {
|
use: {
|
||||||
icon: '<i class="fas fa-fist-raised"></i>',
|
icon: `<i class="fas ${icon}"></i>`,
|
||||||
label: "Use Ability",
|
label: label,
|
||||||
callback: html => formData = new FormData(html[0].querySelector("#ability-use-form"))
|
callback: html => resolve(new FormData(html[0].querySelector("form")))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
default: "use",
|
default: "use",
|
||||||
close: () => resolve(formData)
|
close: () => resolve(null)
|
||||||
});
|
});
|
||||||
dlg.render(true);
|
dlg.render(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Helpers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dialog data related to limited power slots
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static _getPowerData(actorData, itemData, data) {
|
||||||
|
|
||||||
|
// Determine whether the power may be up-cast
|
||||||
|
const lvl = itemData.level;
|
||||||
|
const canUpcast = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||||
|
|
||||||
|
// Determine the levels which are feasible
|
||||||
|
let lmax = 0;
|
||||||
|
const powerLevels = Array.fromRange(10).reduce((arr, i) => {
|
||||||
|
if ( i < lvl ) return arr;
|
||||||
|
const label = CONFIG.SW5E.powerLevels[i];
|
||||||
|
const l = actorData.powers["power"+i] || {max: 0, override: null};
|
||||||
|
let max = parseInt(l.override || l.max || 0);
|
||||||
|
let slots = Math.clamped(parseInt(l.value || 0), 0, max);
|
||||||
|
if ( max > 0 ) lmax = i;
|
||||||
|
arr.push({
|
||||||
|
level: i,
|
||||||
|
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
|
||||||
|
canCast: canUpcast && (max > 0),
|
||||||
|
hasSlots: slots > 0
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
}, []).filter(sl => sl.level <= lmax);
|
||||||
|
|
||||||
|
// If this character has pact slots, present them as an option for casting the power.
|
||||||
|
const pact = actorData.powers.pact;
|
||||||
|
if (pact.level >= lvl) {
|
||||||
|
powerLevels.push({
|
||||||
|
level: 'pact',
|
||||||
|
label: `${game.i18n.format('SW5E.PowerLevelPact', {level: pact.level, n: pact.value})}`,
|
||||||
|
canCast: canUpcast,
|
||||||
|
hasSlots: pact.value > 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const canCast = powerLevels.some(l => l.hasSlots);
|
||||||
|
|
||||||
|
// Return merged data
|
||||||
|
data = mergeObject(data, { hasPowerSlots: true, canUpcast, powerLevels });
|
||||||
|
if ( !canCast ) data.errors.push("SW5E.PowerCastNoSlots");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ability usage note that is displayed
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static _getAbilityUseNote(item, uses, recharge) {
|
||||||
|
|
||||||
|
// Zero quantity
|
||||||
|
const quantity = item.data.quantity;
|
||||||
|
if ( quantity <= 0 ) return game.i18n.localize("SW5E.AbilityUseUnavailableHint");
|
||||||
|
|
||||||
|
// Abilities which use Recharge
|
||||||
|
if ( !!recharge.value ) {
|
||||||
|
return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", {
|
||||||
|
type: item.type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not use any resource
|
||||||
|
if ( !uses.per || !uses.max ) return "";
|
||||||
|
|
||||||
|
// Consumables
|
||||||
|
if ( item.type === "consumable" ) {
|
||||||
|
let str = "SW5E.AbilityUseNormalHint";
|
||||||
|
if ( uses.value > 1 ) str = "SW5E.AbilityUseConsumableChargeHint";
|
||||||
|
else if ( item.data.quantity === 1 && uses.autoDestroy ) str = "SW5E.AbilityUseConsumableDestroyHint";
|
||||||
|
else if ( item.data.quantity > 1 ) str = "SW5E.AbilityUseConsumableQuantityHint";
|
||||||
|
return game.i18n.format(str, {
|
||||||
|
type: item.data.consumableType,
|
||||||
|
value: uses.value,
|
||||||
|
quantity: item.data.quantity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other Items
|
||||||
|
else {
|
||||||
|
return game.i18n.format("SW5E.AbilityUseNormalHint", {
|
||||||
|
type: item.type,
|
||||||
|
value: uses.value,
|
||||||
|
max: uses.max,
|
||||||
|
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
static _handleSubmit(formData, item) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
export class ActorSheetFlags extends BaseEntitySheet {
|
/**
|
||||||
static get defaultOptions() {
|
* An application class which provides advanced configuration for special character flags which modify an Actor
|
||||||
|
* @extends {BaseEntitySheet}
|
||||||
|
*/
|
||||||
|
export default class ActorSheetFlags extends BaseEntitySheet {
|
||||||
|
static get defaultOptions() {
|
||||||
const options = super.defaultOptions;
|
const options = super.defaultOptions;
|
||||||
return mergeObject(options, {
|
return mergeObject(options, {
|
||||||
id: "actor-flags",
|
id: "actor-flags",
|
||||||
|
@ -68,10 +72,10 @@ export class ActorSheetFlags extends BaseEntitySheet {
|
||||||
{name: "data.bonuses.mwak.damage", label: "SW5E.BonusMWDamage"},
|
{name: "data.bonuses.mwak.damage", label: "SW5E.BonusMWDamage"},
|
||||||
{name: "data.bonuses.rwak.attack", label: "SW5E.BonusRWAttack"},
|
{name: "data.bonuses.rwak.attack", label: "SW5E.BonusRWAttack"},
|
||||||
{name: "data.bonuses.rwak.damage", label: "SW5E.BonusRWDamage"},
|
{name: "data.bonuses.rwak.damage", label: "SW5E.BonusRWDamage"},
|
||||||
{name: "data.bonuses.mpak.attack", label: "SW5E.BonusMSAttack"},
|
{name: "data.bonuses.mpak.attack", label: "SW5E.BonusMPAttack"},
|
||||||
{name: "data.bonuses.mpak.damage", label: "SW5E.BonusMSDamage"},
|
{name: "data.bonuses.mpak.damage", label: "SW5E.BonusMPDamage"},
|
||||||
{name: "data.bonuses.rpak.attack", label: "SW5E.BonusRSAttack"},
|
{name: "data.bonuses.rpak.attack", label: "SW5E.BonusRPAttack"},
|
||||||
{name: "data.bonuses.rpak.damage", label: "SW5E.BonusRSDamage"},
|
{name: "data.bonuses.rpak.damage", label: "SW5E.BonusRPDamage"},
|
||||||
{name: "data.bonuses.abilities.check", label: "SW5E.BonusAbilityCheck"},
|
{name: "data.bonuses.abilities.check", label: "SW5E.BonusAbilityCheck"},
|
||||||
{name: "data.bonuses.abilities.save", label: "SW5E.BonusAbilitySave"},
|
{name: "data.bonuses.abilities.save", label: "SW5E.BonusAbilitySave"},
|
||||||
{name: "data.bonuses.abilities.skill", label: "SW5E.BonusAbilitySkill"},
|
{name: "data.bonuses.abilities.skill", label: "SW5E.BonusAbilitySkill"},
|
||||||
|
@ -91,7 +95,7 @@ export class ActorSheetFlags extends BaseEntitySheet {
|
||||||
*/
|
*/
|
||||||
async _updateObject(event, formData) {
|
async _updateObject(event, formData) {
|
||||||
const actor = this.object;
|
const actor = this.object;
|
||||||
const updateData = expandObject(formData);
|
let updateData = expandObject(formData);
|
||||||
|
|
||||||
// Unset any flags which are "false"
|
// Unset any flags which are "false"
|
||||||
let unset = false;
|
let unset = false;
|
||||||
|
@ -106,7 +110,18 @@ export class ActorSheetFlags extends BaseEntitySheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the changes
|
// Clear any bonuses which are whitespace only
|
||||||
|
for ( let b of Object.values(updateData.data.bonuses ) ) {
|
||||||
|
for ( let [k, v] of Object.entries(b) ) {
|
||||||
|
b[k] = v.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff the data against any applied overrides and apply
|
||||||
|
// TODO: Remove this logical gate once 0.7.x is release channel
|
||||||
|
if ( !isNewerVersion("0.7.1", game.data.version) ){
|
||||||
|
updateData.data = diffObject(this.object.overrides, updateData.data);
|
||||||
|
}
|
||||||
await actor.update(updateData, {diff: false});
|
await actor.update(updateData, {diff: false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
69
module/apps/long-rest.js
Normal file
69
module/apps/long-rest.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* A helper Dialog subclass for completing a long rest
|
||||||
|
* @extends {Dialog}
|
||||||
|
*/
|
||||||
|
export default class LongRestDialog extends Dialog {
|
||||||
|
constructor(actor, dialogData = {}, options = {}) {
|
||||||
|
super(dialogData, options);
|
||||||
|
this.actor = actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
template: "systems/sw5e/templates/apps/long-rest.html",
|
||||||
|
classes: ["sw5e", "dialog"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getData() {
|
||||||
|
const data = super.getData();
|
||||||
|
const variant = game.settings.get("sw5e", "restVariant");
|
||||||
|
data.promptNewDay = variant !== "gritty"; // It's always a new day when resting 1 week
|
||||||
|
data.newDay = variant === "normal"; // It's probably a new day when resting normally (8 hours)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||||
|
* workflow has been resolved.
|
||||||
|
* @param {Actor5e} actor
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
static async longRestDialog({ actor } = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const dlg = new this(actor, {
|
||||||
|
title: "Long Rest",
|
||||||
|
buttons: {
|
||||||
|
rest: {
|
||||||
|
icon: '<i class="fas fa-bed"></i>',
|
||||||
|
label: "Rest",
|
||||||
|
callback: html => {
|
||||||
|
let newDay = false;
|
||||||
|
if (game.settings.get("sw5e", "restVariant") === "normal")
|
||||||
|
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||||
|
else if(game.settings.get("sw5e", "restVariant") === "gritty")
|
||||||
|
newDay = true;
|
||||||
|
resolve(newDay);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
icon: '<i class="fas fa-times"></i>',
|
||||||
|
label: "Cancel",
|
||||||
|
callback: reject
|
||||||
|
}
|
||||||
|
},
|
||||||
|
default: 'rest',
|
||||||
|
close: reject
|
||||||
|
});
|
||||||
|
dlg.render(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
|
import LongRestDialog from "./long-rest.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper Dialog subclass for rolling Hit Dice on short rest
|
* A helper Dialog subclass for rolling Hit Dice on short rest
|
||||||
* @type {Dialog}
|
* @extends {Dialog}
|
||||||
*/
|
*/
|
||||||
export class ShortRestDialog extends Dialog {
|
export default class ShortRestDialog extends Dialog {
|
||||||
constructor(actor, dialogData={}, options={}) {
|
constructor(actor, dialogData={}, options={}) {
|
||||||
super(dialogData, options);
|
super(dialogData, options);
|
||||||
|
|
||||||
|
@ -34,6 +36,8 @@ export class ShortRestDialog extends Dialog {
|
||||||
/** @override */
|
/** @override */
|
||||||
getData() {
|
getData() {
|
||||||
const data = super.getData();
|
const data = super.getData();
|
||||||
|
|
||||||
|
// Determine Hit Dice
|
||||||
data.availableHD = this.actor.data.items.reduce((hd, item) => {
|
data.availableHD = this.actor.data.items.reduce((hd, item) => {
|
||||||
if ( item.type === "class" ) {
|
if ( item.type === "class" ) {
|
||||||
const d = item.data;
|
const d = item.data;
|
||||||
|
@ -45,6 +49,11 @@ export class ShortRestDialog extends Dialog {
|
||||||
}, {});
|
}, {});
|
||||||
data.canRoll = this.actor.data.data.attributes.hd > 0;
|
data.canRoll = this.actor.data.data.attributes.hd > 0;
|
||||||
data.denomination = this._denom;
|
data.denomination = this._denom;
|
||||||
|
|
||||||
|
// Determine rest type
|
||||||
|
const variant = game.settings.get("sw5e", "restVariant");
|
||||||
|
data.promptNewDay = variant !== "epic"; // It's never a new day when only resting 1 minute
|
||||||
|
data.newDay = false; // It may be a new day, but not by default
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +65,6 @@ export class ShortRestDialog extends Dialog {
|
||||||
super.activateListeners(html);
|
super.activateListeners(html);
|
||||||
let btn = html.find("#roll-hd");
|
let btn = html.find("#roll-hd");
|
||||||
btn.click(this._onRollHitDie.bind(this));
|
btn.click(this._onRollHitDie.bind(this));
|
||||||
super.activateListeners(html);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -83,21 +91,27 @@ export class ShortRestDialog extends Dialog {
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
static async shortRestDialog({actor}={}) {
|
static async shortRestDialog({actor}={}) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve, reject) => {
|
||||||
const dlg = new this(actor, {
|
const dlg = new this(actor, {
|
||||||
title: "Short Rest",
|
title: "Short Rest",
|
||||||
buttons: {
|
buttons: {
|
||||||
rest: {
|
rest: {
|
||||||
icon: '<i class="fas fa-bed"></i>',
|
icon: '<i class="fas fa-bed"></i>',
|
||||||
label: "Rest",
|
label: "Rest",
|
||||||
callback: () => resolve(true)
|
callback: html => {
|
||||||
|
let newDay = false;
|
||||||
|
if (game.settings.get("sw5e", "restVariant") === "gritty")
|
||||||
|
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||||
|
resolve(newDay);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
icon: '<i class="fas fa-times"></i>',
|
icon: '<i class="fas fa-times"></i>',
|
||||||
label: "Cancel",
|
label: "Cancel",
|
||||||
callback: () => resolve(false)
|
callback: reject
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
close: reject
|
||||||
});
|
});
|
||||||
dlg.render(true);
|
dlg.render(true);
|
||||||
});
|
});
|
||||||
|
@ -108,31 +122,12 @@ export class ShortRestDialog extends Dialog {
|
||||||
/**
|
/**
|
||||||
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||||
* workflow has been resolved.
|
* workflow has been resolved.
|
||||||
|
* @deprecated
|
||||||
* @param {Actor5e} actor
|
* @param {Actor5e} actor
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
static async longRestDialog({actor}={}) {
|
static async longRestDialog({actor}={}) {
|
||||||
const content = `<p>Take a long rest?</p><p>On a long rest you will recover hit points, half your maximum hit dice,
|
console.warn("WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead.");
|
||||||
class resources, limited use item charges, and power slots.</p>`;
|
return LongRestDialog.longRestDialog(...arguments);
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
new Dialog({
|
|
||||||
title: "Long Rest",
|
|
||||||
content: content,
|
|
||||||
buttons: {
|
|
||||||
rest: {
|
|
||||||
icon: '<i class="fas fa-bed"></i>',
|
|
||||||
label: "Rest",
|
|
||||||
callback: resolve
|
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
icon: '<i class="fas fa-times"></i>',
|
|
||||||
label: "Cancel",
|
|
||||||
callback: reject
|
|
||||||
},
|
|
||||||
},
|
|
||||||
default: 'rest',
|
|
||||||
close: reject
|
|
||||||
}, {classes: ["sw5e", "dialog"]}).render(true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* A specialized form used to select from a checklist of attributes, traits, or properties
|
* A specialized form used to select from a checklist of attributes, traits, or properties
|
||||||
* @extends {FormApplication}
|
* @extends {FormApplication}
|
||||||
*/
|
*/
|
||||||
export class TraitSelector extends FormApplication {
|
export default class TraitSelector extends FormApplication {
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
|
|
|
@ -1,32 +1,40 @@
|
||||||
/**
|
/** @override */
|
||||||
* Measure the distance between two pixel coordinates
|
export const measureDistances = function(segments, options={}) {
|
||||||
* See BaseGrid.measureDistance for more details
|
if ( !options.gridSpaces ) return BaseGrid.prototype.measureDistances.call(this, segments, options);
|
||||||
*
|
|
||||||
* @param {Object} p0 The origin coordinate {x, y}
|
|
||||||
* @param {Object} p1 The destination coordinate {x, y}
|
|
||||||
* @param {boolean} gridSpaces Enforce grid distance (if true) vs. direct point-to-point (if false)
|
|
||||||
* @return {number} The distance between p1 and p0
|
|
||||||
*/
|
|
||||||
export const measureDistance = function(p0, p1, {gridSpaces=true}={}) {
|
|
||||||
if ( !gridSpaces ) return BaseGrid.prototype.measureDistance.bind(this)(p0, p1, {gridSpaces});
|
|
||||||
let gs = canvas.dimensions.size,
|
|
||||||
ray = new Ray(p0, p1),
|
|
||||||
nx = Math.abs(Math.ceil(ray.dx / gs)),
|
|
||||||
ny = Math.abs(Math.ceil(ray.dy / gs));
|
|
||||||
|
|
||||||
// Get the number of straight and diagonal moves
|
// Track the total number of diagonals
|
||||||
let nDiagonal = Math.min(nx, ny),
|
let nDiagonal = 0;
|
||||||
nStraight = Math.abs(ny - nx);
|
const rule = this.parent.diagonalRule;
|
||||||
|
const d = canvas.dimensions;
|
||||||
|
|
||||||
// Alternative DMG Movement
|
// Iterate over measured segments
|
||||||
if ( this.parent.diagonalRule === "5105" ) {
|
return segments.map(s => {
|
||||||
let nd10 = Math.floor(nDiagonal / 2);
|
let r = s.ray;
|
||||||
let spaces = (nd10 * 2) + (nDiagonal - nd10) + nStraight;
|
|
||||||
return spaces * canvas.dimensions.distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard PHB Movement
|
// Determine the total distance traveled
|
||||||
else return (nStraight + nDiagonal) * canvas.scene.data.gridDistance;
|
let nx = Math.abs(Math.ceil(r.dx / d.size));
|
||||||
|
let ny = Math.abs(Math.ceil(r.dy / d.size));
|
||||||
|
|
||||||
|
// Determine the number of straight and diagonal moves
|
||||||
|
let nd = Math.min(nx, ny);
|
||||||
|
let ns = Math.abs(ny - nx);
|
||||||
|
nDiagonal += nd;
|
||||||
|
|
||||||
|
// Alternative DMG Movement
|
||||||
|
if (rule === "5105") {
|
||||||
|
let nd10 = Math.floor(nDiagonal / 2) - Math.floor((nDiagonal - nd) / 2);
|
||||||
|
let spaces = (nd10 * 2) + (nd - nd10) + ns;
|
||||||
|
return spaces * canvas.dimensions.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Euclidean Measurement
|
||||||
|
else if (rule === "EUCL") {
|
||||||
|
return Math.round(Math.hypot(nx, ny) * canvas.scene.data.gridDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard PHB Movement
|
||||||
|
else return (ns + nd) * canvas.scene.data.gridDistance;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -39,8 +47,8 @@ const _TokenGetBarAttribute = Token.prototype.getBarAttribute;
|
||||||
export const getBarAttribute = function(...args) {
|
export const getBarAttribute = function(...args) {
|
||||||
const data = _TokenGetBarAttribute.bind(this)(...args);
|
const data = _TokenGetBarAttribute.bind(this)(...args);
|
||||||
if ( data && (data.attribute === "attributes.hp") ) {
|
if ( data && (data.attribute === "attributes.hp") ) {
|
||||||
data.value += parseInt(data['temp'] || 0);
|
data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0);
|
||||||
data.max += parseInt(data['tempmax'] || 0);
|
data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import {Actor5e} from "./actor/entity.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight critical success or failure on d20 rolls
|
* Highlight critical success or failure on d20 rolls
|
||||||
*/
|
*/
|
||||||
export const highlightCriticalSuccessFailure = function(message, html, data) {
|
export const highlightCriticalSuccessFailure = function(message, html, data) {
|
||||||
if ( !message.isRoll || !message.isRollVisible || !message.roll.parts.length ) return;
|
if ( !message.isRoll || !message.isContentVisible ) return;
|
||||||
|
|
||||||
// Highlight rolls where the first part is a d20 roll
|
// Highlight rolls where the first part is a d20 roll
|
||||||
const roll = message.roll;
|
const roll = message.roll;
|
||||||
let d = roll.parts[0];
|
if ( !roll.dice.length ) return;
|
||||||
const isD20Roll = d instanceof Die && (d.faces === 20) && (d.results.length === 1);
|
const d = roll.dice[0];
|
||||||
if ( !isD20Roll ) return;
|
|
||||||
|
|
||||||
// Ensure it is not a modified roll
|
// Ensure it is an un-modified d20 roll
|
||||||
|
const isD20 = (d.faces === 20) && ( d.results.length === 1 );
|
||||||
|
if ( !isD20 ) return;
|
||||||
const isModifiedRoll = ("success" in d.rolls[0]) || d.options.marginSuccess || d.options.marginFailure;
|
const isModifiedRoll = ("success" in d.rolls[0]) || d.options.marginSuccess || d.options.marginFailure;
|
||||||
if ( isModifiedRoll ) return;
|
if ( isModifiedRoll ) return;
|
||||||
|
|
||||||
|
@ -60,32 +60,55 @@ export const displayChatActionButtons = function(message, html, data) {
|
||||||
* @return {Array} The extended options Array including new context choices
|
* @return {Array} The extended options Array including new context choices
|
||||||
*/
|
*/
|
||||||
export const addChatMessageContextOptions = function(html, options) {
|
export const addChatMessageContextOptions = function(html, options) {
|
||||||
let canApply = li => canvas.tokens.controlledTokens.length && li.find(".dice-roll").length;
|
let canApply = li => {
|
||||||
|
const message = game.messages.get(li.data("messageId"));
|
||||||
|
return message.isRoll && message.isContentVisible && canvas.tokens.controlled.length;
|
||||||
|
};
|
||||||
options.push(
|
options.push(
|
||||||
{
|
{
|
||||||
name: game.i18n.localize("SW5E.ChatContextDamage"),
|
name: game.i18n.localize("SW5E.ChatContextDamage"),
|
||||||
icon: '<i class="fas fa-user-minus"></i>',
|
icon: '<i class="fas fa-user-minus"></i>',
|
||||||
condition: canApply,
|
condition: canApply,
|
||||||
callback: li => Actor5e.applyDamage(li, 1)
|
callback: li => applyChatCardDamage(li, 1)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: game.i18n.localize("SW5E.ChatContextHealing"),
|
name: game.i18n.localize("SW5E.ChatContextHealing"),
|
||||||
icon: '<i class="fas fa-user-plus"></i>',
|
icon: '<i class="fas fa-user-plus"></i>',
|
||||||
condition: canApply,
|
condition: canApply,
|
||||||
callback: li => Actor5e.applyDamage(li, -1)
|
callback: li => applyChatCardDamage(li, -1)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: game.i18n.localize("SW5E.ChatContextDoubleDamage"),
|
name: game.i18n.localize("SW5E.ChatContextDoubleDamage"),
|
||||||
icon: '<i class="fas fa-user-injured"></i>',
|
icon: '<i class="fas fa-user-injured"></i>',
|
||||||
condition: canApply,
|
condition: canApply,
|
||||||
callback: li => Actor5e.applyDamage(li, 2)
|
callback: li => applyChatCardDamage(li, 2)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: game.i18n.localize("SW5E.ChatContextHalfDamage"),
|
name: game.i18n.localize("SW5E.ChatContextHalfDamage"),
|
||||||
icon: '<i class="fas fa-user-shield"></i>',
|
icon: '<i class="fas fa-user-shield"></i>',
|
||||||
condition: canApply,
|
condition: canApply,
|
||||||
callback: li => Actor5e.applyDamage(li, 0.5)
|
callback: li => applyChatCardDamage(li, 0.5)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply rolled dice damage to the token or tokens which are currently controlled.
|
||||||
|
* This allows for damage to be scaled by a multiplier to account for healing, critical hits, or resistance
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} roll The chat entry which contains the roll data
|
||||||
|
* @param {Number} multiplier A damage multiplier to apply to the rolled damage.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
function applyChatCardDamage(roll, multiplier) {
|
||||||
|
const amount = roll.find('.dice-total').text();
|
||||||
|
return Promise.all(canvas.tokens.controlled.map(t => {
|
||||||
|
const a = t.actor;
|
||||||
|
return a.applyDamage(amount, multiplier);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
|
@ -11,6 +11,48 @@ export const _getInitiativeFormula = function(combatant) {
|
||||||
const init = actor.data.data.attributes.init;
|
const init = actor.data.data.attributes.init;
|
||||||
const parts = ["1d20", init.mod, (init.prof !== 0) ? init.prof : null, (init.bonus !== 0) ? init.bonus : null];
|
const parts = ["1d20", init.mod, (init.prof !== 0) ? init.prof : null, (init.bonus !== 0) ? init.bonus : null];
|
||||||
if ( actor.getFlag("sw5e", "initiativeAdv") ) parts[0] = "2d20kh";
|
if ( actor.getFlag("sw5e", "initiativeAdv") ) parts[0] = "2d20kh";
|
||||||
if ( CONFIG.Combat.initiative.tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100);
|
|
||||||
|
// Optionally apply Dexterity tiebreaker
|
||||||
|
const tiebreaker = game.settings.get("sw5e", "initiativeDexTiebreaker");
|
||||||
|
if ( tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100);
|
||||||
return parts.filter(p => p !== null).join(" + ");
|
return parts.filter(p => p !== null).join(" + ");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: A temporary shim until 0.7.x becomes stable
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
TokenConfig.getTrackedAttributes = function(data, _path=[]) {
|
||||||
|
|
||||||
|
// Track the path and record found attributes
|
||||||
|
const attributes = {
|
||||||
|
"bar": [],
|
||||||
|
"value": []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recursively explore the object
|
||||||
|
for ( let [k, v] of Object.entries(data) ) {
|
||||||
|
let p = _path.concat([k]);
|
||||||
|
|
||||||
|
// Check objects for both a "value" and a "max"
|
||||||
|
if ( v instanceof Object ) {
|
||||||
|
const isBar = ("value" in v) && ("max" in v);
|
||||||
|
if ( isBar ) attributes.bar.push(p);
|
||||||
|
else {
|
||||||
|
const inner = this.getTrackedAttributes(data[k], p);
|
||||||
|
attributes.bar.push(...inner.bar);
|
||||||
|
attributes.value.push(...inner.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise identify values which are numeric or null
|
||||||
|
else if ( Number.isNumeric(v) || (v === null) ) {
|
||||||
|
attributes.value.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
};
|
|
@ -2,14 +2,14 @@
|
||||||
export const SW5E = {};
|
export const SW5E = {};
|
||||||
|
|
||||||
// ASCII Artwork
|
// ASCII Artwork
|
||||||
SW5E.ASCII = `_______________________________
|
SW5E.ASCII = `__________________________________________
|
||||||
_
|
_
|
||||||
| |
|
| |
|
||||||
___| |_ __ _ _ ____ ____ _ _ __ ___
|
___| |_ __ _ _ ____ ____ _ _ __ ___
|
||||||
/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __|
|
/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __|
|
||||||
\__ \ || (_) | | \ V V / (_) | | \__ \
|
\__ \ || (_) | | \ V V / (_) | | \__ \
|
||||||
|___/\__\__/_|_| \_/\_/ \__/_|_| |___/
|
|___/\__\__/_|_| \_/\_/ \__/_|_| |___/
|
||||||
_______________________________`;
|
__________________________________________`;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +25,15 @@ SW5E.abilities = {
|
||||||
"cha": "SW5E.AbilityCha"
|
"cha": "SW5E.AbilityCha"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SW5E.abilityAbbreviations = {
|
||||||
|
"str": "SW5E.AbilityStrAbbr",
|
||||||
|
"dex": "SW5E.AbilityDexAbbr",
|
||||||
|
"con": "SW5E.AbilityConAbbr",
|
||||||
|
"int": "SW5E.AbilityIntAbbr",
|
||||||
|
"wis": "SW5E.AbilityWisAbbr",
|
||||||
|
"cha": "SW5E.AbilityChaAbbr"
|
||||||
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +55,7 @@ SW5E.alignments = {
|
||||||
|
|
||||||
SW5E.weaponProficiencies = {
|
SW5E.weaponProficiencies = {
|
||||||
"sim": "SW5E.WeaponSimpleProficiency",
|
"sim": "SW5E.WeaponSimpleProficiency",
|
||||||
"mar": "SW5E.WeaponMartialProficiency",
|
"mar": "SW5E.WeaponMartialProficiency"
|
||||||
};
|
};
|
||||||
|
|
||||||
SW5E.toolProficiencies = {
|
SW5E.toolProficiencies = {
|
||||||
|
@ -119,9 +128,21 @@ SW5E.abilityActivationTypes = {
|
||||||
"day": SW5E.timePeriods.day,
|
"day": SW5E.timePeriods.day,
|
||||||
"special": SW5E.timePeriods.spec,
|
"special": SW5E.timePeriods.spec,
|
||||||
"legendary": "SW5E.LegAct",
|
"legendary": "SW5E.LegAct",
|
||||||
"lair": "SW5E.LairAct"
|
"lair": "SW5E.LairAct",
|
||||||
|
"crew": "SW5E.VehicleCrewAction"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
SW5E.abilityConsumptionTypes = {
|
||||||
|
"ammo": "SW5E.ConsumeAmmunition",
|
||||||
|
"attribute": "SW5E.ConsumeAttribute",
|
||||||
|
"material": "SW5E.ConsumeMaterial",
|
||||||
|
"charges": "SW5E.ConsumeCharges"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
// Creature Sizes
|
// Creature Sizes
|
||||||
|
@ -196,7 +217,8 @@ SW5E.equipmentTypes = {
|
||||||
"natural": "SW5E.EquipmentNatural",
|
"natural": "SW5E.EquipmentNatural",
|
||||||
"shield": "SW5E.EquipmentShield",
|
"shield": "SW5E.EquipmentShield",
|
||||||
"clothing": "SW5E.EquipmentClothing",
|
"clothing": "SW5E.EquipmentClothing",
|
||||||
"trinket": "SW5E.EquipmentTrinket"
|
"trinket": "SW5E.EquipmentTrinket",
|
||||||
|
"vehicle": "SW5E.EquipmentVehicle"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -231,7 +253,6 @@ SW5E.consumableTypes = {
|
||||||
"trinket": "SW5E.ConsumableTrinket"
|
"trinket": "SW5E.ConsumableTrinket"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -261,10 +282,14 @@ SW5E.damageTypes = {
|
||||||
"sonic": "SW5E.DamageSonic"
|
"sonic": "SW5E.DamageSonic"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Damage Resistance Types
|
||||||
|
SW5E.damageResistanceTypes = mergeObject(duplicate(SW5E.damageTypes), {
|
||||||
|
"physical": "SW5E.DamagePhysical"
|
||||||
|
});
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
// armor Types
|
// armor Types
|
||||||
SW5E.armorpropertiesTypes = {
|
SW5E.armorPropertiesTypes = {
|
||||||
"Absorptive": "SW5E.ArmorProperAbsorptive",
|
"Absorptive": "SW5E.ArmorProperAbsorptive",
|
||||||
"Agile": "SW5E.ArmorProperAgile",
|
"Agile": "SW5E.ArmorProperAgile",
|
||||||
"Anchor": "SW5E.ArmorProperAnchor",
|
"Anchor": "SW5E.ArmorProperAnchor",
|
||||||
|
@ -315,7 +340,8 @@ SW5E.distanceUnits = {
|
||||||
*/
|
*/
|
||||||
SW5E.encumbrance = {
|
SW5E.encumbrance = {
|
||||||
currencyPerWeight: 50,
|
currencyPerWeight: 50,
|
||||||
strMultiplier: 15
|
strMultiplier: 15,
|
||||||
|
vehicleWeightMultiplier: 2000 // 2000 lbs in a ton
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -435,7 +461,7 @@ SW5E.powerPreparationModes = {
|
||||||
"prepared": "SW5E.PowerPrepPrepared"
|
"prepared": "SW5E.PowerPrepPrepared"
|
||||||
};
|
};
|
||||||
|
|
||||||
SW5E.powerUpcastModes = ["always"];
|
SW5E.powerUpcastModes = ["always", "pact", "prepared"];
|
||||||
|
|
||||||
|
|
||||||
SW5E.powerProgression = {
|
SW5E.powerProgression = {
|
||||||
|
@ -604,12 +630,28 @@ SW5E.proficiencyLevels = {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of cover provided by an object.
|
||||||
|
* In cases where multiple pieces of cover are
|
||||||
|
* in play, we take the highest value.
|
||||||
|
*/
|
||||||
|
SW5E.cover = {
|
||||||
|
0: 'SW5E.None',
|
||||||
|
.5: 'SW5E.CoverHalf',
|
||||||
|
.75: 'SW5E.CoverThreeQuarters',
|
||||||
|
1: 'SW5E.CoverTotal'
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
// Condition Types
|
// Condition Types
|
||||||
SW5E.conditionTypes = {
|
SW5E.conditionTypes = {
|
||||||
"blinded": "SW5E.ConBlinded",
|
"blinded": "SW5E.ConBlinded",
|
||||||
"charmed": "SW5E.ConCharmed",
|
"charmed": "SW5E.ConCharmed",
|
||||||
"deafened": "SW5E.ConDeafened",
|
"deafened": "SW5E.ConDeafened",
|
||||||
|
"diseased": "SW5E.ConDiseased",
|
||||||
"exhaustion": "SW5E.ConExhaustion",
|
"exhaustion": "SW5E.ConExhaustion",
|
||||||
"frightened": "SW5E.ConFrightened",
|
"frightened": "SW5E.ConFrightened",
|
||||||
"grappled": "SW5E.ConGrappled",
|
"grappled": "SW5E.ConGrappled",
|
||||||
|
@ -768,8 +810,8 @@ SW5E.characterFlags = {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"powerfulBuild": {
|
"powerfulBuild": {
|
||||||
name: "Powerful Build",
|
name: "SW5E.FlagsPowerfulBuild",
|
||||||
hint: "You count as one size larger when determining your carrying capacity and the weight you can push, drag, or lift.",
|
hint: "SW5E.FlagsPowerfulBuildHint",
|
||||||
section: "Racial Traits",
|
section: "Racial Traits",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
|
@ -798,40 +840,40 @@ SW5E.characterFlags = {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"initiativeAdv": {
|
"initiativeAdv": {
|
||||||
name: "Advantage on Initiative",
|
name: "SW5E.FlagsInitiativeAdv",
|
||||||
hint: "Provided by feats or magical items.",
|
hint: "SW5E.FlagsInitiativeAdvHint",
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"initiativeAlert": {
|
"initiativeAlert": {
|
||||||
name: "Alert Feat",
|
name: "SW5E.FlagsAlert",
|
||||||
hint: "Provides +5 to Initiative.",
|
hint: "SW5E.FlagsAlertHint",
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"jackOfAllTrades": {
|
"jackOfAllTrades": {
|
||||||
name: "Jack of All Trades",
|
name: "SW5E.FlagsJOAT",
|
||||||
hint: "Half-Proficiency to Ability Checks in which you are not already Proficient.",
|
hint: "SW5E.FlagsJOATHint",
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"observantFeat": {
|
"observantFeat": {
|
||||||
name: "Observant Feat",
|
name: "SW5E.FlagsObservant",
|
||||||
hint: "Provides a +5 to passive Perception and Investigation.",
|
hint: "SW5E.FlagsObservantHint",
|
||||||
skills: ['prc','inv'],
|
skills: ['prc','inv'],
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"remarkableAthlete": {
|
"remarkableAthlete": {
|
||||||
name: "Remarkable Athlete.",
|
name: "SW5E.FlagsRemarkableAthlete",
|
||||||
hint: "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
|
hint: "SW5E.FlagsRemarkableAthleteHint",
|
||||||
abilities: ['str','dex','con'],
|
abilities: ['str','dex','con'],
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"weaponCriticalThreshold": {
|
"weaponCriticalThreshold": {
|
||||||
name: "Critical Hit Threshold",
|
name: "SW5E.FlagsCritThreshold",
|
||||||
hint: "Allow for expanded critical range; for example Improved or Superior Critical",
|
hint: "SW5E.FlagsCritThresholdHint",
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Number,
|
type: Number,
|
||||||
placeholder: 20
|
placeholder: 20
|
||||||
|
|
357
module/dice.js
357
module/dice.js
|
@ -1,112 +1,144 @@
|
||||||
export class Dice5e {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A standardized helper function for managing core 5e "d20 rolls"
|
* A standardized helper function for managing core 5e "d20 rolls"
|
||||||
*
|
*
|
||||||
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
|
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
|
||||||
* This chooses the default options of a normal attack with no bonus, Advantage, or Disadvantage respectively
|
* This chooses the default options of a normal attack with no bonus, Advantage, or Disadvantage respectively
|
||||||
*
|
*
|
||||||
* @param {Array} parts The dice roll component parts, excluding the initial d20
|
* @param {Array} parts The dice roll component parts, excluding the initial d20
|
||||||
* @param {Object} data Actor or item data against which to parse the roll
|
* @param {Object} data Actor or item data against which to parse the roll
|
||||||
* @param {Event|object} event The triggering event which initiated the roll
|
* @param {Event|object} event The triggering event which initiated the roll
|
||||||
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
|
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
|
||||||
* @param {string|null} template The HTML template used to render the roll dialog
|
* @param {string|null} template The HTML template used to render the roll dialog
|
||||||
* @param {string|null} title The dice roll UI window title
|
* @param {string|null} title The dice roll UI window title
|
||||||
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
|
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
|
||||||
* @param {string|null} flavor Flavor text to use in the posted chat message
|
* @param {string|null} flavor Flavor text to use in the posted chat message
|
||||||
* @param {Boolean} fastForward Allow fast-forward advantage selection
|
* @param {Boolean} fastForward Allow fast-forward advantage selection
|
||||||
* @param {Function} onClose Callback for actions to take when the dialog form is closed
|
* @param {Function} onClose Callback for actions to take when the dialog form is closed
|
||||||
* @param {Object} dialogOptions Modal dialog options
|
* @param {Object} dialogOptions Modal dialog options
|
||||||
* @param {boolean} advantage Apply advantage to the roll (unless otherwise specified)
|
* @param {boolean} advantage Apply advantage to the roll (unless otherwise specified)
|
||||||
* @param {boolean} disadvantage Apply disadvantage to the roll (unless otherwise specified)
|
* @param {boolean} disadvantage Apply disadvantage to the roll (unless otherwise specified)
|
||||||
* @param {number} critical The value of d20 result which represents a critical success
|
* @param {number} critical The value of d20 result which represents a critical success
|
||||||
* @param {number} fumble The value of d20 result which represents a critical failure
|
* @param {number} fumble The value of d20 result which represents a critical failure
|
||||||
* @param {number} targetValue Assign a target value against which the result of this roll should be compared
|
* @param {number} targetValue Assign a target value against which the result of this roll should be compared
|
||||||
* @param {boolean} elvenAccuracy Allow Elven Accuracy to modify this roll?
|
* @param {boolean} elvenAccuracy Allow Elven Accuracy to modify this roll?
|
||||||
* @param {boolean} halflingLucky Allow Halfling Luck to modify this roll?
|
* @param {boolean} halflingLucky Allow Halfling Luck to modify this roll?
|
||||||
*
|
* @param {boolean} reliableTalent Allow Reliable Talent to modify this roll?
|
||||||
* @return {Promise} A Promise which resolves once the roll workflow has completed
|
* @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll
|
||||||
|
* @param {object} messageData Additional data which is applied to the created Chat Message, if any
|
||||||
|
*
|
||||||
|
* @return {Promise} A Promise which resolves once the roll workflow has completed
|
||||||
*/
|
*/
|
||||||
static async d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null,
|
export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null,
|
||||||
flavor=null, fastForward=null, onClose, dialogOptions,
|
flavor=null, fastForward=null, dialogOptions,
|
||||||
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null,
|
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null,
|
||||||
elvenAccuracy=false, halflingLucky=false}={}) {
|
elvenAccuracy=false, halflingLucky=false, reliableTalent=false,
|
||||||
|
chatMessage=true, messageData={}}={}) {
|
||||||
// Handle input arguments
|
|
||||||
flavor = flavor || title;
|
// Prepare Message Data
|
||||||
speaker = speaker || ChatMessage.getSpeaker();
|
messageData.flavor = flavor || title;
|
||||||
|
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
||||||
|
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
||||||
parts = parts.concat(["@bonus"]);
|
parts = parts.concat(["@bonus"]);
|
||||||
rollMode = rollMode || game.settings.get("core", "rollMode");
|
|
||||||
let rolled = false;
|
// Handle fast-forward events
|
||||||
|
let adv = 0;
|
||||||
// Define inner roll function
|
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||||
const _roll = function(parts, adv, form=null) {
|
if (fastForward) {
|
||||||
|
if ( advantage || event.altKey ) adv = 1;
|
||||||
// Determine the d20 roll and modifiers
|
else if ( disadvantage || event.ctrlKey || event.metaKey ) adv = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the inner roll function
|
||||||
|
const _roll = (parts, adv, form) => {
|
||||||
|
|
||||||
|
// Determine the d20 roll and modifiers
|
||||||
let nd = 1;
|
let nd = 1;
|
||||||
let mods = halflingLucky ? "r=1" : "";
|
let mods = halflingLucky ? "r=1" : "";
|
||||||
|
|
||||||
// Handle advantage
|
// Handle advantage
|
||||||
if ( adv === 1 ) {
|
if (adv === 1) {
|
||||||
nd = elvenAccuracy ? 3 : 2;
|
nd = elvenAccuracy ? 3 : 2;
|
||||||
flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
|
messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
|
||||||
mods += "kh";
|
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true;
|
||||||
|
mods += "kh";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle disadvantage
|
// Handle disadvantage
|
||||||
else if ( adv === -1 ) {
|
else if (adv === -1) {
|
||||||
nd = 2;
|
nd = 2;
|
||||||
flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
||||||
mods += "kl";
|
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true;
|
||||||
|
mods += "kl";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the d20 roll
|
// Prepend the d20 roll
|
||||||
parts.unshift(`${nd}d20${mods}`);
|
let formula = `${nd}d20${mods}`;
|
||||||
|
if (reliableTalent) formula = `{${nd}d20${mods},10}kh`;
|
||||||
|
parts.unshift(formula);
|
||||||
|
|
||||||
// Optionally include a situational bonus
|
// Optionally include a situational bonus
|
||||||
if ( form !== null ) data['bonus'] = form.bonus.value;
|
if ( form ) {
|
||||||
if ( !data["bonus"] ) parts.pop();
|
data['bonus'] = form.bonus.value;
|
||||||
|
messageOptions.rollMode = form.rollMode.value;
|
||||||
|
}
|
||||||
|
if (!data["bonus"]) parts.pop();
|
||||||
|
|
||||||
// Optionally include an ability score selection (used for tool checks)
|
// Optionally include an ability score selection (used for tool checks)
|
||||||
const ability = form ? form.ability : null;
|
const ability = form ? form.ability : null;
|
||||||
if ( ability && ability.value ) {
|
if (ability && ability.value) {
|
||||||
data.ability = ability.value;
|
data.ability = ability.value;
|
||||||
const abl = data.abilities[data.ability];
|
const abl = data.abilities[data.ability];
|
||||||
if ( abl ) {
|
if (abl) {
|
||||||
data.mod = abl.mod;
|
data.mod = abl.mod;
|
||||||
flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
|
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the roll and flag critical thresholds on the d20
|
// Execute the roll
|
||||||
let roll = new Roll(parts.join(" + "), data).roll();
|
let roll = new Roll(parts.join(" + "), data);
|
||||||
const d20 = roll.parts[0];
|
try {
|
||||||
d20.options.critical = critical;
|
roll.roll();
|
||||||
d20.options.fumble = fumble;
|
} catch (err) {
|
||||||
if ( targetValue ) d20.options.target = targetValue;
|
console.error(err);
|
||||||
|
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
|
||||||
// Convert the roll to a chat message and return the roll
|
return null;
|
||||||
rollMode = form ? form.rollMode.value : rollMode;
|
|
||||||
roll.toMessage({
|
|
||||||
speaker: speaker,
|
|
||||||
flavor: flavor
|
|
||||||
}, { rollMode });
|
|
||||||
rolled = true;
|
|
||||||
return roll;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine whether the roll can be fast-forward
|
|
||||||
if ( fastForward === null ) {
|
|
||||||
fastForward = event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally allow fast-forwarding to specify advantage or disadvantage
|
// Flag d20 options for any 20-sided dice in the roll
|
||||||
if ( fastForward ) {
|
for (let d of roll.dice) {
|
||||||
if ( advantage || event.altKey ) return _roll(parts, 1);
|
if (d.faces === 20) {
|
||||||
else if ( disadvantage || event.ctrlKey || event.metaKey ) return _roll(parts, -1);
|
d.options.critical = critical;
|
||||||
else return _roll(parts, 0);
|
d.options.fumble = fumble;
|
||||||
|
if (targetValue) d.options.target = targetValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If reliable talent was applied, add it to the flavor text
|
||||||
|
if (reliableTalent && roll.dice[0].total < 10) {
|
||||||
|
messageData.flavor += ` (${game.i18n.localize("SW5E.FlagsReliableTalent")})`;
|
||||||
|
}
|
||||||
|
return roll;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the Roll instance
|
||||||
|
const roll = fastForward ? _roll(parts, adv) :
|
||||||
|
await _d20RollDialog({template, title, parts, data, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll});
|
||||||
|
|
||||||
|
// Create a Chat Message
|
||||||
|
if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions);
|
||||||
|
return roll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Present a Dialog form which creates a d20 roll once submitted
|
||||||
|
* @return {Promise<Roll>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) {
|
||||||
|
|
||||||
// Render modal dialog
|
// Render modal dialog
|
||||||
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
||||||
let dialogData = {
|
let dialogData = {
|
||||||
|
@ -119,7 +151,6 @@ export class Dice5e {
|
||||||
const html = await renderTemplate(template, dialogData);
|
const html = await renderTemplate(template, dialogData);
|
||||||
|
|
||||||
// Create the Dialog window
|
// Create the Dialog window
|
||||||
let roll;
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
new Dialog({
|
new Dialog({
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -127,26 +158,24 @@ export class Dice5e {
|
||||||
buttons: {
|
buttons: {
|
||||||
advantage: {
|
advantage: {
|
||||||
label: game.i18n.localize("SW5E.Advantage"),
|
label: game.i18n.localize("SW5E.Advantage"),
|
||||||
callback: html => roll = _roll(parts, 1, html[0].children[0])
|
callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
label: game.i18n.localize("SW5E.Normal"),
|
label: game.i18n.localize("SW5E.Normal"),
|
||||||
callback: html => roll = _roll(parts, 0, html[0].children[0])
|
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
|
||||||
},
|
},
|
||||||
disadvantage: {
|
disadvantage: {
|
||||||
label: game.i18n.localize("SW5E.Disadvantage"),
|
label: game.i18n.localize("SW5E.Disadvantage"),
|
||||||
callback: html => roll = _roll(parts, -1, html[0].children[0])
|
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
default: "normal",
|
default: "normal",
|
||||||
close: html => {
|
close: () => resolve(null)
|
||||||
if (onClose) onClose(html, parts, data);
|
|
||||||
resolve(rolled ? roll : false)
|
|
||||||
}
|
|
||||||
}, dialogOptions).render(true);
|
}, dialogOptions).render(true);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,83 +198,103 @@ export class Dice5e {
|
||||||
* @param {Boolean} fastForward Allow fast-forward advantage selection
|
* @param {Boolean} fastForward Allow fast-forward advantage selection
|
||||||
* @param {Function} onClose Callback for actions to take when the dialog form is closed
|
* @param {Function} onClose Callback for actions to take when the dialog form is closed
|
||||||
* @param {Object} dialogOptions Modal dialog options
|
* @param {Object} dialogOptions Modal dialog options
|
||||||
|
* @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll
|
||||||
|
* @param {object} messageData Additional data which is applied to the created Chat Message, if any
|
||||||
*
|
*
|
||||||
* @return {Promise} A Promise which resolves once the roll workflow has completed
|
* @return {Promise} A Promise which resolves once the roll workflow has completed
|
||||||
*/
|
*/
|
||||||
static async damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor,
|
export async function damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor,
|
||||||
allowCritical=true, critical=false, fastForward=null, onClose, dialogOptions}) {
|
allowCritical=true, critical=false, fastForward=null, dialogOptions, chatMessage=true, messageData={}}={}) {
|
||||||
|
|
||||||
// Handle input arguments
|
// Prepare Message Data
|
||||||
flavor = flavor || title;
|
messageData.flavor = flavor || title;
|
||||||
speaker = speaker || ChatMessage.getSpeaker();
|
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
||||||
rollMode = game.settings.get("core", "rollMode");
|
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
||||||
let rolled = false;
|
parts = parts.concat(["@bonus"]);
|
||||||
|
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||||
|
|
||||||
// Define inner roll function
|
// Define inner roll function
|
||||||
const _roll = function(parts, crit, form) {
|
const _roll = function(parts, crit, form) {
|
||||||
data['bonus'] = form ? form.bonus.value : 0;
|
|
||||||
|
// Optionally include a situational bonus
|
||||||
|
if ( form ) {
|
||||||
|
data['bonus'] = form.bonus.value;
|
||||||
|
messageOptions.rollMode = form.rollMode.value;
|
||||||
|
}
|
||||||
|
if (!data["bonus"]) parts.pop();
|
||||||
|
|
||||||
|
// Create the damage roll
|
||||||
let roll = new Roll(parts.join("+"), data);
|
let roll = new Roll(parts.join("+"), data);
|
||||||
|
|
||||||
// Modify the damage formula for critical hits
|
// Modify the damage formula for critical hits
|
||||||
if ( crit === true ) {
|
if ( crit === true ) {
|
||||||
let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0;
|
let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0;
|
||||||
let mult = 2;
|
let mult = 2;
|
||||||
roll.alter(add, mult);
|
// TODO Backwards compatibility - REMOVE LATER
|
||||||
flavor = `${flavor} (${game.i18n.localize("SW5E.Critical")})`;
|
if (isNewerVersion(game.data.version, "0.6.9")) roll.alter(mult, add);
|
||||||
}
|
else roll.alter(add, mult);
|
||||||
|
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
|
||||||
// Convert the roll to a chat message
|
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
|
||||||
rollMode = form ? form.rollMode.value : rollMode;
|
|
||||||
roll.toMessage({
|
|
||||||
speaker: speaker,
|
|
||||||
flavor: flavor
|
|
||||||
}, { rollMode });
|
|
||||||
rolled = true;
|
|
||||||
return roll;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine whether the roll can be fast-forward
|
|
||||||
if ( fastForward === null ) {
|
|
||||||
fastForward = event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modify the roll and handle fast-forwarding
|
// Execute the roll
|
||||||
if ( fastForward ) return _roll(parts, critical || event.altKey);
|
try {
|
||||||
else parts = parts.concat(["@bonus"]);
|
return roll.roll();
|
||||||
|
} catch(err) {
|
||||||
|
console.error(err);
|
||||||
|
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Render modal dialog
|
// Create the Roll instance
|
||||||
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
const roll = fastForward ? _roll(parts, critical || event.altKey) : await _damageRollDialog({
|
||||||
let dialogData = {
|
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll
|
||||||
formula: parts.join(" + "),
|
});
|
||||||
data: data,
|
|
||||||
rollMode: rollMode,
|
// Create a Chat Message
|
||||||
rollModes: CONFIG.Dice.rollModes
|
if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions);
|
||||||
};
|
return roll;
|
||||||
const html = await renderTemplate(template, dialogData);
|
|
||||||
|
|
||||||
// Create the Dialog window
|
}
|
||||||
let roll;
|
|
||||||
return new Promise(resolve => {
|
/* -------------------------------------------- */
|
||||||
new Dialog({
|
|
||||||
title: title,
|
/**
|
||||||
content: html,
|
* Present a Dialog form which creates a damage roll once submitted
|
||||||
buttons: {
|
* @return {Promise<Roll>}
|
||||||
critical: {
|
* @private
|
||||||
condition: allowCritical,
|
*/
|
||||||
label: game.i18n.localize("SW5E.CriticalHit"),
|
async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll}={}) {
|
||||||
callback: html => roll = _roll(parts, true, html[0].children[0])
|
|
||||||
},
|
// Render modal dialog
|
||||||
normal: {
|
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
||||||
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
|
let dialogData = {
|
||||||
callback: html => roll = _roll(parts, false, html[0].children[0])
|
formula: parts.join(" + "),
|
||||||
},
|
data: data,
|
||||||
},
|
rollMode: rollMode,
|
||||||
default: "normal",
|
rollModes: CONFIG.Dice.rollModes
|
||||||
close: html => {
|
};
|
||||||
if (onClose) onClose(html, parts, data);
|
const html = await renderTemplate(template, dialogData);
|
||||||
resolve(rolled ? roll : false);
|
|
||||||
}
|
// Create the Dialog window
|
||||||
}, dialogOptions).render(true);
|
return new Promise(resolve => {
|
||||||
});
|
new Dialog({
|
||||||
}
|
title: title,
|
||||||
|
content: html,
|
||||||
|
buttons: {
|
||||||
|
critical: {
|
||||||
|
condition: allowCritical,
|
||||||
|
label: game.i18n.localize("SW5E.CriticalHit"),
|
||||||
|
callback: html => resolve(roll(parts, true, html[0].querySelector("form")))
|
||||||
|
},
|
||||||
|
normal: {
|
||||||
|
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
|
||||||
|
callback: html => resolve(roll(parts, false, html[0].querySelector("form")))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: "normal",
|
||||||
|
close: () => resolve(null)
|
||||||
|
}, dialogOptions).render(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Dice5e } from "../dice.js";
|
import {d20Roll, damageRoll} from "../dice.js";
|
||||||
import { AbilityUseDialog } from "../apps/ability-use-dialog.js";
|
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
||||||
import { AbilityTemplate } from "../pixi/ability-template.js";
|
import AbilityTemplate from "../pixi/ability-template.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override and extend the basic :class:`Item` implementation
|
* Override and extend the basic :class:`Item` implementation
|
||||||
*/
|
*/
|
||||||
export class Item5e extends Item {
|
export default class Item5e extends Item {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Item Properties */
|
/* Item Properties */
|
||||||
|
@ -20,14 +20,34 @@ export class Item5e extends Item {
|
||||||
if (!("ability" in itemData)) return null;
|
if (!("ability" in itemData)) return null;
|
||||||
|
|
||||||
// Case 1 - defined directly by the item
|
// Case 1 - defined directly by the item
|
||||||
if ( itemData.ability ) return itemData.ability;
|
if (itemData.ability) return itemData.ability;
|
||||||
|
|
||||||
// Case 2 - inferred from a parent actor
|
// Case 2 - inferred from a parent actor
|
||||||
else if ( this.actor ) {
|
else if (this.actor) {
|
||||||
const actorData = this.actor.data.data;
|
const actorData = this.actor.data.data;
|
||||||
if ( this.data.type === "power" ) return actorData.attributes.powercasting || "int";
|
|
||||||
else if ( this.data.type === "tool" ) return "int";
|
// Powers - Use Actor powercasting modifier
|
||||||
else return "str";
|
if (this.data.type === "power") return actorData.attributes.powercasting || "int";
|
||||||
|
|
||||||
|
// Tools - default to Intelligence
|
||||||
|
else if (this.data.type === "tool") return "int";
|
||||||
|
|
||||||
|
// Weapons
|
||||||
|
else if (this.data.type === "weapon") {
|
||||||
|
const wt = itemData.weaponType;
|
||||||
|
|
||||||
|
// Melee weapons - Str or Dex if Finesse (PHB pg. 147)
|
||||||
|
if ( ["simpleVW", "martialVW", "simpleLW", "martialLW"].includes(wt) ) {
|
||||||
|
if (itemData.properties.fin === true) { // Finesse weapons
|
||||||
|
return (actorData.abilities["dex"].mod >= actorData.abilities["str"].mod) ? "dex" : "str";
|
||||||
|
}
|
||||||
|
return "str";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranged weapons - Dex (PH p.194)
|
||||||
|
else if ( ["simpleB", "martialB"].includes(wt) ) return "dex";
|
||||||
|
}
|
||||||
|
return "str";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3 - unknown
|
// Case 3 - unknown
|
||||||
|
@ -149,15 +169,16 @@ export class Item5e extends Item {
|
||||||
arr.push(c[0].titleCase().slice(0, 1));
|
arr.push(c[0].titleCase().slice(0, 1));
|
||||||
return arr;
|
return arr;
|
||||||
}, []);
|
}, []);
|
||||||
|
labels.materials = data?.materials?.value ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feat Items
|
// Feat Items
|
||||||
else if ( itemData.type === "feat" ) {
|
else if ( itemData.type === "feat" ) {
|
||||||
const act = data.activation;
|
const act = data.activation;
|
||||||
if ( act && (act.type === C.abilityActivationTypes.legendary) ) labels.featType = "Legendary Action";
|
if ( act && (act.type === C.abilityActivationTypes.legendary) ) labels.featType = game.i18n.localize("SW5E.LegendaryActionLabel");
|
||||||
else if ( act && (act.type === C.abilityActivationTypes.lair) ) labels.featType = "Lair Action";
|
else if ( act && (act.type === C.abilityActivationTypes.lair) ) labels.featType = game.i18n.localize("SW5E.LairActionLabel");
|
||||||
else if ( act && act.type ) labels.featType = data.damage.length ? "Attack" : "Action";
|
else if ( act && act.type ) labels.featType = game.i18n.localize(data.damage.length ? "SW5E.Attack" : "SW5E.Action");
|
||||||
else labels.featType = "Passive";
|
else labels.featType = game.i18n.localize("SW5E.Passive");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Species Items
|
// Species Items
|
||||||
|
@ -167,7 +188,7 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
// Equipment Items
|
// Equipment Items
|
||||||
else if ( itemData.type === "equipment" ) {
|
else if ( itemData.type === "equipment" ) {
|
||||||
labels.armor = data.armor.value ? `${data.armor.value} AC` : "";
|
labels.armor = data.armor.value ? `${data.armor.value} ${game.i18n.localize("SW5E.AC")}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activated Items
|
// Activated Items
|
||||||
|
@ -201,7 +222,7 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
// Recharge Label
|
// Recharge Label
|
||||||
let chg = data.recharge || {};
|
let chg = data.recharge || {};
|
||||||
labels.recharge = `Recharge [${chg.value}${parseInt(chg.value) < 6 ? "+" : ""}]`;
|
labels.recharge = `${game.i18n.localize("SW5E.Recharge")} [${chg.value}${parseInt(chg.value) < 6 ? "+" : ""}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item Actions
|
// Item Actions
|
||||||
|
@ -216,7 +237,7 @@ export class Item5e extends Item {
|
||||||
} else { // Un-owned items
|
} else { // Un-owned items
|
||||||
if ( save.scaling !== "flat" ) save.dc = null;
|
if ( save.scaling !== "flat" ) save.dc = null;
|
||||||
}
|
}
|
||||||
labels.save = save.ability ? `DC ${save.dc || ""} ${C.abilities[save.ability]}` : "";
|
labels.save = save.ability ? `${game.i18n.localize("SW5E.AbbreviationDC")} ${save.dc || ""} ${C.abilities[save.ability]}` : "";
|
||||||
|
|
||||||
// Damage
|
// Damage
|
||||||
let dam = data.damage || {};
|
let dam = data.damage || {};
|
||||||
|
@ -234,9 +255,13 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options
|
* Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options
|
||||||
|
* @param {boolean} [configureDialog] Display a configuration dialog for the item roll, if applicable?
|
||||||
|
* @param {string} [rollMode] The roll display mode with which to display (or not) the card
|
||||||
|
* @param {boolean} [createMessage] Whether to automatically create a chat message (if true) or simply return
|
||||||
|
* the prepared chat message data (if false).
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
async roll({configureDialog=true}={}) {
|
async roll({configureDialog=true, rollMode=null, createMessage=true}={}) {
|
||||||
|
|
||||||
// Basic template rendering data
|
// Basic template rendering data
|
||||||
const token = this.actor.token;
|
const token = this.actor.token;
|
||||||
|
@ -259,10 +284,17 @@ export class Item5e extends Item {
|
||||||
if (this.data.type === "feat") {
|
if (this.data.type === "feat") {
|
||||||
let configured = await this._rollFeat(configureDialog);
|
let configured = await this._rollFeat(configureDialog);
|
||||||
if ( configured === false ) return;
|
if ( configured === false ) return;
|
||||||
|
} else if ( this.data.type === "consumable" ) {
|
||||||
|
let configured = await this._rollConsumable(configureDialog);
|
||||||
|
if ( configured === false ) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For items which consume a resource, handle that here
|
||||||
|
const allowed = await this._handleResourceConsumption({isCard: true, isAttack: false});
|
||||||
|
if ( allowed === false ) return;
|
||||||
|
|
||||||
// Render the chat card template
|
// Render the chat card template
|
||||||
const templateType = ["tool", "consumable"].includes(this.data.type) ? this.data.type : "item";
|
const templateType = ["tool"].includes(this.data.type) ? this.data.type : "item";
|
||||||
const template = `systems/sw5e/templates/chat/${templateType}-card.html`;
|
const template = `systems/sw5e/templates/chat/${templateType}-card.html`;
|
||||||
const html = await renderTemplate(template, templateData);
|
const html = await renderTemplate(template, templateData);
|
||||||
|
|
||||||
|
@ -279,12 +311,90 @@ export class Item5e extends Item {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle default roll mode
|
// Toggle default roll mode
|
||||||
let rollMode = game.settings.get("core", "rollMode");
|
rollMode = rollMode || game.settings.get("core", "rollMode");
|
||||||
if ( ["gmroll", "blindroll"].includes(rollMode) ) chatData["whisper"] = ChatMessage.getWhisperIDs("GM");
|
if ( ["gmroll", "blindroll"].includes(rollMode) ) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM");
|
||||||
if ( rollMode === "blindroll" ) chatData["blind"] = true;
|
if ( rollMode === "blindroll" ) chatData["blind"] = true;
|
||||||
|
|
||||||
// Create the chat message
|
// Create the chat message
|
||||||
return ChatMessage.create(chatData);
|
if ( createMessage ) return ChatMessage.create(chatData);
|
||||||
|
else return chatData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For items which consume a resource, handle the consumption of that resource when the item is used.
|
||||||
|
* There are four types of ability consumptions which are handled:
|
||||||
|
* 1. Ammunition (on attack rolls)
|
||||||
|
* 2. Attributes (on card usage)
|
||||||
|
* 3. Materials (on card usage)
|
||||||
|
* 4. Item Charges (on card usage)
|
||||||
|
*
|
||||||
|
* @param {boolean} isCard Is the item card being played?
|
||||||
|
* @param {boolean} isAttack Is an attack roll being made?
|
||||||
|
* @return {Promise<boolean>} Can the item card or attack roll be allowed to proceed?
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _handleResourceConsumption({isCard=false, isAttack=false}={}) {
|
||||||
|
const itemData = this.data.data;
|
||||||
|
const consume = itemData.consume || {};
|
||||||
|
if ( !consume.type ) return true;
|
||||||
|
const actor = this.actor;
|
||||||
|
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
|
||||||
|
const amount = parseInt(consume.amount || 1);
|
||||||
|
|
||||||
|
// Only handle certain types for certain actions
|
||||||
|
if ( ((consume.type === "ammo") && !isAttack ) || ((consume.type !== "ammo") && !isCard) ) return true;
|
||||||
|
|
||||||
|
// No consumed target set
|
||||||
|
if ( !consume.target ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the consumed resource and it's quantity
|
||||||
|
let consumed = null;
|
||||||
|
let quantity = 0;
|
||||||
|
switch ( consume.type ) {
|
||||||
|
case "attribute":
|
||||||
|
consumed = getProperty(actor.data.data, consume.target);
|
||||||
|
quantity = consumed || 0;
|
||||||
|
break;
|
||||||
|
case "ammo":
|
||||||
|
case "material":
|
||||||
|
consumed = actor.items.get(consume.target);
|
||||||
|
quantity = consumed ? consumed.data.data.quantity : 0;
|
||||||
|
break;
|
||||||
|
case "charges":
|
||||||
|
consumed = actor.items.get(consume.target);
|
||||||
|
quantity = consumed ? consumed.data.data.uses.value : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the consumed resource is available
|
||||||
|
if ( [null, undefined].includes(consumed) ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoSource", {name: this.name, type: typeLabel}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let remaining = quantity - amount;
|
||||||
|
if ( remaining < 0) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoQuantity", {name: this.name, type: typeLabel}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the consumed resource
|
||||||
|
switch ( consume.type ) {
|
||||||
|
case "attribute":
|
||||||
|
await this.actor.update({[`data.${consume.target}`]: remaining});
|
||||||
|
break;
|
||||||
|
case "ammo":
|
||||||
|
case "material":
|
||||||
|
await consumed.update({"data.quantity": remaining});
|
||||||
|
break;
|
||||||
|
case "charges":
|
||||||
|
await consumed.update({"data.uses.value": remaining});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -298,27 +408,35 @@ export class Item5e extends Item {
|
||||||
if ( this.data.type !== "feat" ) throw new Error("Wrong Item type");
|
if ( this.data.type !== "feat" ) throw new Error("Wrong Item type");
|
||||||
|
|
||||||
// Configure whether to consume a limited use or to place a template
|
// Configure whether to consume a limited use or to place a template
|
||||||
const usesRecharge = !!this.data.data.recharge.value;
|
const charge = this.data.data.recharge;
|
||||||
const uses = this.data.data.uses;
|
const uses = this.data.data.uses;
|
||||||
let usesCharges = !!uses.per && (uses.max > 0);
|
let usesCharges = !!uses.per && (uses.max > 0);
|
||||||
let placeTemplate = false;
|
let placeTemplate = false;
|
||||||
let consume = usesRecharge || usesCharges;
|
let consume = charge.value || usesCharges;
|
||||||
|
|
||||||
// Determine whether the feat uses charges
|
// Determine whether the feat uses charges
|
||||||
configureDialog = configureDialog && (consume || this.hasAreaTarget);
|
configureDialog = configureDialog && (consume || this.hasAreaTarget);
|
||||||
if ( configureDialog ) {
|
if ( configureDialog ) {
|
||||||
const usage = await AbilityUseDialog.create(this);
|
const usage = await AbilityUseDialog.create(this);
|
||||||
if ( usage === null ) return false;
|
if ( usage === null ) return false;
|
||||||
consume = Boolean(usage.get("consume"));
|
consume = Boolean(usage.get("consumeUse"));
|
||||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
placeTemplate = Boolean(usage.get("placeTemplate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Item data
|
// Update Item data
|
||||||
const current = getProperty(this.data, "data.uses.value") || 0;
|
const current = getProperty(this.data, "data.uses.value") || 0;
|
||||||
if ( consume && usesRecharge ) {
|
if ( consume && charge.value ) {
|
||||||
await this.update({"data.recharge.charged": false});
|
if ( !charge.charged ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else await this.update({"data.recharge.charged": false});
|
||||||
}
|
}
|
||||||
else if ( consume && usesCharges ) {
|
else if ( consume && usesCharges ) {
|
||||||
|
if ( uses.value <= 0 ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
await this.update({"data.uses.value": Math.max(current - 1, 0)});
|
await this.update({"data.uses.value": Math.max(current - 1, 0)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,14 +458,13 @@ export class Item5e extends Item {
|
||||||
* @param {Object} htmlOptions Options used by the TextEditor.enrichHTML function
|
* @param {Object} htmlOptions Options used by the TextEditor.enrichHTML function
|
||||||
* @return {Object} An object of chat data to render
|
* @return {Object} An object of chat data to render
|
||||||
*/
|
*/
|
||||||
getChatData(htmlOptions) {
|
getChatData(htmlOptions={}) {
|
||||||
const data = duplicate(this.data.data);
|
const data = duplicate(this.data.data);
|
||||||
const labels = this.labels;
|
const labels = this.labels;
|
||||||
|
|
||||||
// Rich text description
|
// Rich text description
|
||||||
data.description.value = TextEditor.enrichHTML(data.description.value, htmlOptions);
|
data.description.value = TextEditor.enrichHTML(data.description.value, htmlOptions);
|
||||||
|
|
||||||
|
|
||||||
// Item type specific properties
|
// Item type specific properties
|
||||||
const props = [];
|
const props = [];
|
||||||
const fn = this[`_${this.data.type}ChatData`];
|
const fn = this[`_${this.data.type}ChatData`];
|
||||||
|
@ -356,16 +473,16 @@ export class Item5e extends Item {
|
||||||
// General equipment properties
|
// General equipment properties
|
||||||
if ( data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type) ) {
|
if ( data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type) ) {
|
||||||
props.push(
|
props.push(
|
||||||
data.equipped ? "Equipped" : "Not Equipped",
|
game.i18n.localize(data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped"),
|
||||||
data.proficient ? "Proficient": "Not Proficient",
|
game.i18n.localize(data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ability activation properties
|
// Ability activation properties
|
||||||
if ( data.hasOwnProperty("activation") ) {
|
if ( data.hasOwnProperty("activation") ) {
|
||||||
props.push(
|
props.push(
|
||||||
|
labels.activation + (data.activation?.condition ? ` (${data.activation.condition})` : ""),
|
||||||
labels.target,
|
labels.target,
|
||||||
labels.activation,
|
|
||||||
labels.range,
|
labels.range,
|
||||||
labels.duration
|
labels.duration
|
||||||
);
|
);
|
||||||
|
@ -386,7 +503,7 @@ export class Item5e extends Item {
|
||||||
props.push(
|
props.push(
|
||||||
CONFIG.SW5E.equipmentTypes[data.armor.type],
|
CONFIG.SW5E.equipmentTypes[data.armor.type],
|
||||||
labels.armor || null,
|
labels.armor || null,
|
||||||
data.stealth.value ? "Stealth Disadvantage" : null,
|
data.stealth.value ? game.i18n.localize("SW5E.StealthDisadvantage") : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,7 +528,7 @@ export class Item5e extends Item {
|
||||||
_consumableChatData(data, labels, props) {
|
_consumableChatData(data, labels, props) {
|
||||||
props.push(
|
props.push(
|
||||||
CONFIG.SW5E.consumableTypes[data.consumableType],
|
CONFIG.SW5E.consumableTypes[data.consumableType],
|
||||||
data.uses.value + "/" + data.uses.max + " Charges"
|
data.uses.value + "/" + data.uses.max + " " + game.i18n.localize("SW5E.Charges")
|
||||||
);
|
);
|
||||||
data.hasCharges = data.uses.value >= 0;
|
data.hasCharges = data.uses.value >= 0;
|
||||||
}
|
}
|
||||||
|
@ -437,8 +554,8 @@ export class Item5e extends Item {
|
||||||
*/
|
*/
|
||||||
_lootChatData(data, labels, props) {
|
_lootChatData(data, labels, props) {
|
||||||
props.push(
|
props.push(
|
||||||
"Loot",
|
game.i18n.localize("SW5E.ItemTypeLoot"),
|
||||||
data.weight ? data.weight + " lbs." : null
|
data.weight ? data.weight + " " + game.i18n.localize("SW5E.AbbreviationLbs") : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +569,7 @@ export class Item5e extends Item {
|
||||||
_powerChatData(data, labels, props) {
|
_powerChatData(data, labels, props) {
|
||||||
props.push(
|
props.push(
|
||||||
labels.level,
|
labels.level,
|
||||||
labels.components,
|
labels.components + (labels.materials ? ` (${labels.materials})` : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,17 +589,19 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
||||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
* Rely upon the d20Roll logic for the core implementation
|
||||||
*
|
*
|
||||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
* @param {object} options Roll options which are configured and provided to the d20Roll function
|
||||||
|
* @return {Promise<Roll|null>} A Promise which resolves to the created Roll instance
|
||||||
*/
|
*/
|
||||||
rollAttack(options={}) {
|
async rollAttack(options={}) {
|
||||||
const itemData = this.data.data;
|
const itemData = this.data.data;
|
||||||
const actorData = this.actor.data.data;
|
const actorData = this.actor.data.data;
|
||||||
const flags = this.actor.data.flags.sw5e || {};
|
const flags = this.actor.data.flags.sw5e || {};
|
||||||
if ( !this.hasAttack ) {
|
if ( !this.hasAttack ) {
|
||||||
throw new Error("You may not place an Attack Roll with this Item.");
|
throw new Error("You may not place an Attack Roll with this Item.");
|
||||||
}
|
}
|
||||||
|
let title = `${this.name} - ${game.i18n.localize("SW5E.AttackRoll")}`;
|
||||||
const rollData = this.getRollData();
|
const rollData = this.getRollData();
|
||||||
|
|
||||||
// Define Roll bonuses
|
// Define Roll bonuses
|
||||||
|
@ -492,26 +611,44 @@ export class Item5e extends Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attack Bonus
|
// Attack Bonus
|
||||||
const actorBonus = actorData.bonuses[itemData.actionType] || {};
|
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
|
||||||
if ( itemData.attackBonus || actorBonus.attack ) {
|
if ( itemData.attackBonus || actorBonus.attack ) {
|
||||||
parts.push("@atk");
|
parts.push("@atk");
|
||||||
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
|
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ammunition Bonus
|
||||||
|
delete this._ammo;
|
||||||
|
const consume = itemData.consume;
|
||||||
|
if ( consume?.type === "ammo" ) {
|
||||||
|
const ammo = this.actor.items.get(consume.target);
|
||||||
|
const q = ammo.data.data.quantity;
|
||||||
|
if ( q && (q - consume.amount >= 0) ) {
|
||||||
|
let ammoBonus = ammo.data.data.attackBonus;
|
||||||
|
if ( ammoBonus ) {
|
||||||
|
parts.push("@ammo");
|
||||||
|
rollData["ammo"] = ammoBonus;
|
||||||
|
title += ` [${ammo.name}]`;
|
||||||
|
this._ammo = ammo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compose roll options
|
// Compose roll options
|
||||||
const rollConfig = {
|
const rollConfig = mergeObject({
|
||||||
event: options.event,
|
|
||||||
parts: parts,
|
parts: parts,
|
||||||
actor: this.actor,
|
actor: this.actor,
|
||||||
data: rollData,
|
data: rollData,
|
||||||
title: `${this.name} - Attack Roll`,
|
title: title,
|
||||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||||
dialogOptions: {
|
dialogOptions: {
|
||||||
width: 400,
|
width: 400,
|
||||||
top: options.event ? options.event.clientY - 80 : null,
|
top: options.event ? options.event.clientY - 80 : null,
|
||||||
left: window.innerWidth - 710
|
left: window.innerWidth - 710
|
||||||
}
|
},
|
||||||
};
|
messageData: {"flags.sw5e.roll": {type: "attack", itemId: this.id }}
|
||||||
|
}, options);
|
||||||
|
rollConfig.event = options.event;
|
||||||
|
|
||||||
// Expanded weapon critical threshold
|
// Expanded weapon critical threshold
|
||||||
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
|
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
|
||||||
|
@ -529,16 +666,22 @@ export class Item5e extends Item {
|
||||||
if ( flags.halflingLucky ) rollConfig.halflingLucky = true;
|
if ( flags.halflingLucky ) rollConfig.halflingLucky = true;
|
||||||
|
|
||||||
// Invoke the d20 roll helper
|
// Invoke the d20 roll helper
|
||||||
return Dice5e.d20Roll(rollConfig);
|
const roll = await d20Roll(rollConfig);
|
||||||
|
if ( roll === false ) return null;
|
||||||
|
|
||||||
|
// Handle resource consumption if the attack roll was made
|
||||||
|
const allowed = await this._handleResourceConsumption({isCard: false, isAttack: true});
|
||||||
|
if ( allowed === false ) return null;
|
||||||
|
return roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Place a damage roll using an item (weapon, feat, power, or equipment)
|
* Place a damage roll using an item (weapon, feat, power, or equipment)
|
||||||
* Rely upon the Dice5e.damageRoll logic for the core implementation
|
* Rely upon the damageRoll logic for the core implementation
|
||||||
*
|
*
|
||||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||||
*/
|
*/
|
||||||
rollDamage({event, powerLevel=null, versatile=false}={}) {
|
rollDamage({event, powerLevel=null, versatile=false}={}) {
|
||||||
const itemData = this.data.data;
|
const itemData = this.data.data;
|
||||||
|
@ -546,32 +689,54 @@ export class Item5e extends Item {
|
||||||
if ( !this.hasDamage ) {
|
if ( !this.hasDamage ) {
|
||||||
throw new Error("You may not make a Damage Roll with this Item.");
|
throw new Error("You may not make a Damage Roll with this Item.");
|
||||||
}
|
}
|
||||||
|
const messageData = {"flags.sw5e.roll": {type: "damage", itemId: this.id }};
|
||||||
|
|
||||||
|
// Get roll data
|
||||||
const rollData = this.getRollData();
|
const rollData = this.getRollData();
|
||||||
if ( powerLevel ) rollData.item.level = powerLevel;
|
if ( powerLevel ) rollData.item.level = powerLevel;
|
||||||
|
|
||||||
|
// Get message labels
|
||||||
|
const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`;
|
||||||
|
let flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title;
|
||||||
|
|
||||||
// Define Roll parts
|
// Define Roll parts
|
||||||
const parts = itemData.damage.parts.map(d => d[0]);
|
const parts = itemData.damage.parts.map(d => d[0]);
|
||||||
if ( versatile && itemData.damage.versatile ) parts[0] = itemData.damage.versatile;
|
|
||||||
|
// Adjust damage from versatile usage
|
||||||
|
if ( versatile && itemData.damage.versatile ) {
|
||||||
|
parts[0] = itemData.damage.versatile;
|
||||||
|
messageData["flags.sw5e.roll"].versatile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 === "cantrip") ) {
|
||||||
const lvl = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
|
const level = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
|
||||||
this._scaleAtWillDamage(parts, lvl, itemData.scaling.formula );
|
this._scaleCantripDamage(parts, itemData.scaling.formula, level, rollData);
|
||||||
} else if ( powerLevel && (itemData.scaling.mode === "level") && itemData.scaling.formula ) {
|
}
|
||||||
this._scalePowerDamage(parts, itemData.level, powerLevel, itemData.scaling.formula );
|
else if ( powerLevel && (itemData.scaling.mode === "level") && itemData.scaling.formula ) {
|
||||||
|
const scaling = itemData.scaling.formula;
|
||||||
|
this._scalePowerDamage(parts, itemData.level, powerLevel, scaling, rollData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define Roll Data
|
// Define Roll Data
|
||||||
const actorBonus = actorData.bonuses[itemData.actionType] || {};
|
const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
|
||||||
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) {
|
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) {
|
||||||
parts.push("@dmg");
|
parts.push("@dmg");
|
||||||
rollData["dmg"] = actorBonus.damage;
|
rollData["dmg"] = actorBonus.damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ammunition Damage
|
||||||
|
if ( this._ammo ) {
|
||||||
|
parts.push("@ammo");
|
||||||
|
rollData["ammo"] = this._ammo.data.data.damage.parts.map(p => p[0]).join("+");
|
||||||
|
flavor += ` [${this._ammo.name}]`;
|
||||||
|
delete this._ammo;
|
||||||
|
}
|
||||||
|
|
||||||
// Call the roll helper utility
|
// Call the roll helper utility
|
||||||
const title = `${this.name} - Damage Roll`;
|
return damageRoll({
|
||||||
const flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title;
|
|
||||||
return Dice5e.damageRoll({
|
|
||||||
event: event,
|
event: event,
|
||||||
parts: parts,
|
parts: parts,
|
||||||
actor: this.actor,
|
actor: this.actor,
|
||||||
|
@ -583,7 +748,8 @@ export class Item5e extends Item {
|
||||||
width: 400,
|
width: 400,
|
||||||
top: event ? event.clientY - 80 : null,
|
top: event ? event.clientY - 80 : null,
|
||||||
left: window.innerWidth - 710
|
left: window.innerWidth - 710
|
||||||
}
|
},
|
||||||
|
messageData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,13 +759,24 @@ export class Item5e extends Item {
|
||||||
* Adjust an at-will damage formula to scale it for higher level characters and monsters
|
* Adjust an at-will damage formula to scale it for higher level characters and monsters
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_scaleAtWillDamage(parts, level, scale) {
|
_scaleAtWillDamage(parts, scale, level, rollData) {
|
||||||
const add = Math.floor((level + 1) / 6);
|
const add = Math.floor((level + 1) / 6);
|
||||||
if ( add === 0 ) return;
|
if ( add === 0 ) return;
|
||||||
if ( scale && (scale !== parts[0]) ) {
|
|
||||||
parts[0] = parts[0] + " + " + scale.replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${add}d${d}`);
|
// FUTURE SOLUTION - 0.7.0 AND LATER
|
||||||
} else {
|
if (isNewerVersion(game.data.version, "0.6.9")) {
|
||||||
parts[0] = parts[0].replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${parseInt(nd)+add}d${d}`);
|
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LEGACY SOLUTION - 0.6.x AND OLDER
|
||||||
|
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
|
||||||
|
else {
|
||||||
|
if ( scale && (scale !== parts[0]) ) {
|
||||||
|
parts[0] = parts[0] + " + " + scale.replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${add}d${d}`);
|
||||||
|
} else {
|
||||||
|
parts[0] = parts[0].replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${parseInt(nd)+add}d${d}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,13 +788,61 @@ export class Item5e extends Item {
|
||||||
* @param {number} baseLevel The default power level
|
* @param {number} baseLevel The default power level
|
||||||
* @param {number} powerLevel The casted power level
|
* @param {number} powerLevel The casted power level
|
||||||
* @param {string} formula The scaling formula
|
* @param {string} formula The scaling formula
|
||||||
|
* @param {object} rollData A data object that should be applied to the scaled damage roll
|
||||||
|
* @return {string[]} The scaled roll parts
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_scalePowerDamage(parts, baseLevel, powerLevel, formula) {
|
_scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) {
|
||||||
const upcastLevels = Math.max(powerLevel - baseLevel, 0);
|
const upcastLevels = Math.max(powerLevel - baseLevel, 0);
|
||||||
if ( upcastLevels === 0 ) return parts;
|
if ( upcastLevels === 0 ) return parts;
|
||||||
const bonus = new Roll(formula).alter(0, upcastLevels);
|
|
||||||
parts.push(bonus.formula);
|
// FUTURE SOLUTION - 0.7.0 AND LATER
|
||||||
|
if (isNewerVersion(game.data.version, "0.6.9")) {
|
||||||
|
this._scaleDamage(parts, formula, upcastLevels, rollData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LEGACY SOLUTION - 0.6.x AND OLDER
|
||||||
|
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
|
||||||
|
else {
|
||||||
|
const bonus = new Roll(formula);
|
||||||
|
bonus.alter(0, upcastLevels);
|
||||||
|
parts.push(bonus.formula);
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale an array of damage parts according to a provided scaling formula and scaling multiplier
|
||||||
|
* @param {string[]} parts Initial roll parts
|
||||||
|
* @param {string} scaling A scaling formula
|
||||||
|
* @param {number} times A number of times to apply the scaling formula
|
||||||
|
* @param {object} rollData A data object that should be applied to the scaled damage roll
|
||||||
|
* @return {string[]} The scaled roll parts
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_scaleDamage(parts, scaling, times, rollData) {
|
||||||
|
if ( times <= 0 ) return parts;
|
||||||
|
const p0 = new Roll(parts[0], rollData);
|
||||||
|
const s = new Roll(scaling, rollData).alter(times);
|
||||||
|
|
||||||
|
// Attempt to simplify by combining like dice terms
|
||||||
|
let simplified = false;
|
||||||
|
if ( (s.terms[0] instanceof Die) && (s.terms.length === 1) ) {
|
||||||
|
const d0 = p0.terms[0];
|
||||||
|
const s0 = s.terms[0];
|
||||||
|
if ( (d0 instanceof Die) && (d0.faces === s0.faces) && d0.modifiers.equals(s0.modifiers) ) {
|
||||||
|
d0.number += s0.number;
|
||||||
|
parts[0] = p0.formula;
|
||||||
|
simplified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise add to the first part
|
||||||
|
if ( !simplified ) {
|
||||||
|
parts[0] = `${parts[0]} + ${s.formula}`;
|
||||||
|
}
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,8 +850,8 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
||||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
* Rely upon the d20Roll logic for the core implementation
|
||||||
*
|
*
|
||||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||||
*/
|
*/
|
||||||
async rollFormula(options={}) {
|
async rollFormula(options={}) {
|
||||||
|
@ -636,14 +861,16 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
// Define Roll Data
|
// Define Roll Data
|
||||||
const rollData = this.getRollData();
|
const rollData = this.getRollData();
|
||||||
const title = `${this.name} - Other Formula`;
|
if ( options.powerLevel ) rollData.item.level = options.powerLevel;
|
||||||
|
const title = `${this.name} - ${game.i18n.localize("SW5E.OtherFormula")}`;
|
||||||
|
|
||||||
// Invoke the roll and submit it to chat
|
// Invoke the roll and submit it to chat
|
||||||
const roll = new Roll(rollData.item.formula, rollData).roll();
|
const roll = new Roll(rollData.item.formula, rollData).roll();
|
||||||
roll.toMessage({
|
roll.toMessage({
|
||||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||||
flavor: this.data.data.chatFlavor || title,
|
flavor: this.data.data.chatFlavor || title,
|
||||||
rollMode: game.settings.get("core", "rollMode")
|
rollMode: game.settings.get("core", "rollMode"),
|
||||||
|
messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }}
|
||||||
});
|
});
|
||||||
return roll;
|
return roll;
|
||||||
}
|
}
|
||||||
|
@ -652,58 +879,77 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use a consumable item, deducting from the quantity or charges of the item.
|
* Use a consumable item, deducting from the quantity or charges of the item.
|
||||||
*
|
* @param {boolean} configureDialog Whether to show a configuration dialog
|
||||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance or null
|
* @return {boolean} Whether further execution should be prevented
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
async rollConsumable(options={}) {
|
async _rollConsumable(configureDialog) {
|
||||||
|
if ( this.data.type !== "consumable" ) throw new Error("Wrong Item type");
|
||||||
const itemData = this.data.data;
|
const itemData = this.data.data;
|
||||||
|
|
||||||
// Dispatch a damage roll
|
// Determine whether to deduct uses of the item
|
||||||
let roll = null;
|
const uses = itemData.uses || {};
|
||||||
if ( itemData.damage.parts.length ) {
|
const autoDestroy = uses.autoDestroy;
|
||||||
roll = await this.rollDamage(options);
|
let usesCharges = !!uses.per && (uses.max > 0);
|
||||||
|
const recharge = itemData.recharge || {};
|
||||||
|
const usesRecharge = !!recharge.value;
|
||||||
|
|
||||||
|
// Display a configuration dialog to confirm the usage
|
||||||
|
let placeTemplate = false;
|
||||||
|
let consume = uses.autoUse || true;
|
||||||
|
if ( configureDialog ) {
|
||||||
|
const usage = await AbilityUseDialog.create(this);
|
||||||
|
if ( usage === null ) return false;
|
||||||
|
consume = Boolean(usage.get("consumeUse"));
|
||||||
|
placeTemplate = Boolean(usage.get("placeTemplate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch an other formula
|
// Update Item data
|
||||||
if ( itemData.formula ) {
|
if ( consume ) {
|
||||||
roll = await this.rollFormula(options);
|
const current = uses.value || 0;
|
||||||
}
|
const remaining = usesCharges ? Math.max(current - 1, 0) : current;
|
||||||
|
if ( usesRecharge ) await this.update({"data.recharge.charged": false});
|
||||||
// Deduct consumed charges from the item
|
|
||||||
if ( itemData.uses.autoUse ) {
|
|
||||||
let q = itemData.quantity;
|
|
||||||
let c = itemData.uses.value;
|
|
||||||
|
|
||||||
// Deduct an item quantity
|
|
||||||
if ( c <= 1 && q > 1 ) {
|
|
||||||
await this.update({
|
|
||||||
'data.quantity': Math.max(q - 1, 0),
|
|
||||||
'data.uses.value': itemData.uses.max
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally destroy the item
|
|
||||||
else if ( c <= 1 && q <= 1 && itemData.uses.autoDestroy ) {
|
|
||||||
await this.actor.deleteOwnedItem(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduct the remaining charges
|
|
||||||
else {
|
else {
|
||||||
await this.update({'data.uses.value': Math.max(c - 1, 0)});
|
const q = itemData.quantity;
|
||||||
|
// Case 1, reduce charges
|
||||||
|
if ( remaining ) {
|
||||||
|
await this.update({"data.uses.value": remaining});
|
||||||
|
}
|
||||||
|
// Case 2, reduce quantity
|
||||||
|
else if ( q > 1 ) {
|
||||||
|
await this.update({"data.quantity": q - 1, "data.uses.value": uses.max || 0});
|
||||||
|
}
|
||||||
|
// Case 3, destroy the item
|
||||||
|
else if ( (q <= 1) && autoDestroy ) {
|
||||||
|
await this.actor.deleteOwnedItem(this.id);
|
||||||
|
}
|
||||||
|
// Case 4, reduce item to 0 quantity and 0 charges
|
||||||
|
else if ( (q === 1) ) {
|
||||||
|
await this.update({"data.quantity": q - 1, "data.uses.value": 0});
|
||||||
|
}
|
||||||
|
// Case 5, item unusable, display warning and do nothing
|
||||||
|
else {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return roll;
|
|
||||||
}
|
// Maybe initiate template placement workflow
|
||||||
|
if ( this.hasAreaTarget && placeTemplate ) {
|
||||||
|
const template = AbilityTemplate.fromItem(this);
|
||||||
|
if ( template ) template.drawPreview(event);
|
||||||
|
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform an ability recharge test for an item which uses the d6 recharge mechanic
|
* Perform an ability recharge test for an item which uses the d6 recharge mechanic
|
||||||
* @prarm {Object} options
|
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||||
*
|
|
||||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
|
||||||
*/
|
*/
|
||||||
async rollRecharge(options={}) {
|
async rollRecharge() {
|
||||||
const data = this.data.data;
|
const data = this.data.data;
|
||||||
if ( !data.recharge.value ) return;
|
if ( !data.recharge.value ) return;
|
||||||
|
|
||||||
|
@ -713,7 +959,7 @@ export class Item5e extends Item {
|
||||||
|
|
||||||
// Display a Chat Message
|
// Display a Chat Message
|
||||||
const promises = [roll.toMessage({
|
const promises = [roll.toMessage({
|
||||||
flavor: `${this.name} recharge check - ${success ? "success!" : "failure!"}`,
|
flavor: `${game.i18n.format("SW5E.ItemRechargeCheck", {name: this.name})} - ${game.i18n.localize(success ? "SW5E.ItemRechargeSuccess" : "SW5E.ItemRechargeFailure")}`,
|
||||||
speaker: ChatMessage.getSpeaker({actor: this.actor, token: this.actor.token})
|
speaker: ChatMessage.getSpeaker({actor: this.actor, token: this.actor.token})
|
||||||
})];
|
})];
|
||||||
|
|
||||||
|
@ -725,10 +971,9 @@ export class Item5e extends Item {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Roll a Tool Check
|
* Roll a Tool Check. Rely upon the d20Roll logic for the core implementation
|
||||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
* @prarm {Object} options Roll configuration options provided to the d20Roll function
|
||||||
*
|
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
|
||||||
*/
|
*/
|
||||||
rollToolCheck(options={}) {
|
rollToolCheck(options={}) {
|
||||||
if ( this.type !== "tool" ) throw "Wrong item type!";
|
if ( this.type !== "tool" ) throw "Wrong item type!";
|
||||||
|
@ -736,24 +981,28 @@ export class Item5e extends Item {
|
||||||
// Prepare roll data
|
// Prepare roll data
|
||||||
let rollData = this.getRollData();
|
let rollData = this.getRollData();
|
||||||
const parts = [`@mod`, "@prof"];
|
const parts = [`@mod`, "@prof"];
|
||||||
const title = `${this.name} - Tool Check`;
|
const title = `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`;
|
||||||
|
|
||||||
// Call the roll helper utility
|
// Compose the roll data
|
||||||
return Dice5e.d20Roll({
|
const rollConfig = mergeObject({
|
||||||
event: options.event,
|
|
||||||
parts: parts,
|
parts: parts,
|
||||||
data: rollData,
|
data: rollData,
|
||||||
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
|
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
|
||||||
title: title,
|
title: title,
|
||||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||||
flavor: `${this.name} - Tool Check`,
|
flavor: `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`,
|
||||||
dialogOptions: {
|
dialogOptions: {
|
||||||
width: 400,
|
width: 400,
|
||||||
top: options.event ? options.event.clientY - 80 : null,
|
top: options.event ? options.event.clientY - 80 : null,
|
||||||
left: window.innerWidth - 710,
|
left: window.innerWidth - 710,
|
||||||
},
|
},
|
||||||
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky" ) || false
|
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky" ) || false,
|
||||||
});
|
messageData: {"flags.sw5e.roll": {type: "tool", itemId: this.id }}
|
||||||
|
}, options);
|
||||||
|
rollConfig.event = options.event;
|
||||||
|
|
||||||
|
// Call the roll helper utility
|
||||||
|
return d20Roll(rollConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -819,7 +1068,7 @@ export class Item5e extends Item {
|
||||||
// Get the Item
|
// Get the Item
|
||||||
const item = actor.getOwnedItem(card.dataset.itemId);
|
const item = actor.getOwnedItem(card.dataset.itemId);
|
||||||
if ( !item ) {
|
if ( !item ) {
|
||||||
return ui.notifications.error(`The requested item ${card.dataset.itemId} no longer exists on Actor ${actor.name}`)
|
return ui.notifications.error(game.i18n.format("SW5E.ActionWarningNoItem", {item: card.dataset.itemId, name: actor.name}))
|
||||||
}
|
}
|
||||||
const powerLevel = parseInt(card.dataset.powerLevel) || null;
|
const powerLevel = parseInt(card.dataset.powerLevel) || null;
|
||||||
|
|
||||||
|
@ -828,7 +1077,7 @@ export class Item5e extends Item {
|
||||||
if ( isTargetted ) {
|
if ( isTargetted ) {
|
||||||
targets = this._getChatCardTargets(card);
|
targets = this._getChatCardTargets(card);
|
||||||
if ( !targets.length ) {
|
if ( !targets.length ) {
|
||||||
ui.notifications.warn(`You must have one or more controlled Tokens in order to use this option.`);
|
ui.notifications.warn(game.i18n.localize("SW5E.ActionWarningNoToken"));
|
||||||
return button.disabled = false;
|
return button.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -837,18 +1086,16 @@ export class Item5e extends Item {
|
||||||
if ( action === "attack" ) await item.rollAttack({event});
|
if ( action === "attack" ) await item.rollAttack({event});
|
||||||
else if ( action === "damage" ) await item.rollDamage({event, powerLevel});
|
else if ( action === "damage" ) await item.rollDamage({event, powerLevel});
|
||||||
else if ( action === "versatile" ) await item.rollDamage({event, powerLevel, versatile: true});
|
else if ( action === "versatile" ) await item.rollDamage({event, powerLevel, versatile: true});
|
||||||
else if ( action === "formula" ) await item.rollFormula({event});
|
else if ( action === "formula" ) await item.rollFormula({event, powerLevel});
|
||||||
|
|
||||||
// Saving Throws for card targets
|
// Saving Throws for card targets
|
||||||
else if ( action === "save" ) {
|
else if ( action === "save" ) {
|
||||||
for ( let t of targets ) {
|
for ( let a of targets ) {
|
||||||
await t.rollAbilitySave(button.dataset.ability, {event});
|
const speaker = ChatMessage.getSpeaker({scene: canvas.scene, token: a.token});
|
||||||
|
await a.rollAbilitySave(button.dataset.ability, { event, speaker });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consumable usage
|
|
||||||
else if ( action === "consume" ) await item.rollConsumable({event});
|
|
||||||
|
|
||||||
// Tool usage
|
// Tool usage
|
||||||
else if ( action === "toolCheck" ) await item.rollToolCheck({event});
|
else if ( action === "toolCheck" ) await item.rollToolCheck({event});
|
||||||
|
|
||||||
|
@ -919,4 +1166,56 @@ export class Item5e extends Item {
|
||||||
if ( character && (controlled.length === 0) ) targets.push(character);
|
if ( character && (controlled.length === 0) ) targets.push(character);
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Factory Methods */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a consumable power scroll Item from a power Item.
|
||||||
|
* @param {Item5e} power The power to be made into a scroll
|
||||||
|
* @return {Item5e} The created scroll consumable item
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static async createScrollFromPower(power) {
|
||||||
|
|
||||||
|
// Get power data
|
||||||
|
const itemData = power instanceof Item5e ? power.data : power;
|
||||||
|
const {actionType, description, source, activation, duration, target, range, damage, save, level} = itemData.data;
|
||||||
|
|
||||||
|
// Get scroll data
|
||||||
|
const scrollUuid = CONFIG.SW5E.powerScrollIds[level];
|
||||||
|
const scrollItem = await fromUuid(scrollUuid);
|
||||||
|
const scrollData = scrollItem.data;
|
||||||
|
delete scrollData._id;
|
||||||
|
|
||||||
|
// Split the scroll description into an intro paragraph and the remaining details
|
||||||
|
const scrollDescription = scrollData.data.description.value;
|
||||||
|
const pdel = '</p>';
|
||||||
|
const scrollIntroEnd = scrollDescription.indexOf(pdel);
|
||||||
|
const scrollIntro = scrollDescription.slice(0, scrollIntroEnd + pdel.length);
|
||||||
|
const scrollDetails = scrollDescription.slice(scrollIntroEnd + pdel.length);
|
||||||
|
|
||||||
|
// Create a composite description from the scroll description and the power details
|
||||||
|
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
|
||||||
|
const powerScrollData = mergeObject(scrollData, {
|
||||||
|
name: `${game.i18n.localize("SW5E.PowerScroll")}: ${itemData.name}`,
|
||||||
|
img: itemData.img,
|
||||||
|
data: {
|
||||||
|
"description.value": desc.trim(),
|
||||||
|
source,
|
||||||
|
actionType,
|
||||||
|
activation,
|
||||||
|
duration,
|
||||||
|
target,
|
||||||
|
range,
|
||||||
|
damage,
|
||||||
|
save,
|
||||||
|
level
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return new this(powerScrollData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
import { TraitSelector } from "../apps/trait-selector.js";
|
import TraitSelector from "../apps/trait-selector.js";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override and extend the core ItemSheet implementation to handle SW5E specific item types
|
* Override and extend the core ItemSheet implementation to handle specific item types
|
||||||
* @type {ItemSheet}
|
* @extends {ItemSheet}
|
||||||
*/
|
*/
|
||||||
export class ItemSheet5e extends ItemSheet {
|
export default class ItemSheet5e extends ItemSheet {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
if ( this.object.data.type === "class" ) {
|
||||||
|
this.options.resizable = true;
|
||||||
|
this.options.width = 600;
|
||||||
|
this.options.height = 640;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
|
@ -13,7 +22,7 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
width: 560,
|
width: 560,
|
||||||
height: 420,
|
height: 420,
|
||||||
classes: ["sw5e", "sheet", "item"],
|
classes: ["sw5e", "sheet", "item"],
|
||||||
resizable: false,
|
resizable: true,
|
||||||
scrollY: [".tab.details"],
|
scrollY: [".tab.details"],
|
||||||
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
|
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
|
||||||
});
|
});
|
||||||
|
@ -43,14 +52,79 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
data.itemProperties = this._getItemProperties(data.item);
|
data.itemProperties = this._getItemProperties(data.item);
|
||||||
data.isPhysical = data.item.data.hasOwnProperty("quantity");
|
data.isPhysical = data.item.data.hasOwnProperty("quantity");
|
||||||
|
|
||||||
|
// Potential consumption targets
|
||||||
|
data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item);
|
||||||
|
|
||||||
// Action Details
|
// Action Details
|
||||||
data.hasAttackRoll = this.item.hasAttack;
|
data.hasAttackRoll = this.item.hasAttack;
|
||||||
data.isHealing = data.item.data.actionType === "heal";
|
data.isHealing = data.item.data.actionType === "heal";
|
||||||
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
|
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
|
||||||
data.isWeapon = data.item.type === "weapon";
|
|
||||||
|
// Vehicles
|
||||||
|
data.isCrewed = data.item.data.activation?.type === 'crew';
|
||||||
|
data.isMountable = this._isItemMountable(data.item);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the valid item consumption targets which exist on the actor
|
||||||
|
* @param {Object} item Item data for the item being displayed
|
||||||
|
* @return {{string: string}} An object of potential consumption targets
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getItemConsumptionTargets(item) {
|
||||||
|
const consume = item.data.consume || {};
|
||||||
|
if ( !consume.type ) return [];
|
||||||
|
const actor = this.item.actor;
|
||||||
|
if ( !actor ) return {};
|
||||||
|
|
||||||
|
// Ammunition
|
||||||
|
if ( consume.type === "ammo" ) {
|
||||||
|
return actor.itemTypes.consumable.reduce((ammo, i) => {
|
||||||
|
if ( i.data.data.consumableType === "ammo" ) {
|
||||||
|
ammo[i.id] = `${i.name} (${i.data.data.quantity})`;
|
||||||
|
}
|
||||||
|
return ammo;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
else if ( consume.type === "attribute" ) {
|
||||||
|
const attributes = Object.values(CombatTrackerConfig.prototype.getAttributeChoices())[0]; // Bit of a hack
|
||||||
|
return attributes.reduce((obj, a) => {
|
||||||
|
obj[a] = a;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Materials
|
||||||
|
else if ( consume.type === "material" ) {
|
||||||
|
return actor.items.reduce((obj, i) => {
|
||||||
|
if ( ["consumable", "loot"].includes(i.data.type) && !i.data.data.activation ) {
|
||||||
|
obj[i.id] = `${i.name} (${i.data.data.quantity})`;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charges
|
||||||
|
else if ( consume.type === "charges" ) {
|
||||||
|
return actor.items.reduce((obj, i) => {
|
||||||
|
const uses = i.data.data.uses || {};
|
||||||
|
if ( uses.per && uses.max ) {
|
||||||
|
const label = uses.per === "charges" ?
|
||||||
|
` (${game.i18n.format("SW5E.AbilityUseChargesLabel", {value: uses.value})})` :
|
||||||
|
` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {max: uses.max, per: uses.per})})`;
|
||||||
|
obj[i.id] = i.name + label;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
else return {};
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,10 +137,10 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
return CONFIG.SW5E.powerPreparationModes[item.data.preparation];
|
return CONFIG.SW5E.powerPreparationModes[item.data.preparation];
|
||||||
}
|
}
|
||||||
else if ( ["weapon", "equipment"].includes(item.type) ) {
|
else if ( ["weapon", "equipment"].includes(item.type) ) {
|
||||||
return item.data.equipped ? "Equipped" : "Unequipped";
|
return game.i18n.localize(item.data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped");
|
||||||
}
|
}
|
||||||
else if ( item.type === "tool" ) {
|
else if ( item.type === "tool" ) {
|
||||||
return item.data.proficient ? "Proficient" : "Not Proficient";
|
return game.i18n.localize(item.data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +165,8 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
props.push(
|
props.push(
|
||||||
labels.components,
|
labels.components,
|
||||||
labels.materials,
|
labels.materials,
|
||||||
item.data.components.concentration ? "Concentration" : null,
|
item.data.components.concentration ? game.i18n.localize("SW5E.Concentration") : null,
|
||||||
item.data.components.ritual ? "Ritual" : null
|
item.data.components.ritual ? game.i18n.localize("SW5E.Ritual") : null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +202,22 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this item a separate large object like a siege engine or vehicle
|
||||||
|
* component that is usually mounted on fixtures rather than equipped, and
|
||||||
|
* has its own AC and HP.
|
||||||
|
* @param item
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_isItemMountable(item) {
|
||||||
|
const data = item.data;
|
||||||
|
return (item.type === 'weapon' && data.weaponType === 'siege')
|
||||||
|
|| (item.type === 'equipment' && data.armor.type === 'vehicle');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
setPosition(position={}) {
|
setPosition(position={}) {
|
||||||
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
|
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
|
||||||
|
@ -141,33 +231,12 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
/** @override */
|
/** @override */
|
||||||
_updateObject(event, formData) {
|
_updateObject(event, formData) {
|
||||||
|
|
||||||
|
// TODO: This can be removed once 0.7.x is release channel
|
||||||
|
if ( !formData.data ) formData = expandObject(formData);
|
||||||
|
|
||||||
// Handle Damage Array
|
// Handle Damage Array
|
||||||
let damage = Object.entries(formData).filter(e => e[0].startsWith("data.damage.parts"));
|
const damage = formData.data?.damage;
|
||||||
formData["data.damage.parts"] = damage.reduce((arr, entry) => {
|
if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
|
||||||
let [i, j] = entry[0].split(".").slice(3);
|
|
||||||
if ( !arr[i] ) arr[i] = [];
|
|
||||||
arr[i][j] = entry[1];
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle armorproperties Array
|
|
||||||
let armorproperties = Object.entries(formData).filter(e => e[0].startsWith("data.armorproperties.parts"));
|
|
||||||
formData["data.armorproperties.parts"] = armorproperties.reduce((arr, entry) => {
|
|
||||||
let [i, j] = entry[0].split(".").slice(3);
|
|
||||||
if ( !arr[i] ) arr[i] = [];
|
|
||||||
arr[i][j] = entry[1];
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle weaponproperties Array
|
|
||||||
let weaponproperties = Object.entries(formData).filter(e => e[0].startsWith("data.weaponproperties.parts"));
|
|
||||||
formData["data.weaponproperties.parts"] = weaponproperties.reduce((arr, entry) => {
|
|
||||||
let [i, j] = entry[0].split(".").slice(3);
|
|
||||||
if ( !arr[i] ) arr[i] = [];
|
|
||||||
arr[i][j] = entry[1];
|
|
||||||
return arr;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
// Update the Item
|
// Update the Item
|
||||||
super._updateObject(event, formData);
|
super._updateObject(event, formData);
|
||||||
|
@ -179,16 +248,14 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
activateListeners(html) {
|
activateListeners(html) {
|
||||||
super.activateListeners(html);
|
super.activateListeners(html);
|
||||||
html.find(".damage-control").click(this._onDamageControl.bind(this));
|
html.find(".damage-control").click(this._onDamageControl.bind(this));
|
||||||
|
|
||||||
// Activate any Trait Selectors
|
|
||||||
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this));
|
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this));
|
||||||
|
|
||||||
// Armor properties
|
// Armor properties
|
||||||
html.find(".armorproperties-control").click(this._onarmorpropertiesControl.bind(this));
|
html.find(".armorproperties-control").click(this._onarmorpropertiesControl.bind(this));
|
||||||
|
|
||||||
// Weapon properties
|
// Weapon properties
|
||||||
html.find(".weaponproperties-control").click(this._onweaponpropertiesControl.bind(this));
|
html.find(".weaponproperties-control").click(this._onweaponpropertiesControl.bind(this));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -222,64 +289,6 @@ export class ItemSheet5e extends ItemSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
|
||||||
* Add or remove a armorproperties part from the armorproperties formula
|
|
||||||
* @param {Event} event The original click event
|
|
||||||
* @return {Promise}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _onarmorpropertiesControl(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const a = event.currentTarget;
|
|
||||||
|
|
||||||
// Add new armorproperties component
|
|
||||||
if ( a.classList.contains("add-armorproperties") ) {
|
|
||||||
await this._onSubmit(event); // Submit any unsaved changes
|
|
||||||
const armorproperties = this.item.data.data.armorproperties;
|
|
||||||
return this.item.update({"data.armorproperties.parts": armorproperties.parts.concat([["", ""]])});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a armorproperties component
|
|
||||||
if ( a.classList.contains("delete-armorproperties") ) {
|
|
||||||
await this._onSubmit(event); // Submit any unsaved changes
|
|
||||||
const li = a.closest(".armorproperties-part");
|
|
||||||
const armorproperties = duplicate(this.item.data.data.armorproperties);
|
|
||||||
armorproperties.parts.splice(Number(li.dataset.armorpropertiesPart), 1);
|
|
||||||
return this.item.update({"data.armorproperties.parts": armorproperties.parts});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add or remove a weaponproperties part from the weaponproperties formula
|
|
||||||
* @param {Event} event The original click event
|
|
||||||
* @return {Promise}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _onweaponpropertiesControl(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const a = event.currentTarget;
|
|
||||||
|
|
||||||
// Add new weaponproperties component
|
|
||||||
if ( a.classList.contains("add-weaponproperties") ) {
|
|
||||||
await this._onSubmit(event); // Submit any unsaved changes
|
|
||||||
const weaponproperties = this.item.data.data.weaponproperties;
|
|
||||||
return this.item.update({"data.weaponproperties.parts": weaponproperties.parts.concat([["", ""]])});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a weaponproperties component
|
|
||||||
if ( a.classList.contains("delete-weaponproperties") ) {
|
|
||||||
await this._onSubmit(event); // Submit any unsaved changes
|
|
||||||
const li = a.closest(".weaponproperties-part");
|
|
||||||
const weaponproperties = duplicate(this.item.data.data.weaponproperties);
|
|
||||||
weaponproperties.parts.splice(Number(li.dataset.weaponpropertiesPart), 1);
|
|
||||||
return this.item.update({"data.weaponproperties.parts": weaponproperties.parts});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
60
module/macros.js
Normal file
60
module/macros.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Hotbar Macros */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Macro from an Item drop.
|
||||||
|
* Get an existing item macro if one exists, otherwise create a new one.
|
||||||
|
* @param {Object} data The dropped data
|
||||||
|
* @param {number} slot The hotbar slot to use
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export async function create5eMacro(data, slot) {
|
||||||
|
if ( data.type !== "Item" ) return;
|
||||||
|
if (!( "data" in data ) ) return ui.notifications.warn("You can only create macro buttons for owned Items");
|
||||||
|
const item = data.data;
|
||||||
|
|
||||||
|
// Create the macro command
|
||||||
|
const command = `game.sw5e.rollItemMacro("${item.name}");`;
|
||||||
|
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
|
||||||
|
if ( !macro ) {
|
||||||
|
macro = await Macro.create({
|
||||||
|
name: item.name,
|
||||||
|
type: "script",
|
||||||
|
img: item.img,
|
||||||
|
command: command,
|
||||||
|
flags: {"sw5e.itemMacro": true}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
game.user.assignHotbarMacro(macro, slot);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Macro from an Item drop.
|
||||||
|
* Get an existing item macro if one exists, otherwise create a new one.
|
||||||
|
* @param {string} itemName
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
export function rollItemMacro(itemName) {
|
||||||
|
const speaker = ChatMessage.getSpeaker();
|
||||||
|
let actor;
|
||||||
|
if ( speaker.token ) actor = game.actors.tokens[speaker.token];
|
||||||
|
if ( !actor ) actor = game.actors.get(speaker.actor);
|
||||||
|
|
||||||
|
// Get matching items
|
||||||
|
const items = actor ? actor.items.filter(i => i.name === itemName) : [];
|
||||||
|
if ( items.length > 1 ) {
|
||||||
|
ui.notifications.warn(`Your controlled Actor ${actor.name} has more than one Item with name ${itemName}. The first matched item will be chosen.`);
|
||||||
|
} else if ( items.length === 0 ) {
|
||||||
|
return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
|
||||||
|
}
|
||||||
|
const item = items[0];
|
||||||
|
|
||||||
|
// Trigger the item roll
|
||||||
|
if ( item.data.type === "power" ) return actor.usePower(item);
|
||||||
|
return item.roll();
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { SW5E } from "../config.js";
|
||||||
* A helper class for building MeasuredTemplates for 5e powers and abilities
|
* A helper class for building MeasuredTemplates for 5e powers and abilities
|
||||||
* @extends {MeasuredTemplate}
|
* @extends {MeasuredTemplate}
|
||||||
*/
|
*/
|
||||||
export class AbilityTemplate extends MeasuredTemplate {
|
export default class AbilityTemplate extends MeasuredTemplate {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory method to create an AbilityTemplate instance using provided data from an Item5e instance
|
* A factory method to create an AbilityTemplate instance using provided data from an Item5e instance
|
||||||
|
@ -37,8 +37,8 @@ export class AbilityTemplate extends MeasuredTemplate {
|
||||||
templateData.width = target.value;
|
templateData.width = target.value;
|
||||||
templateData.direction = 45;
|
templateData.direction = 45;
|
||||||
break;
|
break;
|
||||||
case "ray": // 5e rays are most commonly 5ft wide
|
case "ray": // 5e rays are most commonly 1 square (5 ft) in width
|
||||||
templateData.width = 5;
|
templateData.width = canvas.dimensions.distance;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -11,6 +11,23 @@ export const registerSystemSettings = function() {
|
||||||
default: 0
|
default: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register resting variants
|
||||||
|
*/
|
||||||
|
game.settings.register("sw5e", "restVariant", {
|
||||||
|
name: "SETTINGS.5eRestN",
|
||||||
|
hint: "SETTINGS.5eRestL",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
default: "normal",
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
"normal": "SETTINGS.5eRestPHB",
|
||||||
|
"gritty": "SETTINGS.5eRestGritty",
|
||||||
|
"epic": "SETTINGS.5eRestEpic",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register diagonal movement rule setting
|
* Register diagonal movement rule setting
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +40,8 @@ export const registerSystemSettings = function() {
|
||||||
type: String,
|
type: String,
|
||||||
choices: {
|
choices: {
|
||||||
"555": "SETTINGS.5eDiagPHB",
|
"555": "SETTINGS.5eDiagPHB",
|
||||||
"5105": "SETTINGS.5eDiagDMG"
|
"5105": "SETTINGS.5eDiagDMG",
|
||||||
|
"EUCL": "SETTINGS.5eDiagEuclidean",
|
||||||
},
|
},
|
||||||
onChange: rule => canvas.grid.diagonalRule = rule
|
onChange: rule => canvas.grid.diagonalRule = rule
|
||||||
});
|
});
|
||||||
|
@ -31,21 +49,14 @@ export const registerSystemSettings = function() {
|
||||||
/**
|
/**
|
||||||
* Register Initiative formula setting
|
* Register Initiative formula setting
|
||||||
*/
|
*/
|
||||||
function _set5eInitiative(tiebreaker) {
|
|
||||||
CONFIG.Combat.initiative.tiebreaker = tiebreaker;
|
|
||||||
CONFIG.Combat.initiative.decimals = tiebreaker ? 2 : 0;
|
|
||||||
if ( ui.combat && ui.combat._rendered ) ui.combat.render();
|
|
||||||
}
|
|
||||||
game.settings.register("sw5e", "initiativeDexTiebreaker", {
|
game.settings.register("sw5e", "initiativeDexTiebreaker", {
|
||||||
name: "SETTINGS.5eInitTBN",
|
name: "SETTINGS.5eInitTBN",
|
||||||
hint: "SETTINGS.5eInitTBL",
|
hint: "SETTINGS.5eInitTBL",
|
||||||
scope: "world",
|
scope: "world",
|
||||||
config: true,
|
config: true,
|
||||||
default: false,
|
default: false,
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
onChange: enable => _set5eInitiative(enable)
|
|
||||||
});
|
});
|
||||||
_set5eInitiative(game.settings.get("sw5e", "initiativeDexTiebreaker"));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Require Currency Carrying Weight
|
* Require Currency Carrying Weight
|
||||||
|
|
|
@ -17,7 +17,8 @@ export const preloadHandlebarsTemplates = async function() {
|
||||||
// Item Sheet Partials
|
// Item Sheet Partials
|
||||||
"systems/sw5e/templates/items/parts/item-action.html",
|
"systems/sw5e/templates/items/parts/item-action.html",
|
||||||
"systems/sw5e/templates/items/parts/item-activation.html",
|
"systems/sw5e/templates/items/parts/item-activation.html",
|
||||||
"systems/sw5e/templates/items/parts/item-description.html"
|
"systems/sw5e/templates/items/parts/item-description.html",
|
||||||
|
"systems/sw5e/templates/items/parts/item-mountable.html"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Load the template parts
|
// Load the template parts
|
||||||
|
|
File diff suppressed because one or more lines are too long
152
sw5e.js
152
sw5e.js
|
@ -2,7 +2,7 @@
|
||||||
* The Star Wars 5th Edition game system for Foundry Virtual Tabletop
|
* The Star Wars 5th Edition game system for Foundry Virtual Tabletop
|
||||||
* Author: Kakeman89
|
* Author: Kakeman89
|
||||||
* Software License: GNU GPLv3
|
* Software License: GNU GPLv3
|
||||||
* Content License: https://media.wizards.com/2016/downloads/SW5ERD-OGL_V5.1.pdf
|
* Content License: https://media.wizards.com/2016/downloads/SW5E/SRD-OGL_V5.1.pdf
|
||||||
* Repository: https://gitlab.com/foundrynet/sw5e
|
* Repository: https://gitlab.com/foundrynet/sw5e
|
||||||
* Issue Tracker: https://gitlab.com/foundrynet/sw5e/issues
|
* Issue Tracker: https://gitlab.com/foundrynet/sw5e/issues
|
||||||
*/
|
*/
|
||||||
|
@ -12,14 +12,27 @@ import { SW5E } from "./module/config.js";
|
||||||
import { registerSystemSettings } from "./module/settings.js";
|
import { registerSystemSettings } from "./module/settings.js";
|
||||||
import { preloadHandlebarsTemplates } from "./module/templates.js";
|
import { preloadHandlebarsTemplates } from "./module/templates.js";
|
||||||
import { _getInitiativeFormula } from "./module/combat.js";
|
import { _getInitiativeFormula } from "./module/combat.js";
|
||||||
import { measureDistance, getBarAttribute } from "./module/canvas.js";
|
import { measureDistances, getBarAttribute } from "./module/canvas.js";
|
||||||
import { Actor5e } from "./module/actor/entity.js";
|
|
||||||
import { ActorSheet5eCharacter } from "./module/actor/sheets/character.js";
|
// Import Entities
|
||||||
import { Item5e } from "./module/item/entity.js";
|
import Actor5e from "./module/actor/entity.js";
|
||||||
import { ItemSheet5e } from "./module/item/sheet.js";
|
import Item5e from "./module/item/entity.js";
|
||||||
import { ActorSheet5eNPC } from "./module/actor/sheets/npc.js";
|
|
||||||
import { Dice5e } from "./module/dice.js";
|
// Import Applications
|
||||||
|
import AbilityTemplate from "./module/pixi/ability-template.js";
|
||||||
|
import AbilityUseDialog from "./module/apps/ability-use-dialog.js";
|
||||||
|
import ActorSheetFlags from "./module/apps/actor-flags.js";
|
||||||
|
import ActorSheet5eCharacter from "./module/actor/sheets/character.js";
|
||||||
|
import ActorSheet5eNPC from "./module/actor/sheets/npc.js";
|
||||||
|
import ActorSheet5eVehicle from "./module/actor/sheets/vehicle.js";
|
||||||
|
import ItemSheet5e from "./module/item/sheet.js";
|
||||||
|
import ShortRestDialog from "./module/apps/short-rest.js";
|
||||||
|
import TraitSelector from "./module/apps/trait-selector.js";
|
||||||
|
|
||||||
|
// Import Helpers
|
||||||
import * as chat from "./module/chat.js";
|
import * as chat from "./module/chat.js";
|
||||||
|
import * as dice from "./module/dice.js";
|
||||||
|
import * as macros from "./module/macros.js";
|
||||||
import * as migrations from "./module/migration.js";
|
import * as migrations from "./module/migration.js";
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -31,11 +44,28 @@ Hooks.once("init", function() {
|
||||||
|
|
||||||
// Create a SW5E namespace within the game global
|
// Create a SW5E namespace within the game global
|
||||||
game.sw5e = {
|
game.sw5e = {
|
||||||
Actor5e,
|
applications: {
|
||||||
Dice5e,
|
AbilityUseDialog,
|
||||||
Item5e,
|
ActorSheetFlags,
|
||||||
migrations,
|
ActorSheet5eCharacter,
|
||||||
rollItemMacro
|
ActorSheet5eNPC,
|
||||||
|
ActorSheet5eVehicle,
|
||||||
|
ItemSheet5e,
|
||||||
|
ShortRestDialog,
|
||||||
|
TraitSelector
|
||||||
|
},
|
||||||
|
canvas: {
|
||||||
|
AbilityTemplate
|
||||||
|
},
|
||||||
|
config: SW5E,
|
||||||
|
dice: dice,
|
||||||
|
entities: {
|
||||||
|
Actor5e,
|
||||||
|
Item5e,
|
||||||
|
},
|
||||||
|
macros: macros,
|
||||||
|
migrations: migrations,
|
||||||
|
rollItemMacro: macros.rollItemMacro
|
||||||
};
|
};
|
||||||
|
|
||||||
// Record Configuration Values
|
// Record Configuration Values
|
||||||
|
@ -51,12 +81,14 @@ Hooks.once("init", function() {
|
||||||
registerSystemSettings();
|
registerSystemSettings();
|
||||||
|
|
||||||
// Patch Core Functions
|
// Patch Core Functions
|
||||||
|
CONFIG.Combat.initiative.formula = "1d20 + @attributes.init.mod + @attributes.init.prof + @attributes.init.bonus";
|
||||||
Combat.prototype._getInitiativeFormula = _getInitiativeFormula;
|
Combat.prototype._getInitiativeFormula = _getInitiativeFormula;
|
||||||
|
|
||||||
// Register sheet application classes
|
// Register sheet application classes
|
||||||
Actors.unregisterSheet("core", ActorSheet);
|
Actors.unregisterSheet("core", ActorSheet);
|
||||||
Actors.registerSheet("sw5e", ActorSheet5eCharacter, { types: ["character"], makeDefault: true });
|
Actors.registerSheet("sw5e", ActorSheet5eCharacter, { types: ["character"], makeDefault: true });
|
||||||
Actors.registerSheet("sw5e", ActorSheet5eNPC, { types: ["npc"], makeDefault: true });
|
Actors.registerSheet("sw5e", ActorSheet5eNPC, { types: ["npc"], makeDefault: true });
|
||||||
|
Actors.registerSheet('sw5e', ActorSheet5eVehicle, {types: ['vehicle'], makeDefault: true});
|
||||||
Items.unregisterSheet("core", ItemSheet);
|
Items.unregisterSheet("core", ItemSheet);
|
||||||
Items.registerSheet("sw5e", ItemSheet5e, {makeDefault: true});
|
Items.registerSheet("sw5e", ItemSheet5e, {makeDefault: true});
|
||||||
|
|
||||||
|
@ -76,14 +108,28 @@ Hooks.once("setup", function() {
|
||||||
|
|
||||||
// Localize CONFIG objects once up-front
|
// Localize CONFIG objects once up-front
|
||||||
const toLocalize = [
|
const toLocalize = [
|
||||||
"abilities", "alignments", "conditionTypes", "consumableTypes", "currencies", "damageTypes", "distanceUnits", "equipmentTypes",
|
"abilities", "abilityAbbreviations", "alignments", "conditionTypes", "consumableTypes", "currencies",
|
||||||
"healingTypes", "itemActionTypes", "limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes",
|
"damageTypes", "damageResistanceTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes",
|
||||||
"powerSchools", "powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages", "polymorphSettings",
|
"limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes", "powerSchools",
|
||||||
"armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes", "actorSizes", "proficiencyLevels", "armorpropertiesTypes"
|
"powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages",
|
||||||
|
"polymorphSettings", "armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes",
|
||||||
|
"abilityConsumptionTypes", "actorSizes", "proficiencyLevels", "armorPropertiesTypes", "cover"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Exclude some from sorting where the default order matters
|
||||||
|
const noSort = [
|
||||||
|
"abilities", "alignments", "currencies", "distanceUnits", "itemActionTypes", "proficiencyLevels",
|
||||||
|
"limitedUsePeriods", "powerComponents", "powerLevels", "weaponTypes"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Localize and sort CONFIG objects
|
||||||
for ( let o of toLocalize ) {
|
for ( let o of toLocalize ) {
|
||||||
CONFIG.SW5E[o] = Object.entries(CONFIG.SW5E[o]).reduce((obj, e) => {
|
const localized = Object.entries(CONFIG.SW5E[o]).map(e => {
|
||||||
obj[e[0]] = game.i18n.localize(e[1]);
|
return [e[0], game.i18n.localize(e[1])];
|
||||||
|
});
|
||||||
|
if ( !noSort.includes(o) ) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
||||||
|
CONFIG.SW5E[o] = localized.reduce((obj, e) => {
|
||||||
|
obj[e[0]] = e[1];
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
@ -101,7 +147,6 @@ Hooks.once("ready", function() {
|
||||||
const NEEDS_MIGRATION_VERSION = 0.84;
|
const NEEDS_MIGRATION_VERSION = 0.84;
|
||||||
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
||||||
let needMigration = (currentVersion < NEEDS_MIGRATION_VERSION) || (currentVersion === null);
|
let needMigration = (currentVersion < NEEDS_MIGRATION_VERSION) || (currentVersion === null);
|
||||||
const canMigrate = currentVersion >= COMPATIBLE_MIGRATION_VERSION;
|
|
||||||
|
|
||||||
// Perform the migration
|
// Perform the migration
|
||||||
if ( needMigration && game.user.isGM ) {
|
if ( needMigration && game.user.isGM ) {
|
||||||
|
@ -112,7 +157,7 @@ Hooks.once("ready", function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
|
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
|
||||||
Hooks.on("hotbarDrop", (bar, data, slot) => create5eMacro(data, slot));
|
Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot));
|
||||||
});
|
});
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -123,7 +168,7 @@ Hooks.on("canvasInit", function() {
|
||||||
|
|
||||||
// Extend Diagonal Measurement
|
// Extend Diagonal Measurement
|
||||||
canvas.grid.diagonalRule = game.settings.get("sw5e", "diagonalMovement");
|
canvas.grid.diagonalRule = game.settings.get("sw5e", "diagonalMovement");
|
||||||
SquareGrid.prototype.measureDistance = measureDistance;
|
SquareGrid.prototype.measureDistances = measureDistances;
|
||||||
|
|
||||||
// Extend Token Resource Bars
|
// Extend Token Resource Bars
|
||||||
Token.prototype.getBarAttribute = getBarAttribute;
|
Token.prototype.getBarAttribute = getBarAttribute;
|
||||||
|
@ -149,63 +194,6 @@ Hooks.on("getChatLogEntryContext", chat.addChatMessageContextOptions);
|
||||||
Hooks.on("renderChatLog", (app, html, data) => Item5e.chatListeners(html));
|
Hooks.on("renderChatLog", (app, html, data) => Item5e.chatListeners(html));
|
||||||
Hooks.on('getActorDirectoryEntryContext', Actor5e.addDirectoryContextOptions);
|
Hooks.on('getActorDirectoryEntryContext', Actor5e.addDirectoryContextOptions);
|
||||||
|
|
||||||
|
Handlebars.registerHelper('getProperty', function (data, property) {
|
||||||
/* -------------------------------------------- */
|
return getProperty(data, property);
|
||||||
/* Hotbar Macros */
|
});
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Macro from an Item drop.
|
|
||||||
* Get an existing item macro if one exists, otherwise create a new one.
|
|
||||||
* @param {Object} data The dropped data
|
|
||||||
* @param {number} slot The hotbar slot to use
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
async function create5eMacro(data, slot) {
|
|
||||||
if ( data.type !== "Item" ) return;
|
|
||||||
if (!( "data" in data ) ) return ui.notifications.warn("You can only create macro buttons for owned Items");
|
|
||||||
const item = data.data;
|
|
||||||
|
|
||||||
// Create the macro command
|
|
||||||
const command = `game.sw5e.rollItemMacro("${item.name}");`;
|
|
||||||
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
|
|
||||||
if ( !macro ) {
|
|
||||||
macro = await Macro.create({
|
|
||||||
name: item.name,
|
|
||||||
type: "script",
|
|
||||||
img: item.img,
|
|
||||||
command: command,
|
|
||||||
flags: {"sw5e.itemMacro": true}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
game.user.assignHotbarMacro(macro, slot);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Macro from an Item drop.
|
|
||||||
* Get an existing item macro if one exists, otherwise create a new one.
|
|
||||||
* @param {string} itemName
|
|
||||||
* @return {Promise}
|
|
||||||
*/
|
|
||||||
function rollItemMacro(itemName) {
|
|
||||||
const speaker = ChatMessage.getSpeaker();
|
|
||||||
let actor;
|
|
||||||
if ( speaker.token ) actor = game.actors.tokens[speaker.token];
|
|
||||||
if ( !actor ) actor = game.actors.get(speaker.actor);
|
|
||||||
|
|
||||||
// Get matching items
|
|
||||||
const items = actor ? actor.items.filter(i => i.name === itemName) : [];
|
|
||||||
if ( items.length > 1 ) {
|
|
||||||
ui.notifications.warn(`Your controlled Actor ${actor.name} has more than one Item with name ${itemName}. The first matched item will be chosen.`);
|
|
||||||
} else if ( items.length === 0 ) {
|
|
||||||
return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
|
|
||||||
}
|
|
||||||
const item = items[0];
|
|
||||||
|
|
||||||
// Trigger the item roll
|
|
||||||
if ( item.data.type === "power" ) return actor.usePower(item);
|
|
||||||
return item.roll();
|
|
||||||
}
|
|
||||||
|
|
|
@ -95,13 +95,12 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"socket": true,
|
"socket": true,
|
||||||
"initiative": "1d20 + @abilities.dex.mod + @attributes.init.value + (@abilities.dex.value / 100)",
|
|
||||||
"gridDistance": 5,
|
"gridDistance": 5,
|
||||||
"gridUnits": "ft",
|
"gridUnits": "ft",
|
||||||
"primaryTokenAttribute": "attributes.hp",
|
"primaryTokenAttribute": "attributes.hp",
|
||||||
"secondaryTokenAttribute": null,
|
"secondaryTokenAttribute": null,
|
||||||
"minimumCoreVersion": "0.5.2",
|
"minimumCoreVersion": "0.5.6",
|
||||||
"compatibleCoreVersion": "0.6.8",
|
"compatibleCoreVersion": "0.7.2",
|
||||||
"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"
|
||||||
|
|
184
template.json
184
template.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"types": ["character", "npc"],
|
"types": ["character", "npc", "vehicle"],
|
||||||
"templates": {
|
"templates": {
|
||||||
"common": {
|
"common": {
|
||||||
"abilities": {
|
"abilities": {
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"ac": {
|
"ac": {
|
||||||
"min": 0
|
"value": 10
|
||||||
},
|
},
|
||||||
"hp": {
|
"hp": {
|
||||||
"value": 10,
|
"value": 10,
|
||||||
|
@ -43,19 +43,47 @@
|
||||||
"init": {
|
"init": {
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"bonus": 0
|
"bonus": 0
|
||||||
},
|
}
|
||||||
"speed": {
|
|
||||||
"value": "30 ft",
|
|
||||||
"special": ""
|
|
||||||
},
|
|
||||||
"powercasting": "int"
|
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"alignment": "",
|
|
||||||
"biography": {
|
"biography": {
|
||||||
"value": "",
|
"value": "",
|
||||||
"public": ""
|
"public": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"traits": {
|
||||||
|
"size": "med",
|
||||||
|
"di": {
|
||||||
|
"value": [],
|
||||||
|
"custom": ""
|
||||||
},
|
},
|
||||||
|
"dr": {
|
||||||
|
"value": [],
|
||||||
|
"custom": ""
|
||||||
|
},
|
||||||
|
"dv": {
|
||||||
|
"value": [],
|
||||||
|
"custom": ""
|
||||||
|
},
|
||||||
|
"ci": {
|
||||||
|
"value": [],
|
||||||
|
"custom": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"gc": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"creature": {
|
||||||
|
"attributes": {
|
||||||
|
"powercasting": "int",
|
||||||
|
"speed": {
|
||||||
|
"value": "30 ft",
|
||||||
|
"special": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"alignment": "",
|
||||||
"species": ""
|
"species": ""
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
|
@ -133,32 +161,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"traits": {
|
"traits": {
|
||||||
"size": "med",
|
|
||||||
"senses": "",
|
"senses": "",
|
||||||
"languages": {
|
"languages": {
|
||||||
"value": [],
|
"value": [],
|
||||||
"custom": ""
|
"custom": ""
|
||||||
},
|
|
||||||
"di": {
|
|
||||||
"value": [],
|
|
||||||
"custom": ""
|
|
||||||
},
|
|
||||||
"dr": {
|
|
||||||
"value": [],
|
|
||||||
"custom": ""
|
|
||||||
},
|
|
||||||
"dv": {
|
|
||||||
"value": [],
|
|
||||||
"custom": ""
|
|
||||||
},
|
|
||||||
"ci": {
|
|
||||||
"value": [],
|
|
||||||
"custom": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"currency": {
|
|
||||||
"gc": 0
|
|
||||||
},
|
|
||||||
"powers": {
|
"powers": {
|
||||||
"power1": {
|
"power1": {
|
||||||
"value": 0,
|
"value": 0,
|
||||||
|
@ -240,12 +248,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"character": {
|
"character": {
|
||||||
"templates": ["common"],
|
"templates": ["common", "creature"],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"death": {
|
"death": {
|
||||||
"success": 0,
|
"success": 0,
|
||||||
"failure": 0
|
"failure": 0
|
||||||
},
|
},
|
||||||
|
"encumbrance": {
|
||||||
|
"value": null,
|
||||||
|
"max": null
|
||||||
|
},
|
||||||
"exhaustion": 0,
|
"exhaustion": 0,
|
||||||
"inspiration": 0
|
"inspiration": 0
|
||||||
},
|
},
|
||||||
|
@ -297,7 +309,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npc": {
|
"npc": {
|
||||||
"templates": ["common"],
|
"templates": ["common", "creature"],
|
||||||
"details": {
|
"details": {
|
||||||
"type": "",
|
"type": "",
|
||||||
"environment": "",
|
"environment": "",
|
||||||
|
@ -322,6 +334,61 @@
|
||||||
"initiative": 0
|
"initiative": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"vehicle": {
|
||||||
|
"templates": ["common"],
|
||||||
|
"abilities": {
|
||||||
|
"int": {
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"wis": {
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"cha": {
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"ac": {
|
||||||
|
"value": null,
|
||||||
|
"motionless": ""
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"stations": false,
|
||||||
|
"value": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"2": null,
|
||||||
|
"1": null,
|
||||||
|
"0": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hp": {
|
||||||
|
"value": null,
|
||||||
|
"max": null,
|
||||||
|
"dt": null,
|
||||||
|
"mt": null
|
||||||
|
},
|
||||||
|
"capacity": {
|
||||||
|
"creature": "",
|
||||||
|
"cargo": 0
|
||||||
|
},
|
||||||
|
"speed": ""
|
||||||
|
},
|
||||||
|
"traits": {
|
||||||
|
"size": "lg",
|
||||||
|
"dimensions": "",
|
||||||
|
"di": {
|
||||||
|
"value": ["poison", "psychic"]
|
||||||
|
},
|
||||||
|
"ci": {
|
||||||
|
"value": ["blinded", "charmed", "deafened", "frightened", "paralyzed", "petrified", "poisoned", "stunned",
|
||||||
|
"unconscious"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cargo": {
|
||||||
|
"crew": [],
|
||||||
|
"passengers": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Item": {
|
"Item": {
|
||||||
|
@ -335,15 +402,15 @@
|
||||||
},
|
},
|
||||||
"source": ""
|
"source": ""
|
||||||
},
|
},
|
||||||
"speciesDescription": {
|
"speciesDescription": {
|
||||||
"data": "$characteristics-table",
|
"data": "$characteristics-table",
|
||||||
"description": {
|
"description": {
|
||||||
"value": "",
|
"value": "",
|
||||||
"chat": "",
|
"chat": "",
|
||||||
"unidentified": ""
|
"unidentified": ""
|
||||||
},
|
},
|
||||||
"traits": ""
|
"traits": ""
|
||||||
},
|
},
|
||||||
"physicalItem": {
|
"physicalItem": {
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"weight": 0,
|
"weight": 0,
|
||||||
|
@ -377,6 +444,11 @@
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"max": 0,
|
"max": 0,
|
||||||
"per": null
|
"per": null
|
||||||
|
},
|
||||||
|
"consume": {
|
||||||
|
"type": "",
|
||||||
|
"target": null,
|
||||||
|
"amount": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
|
@ -395,6 +467,17 @@
|
||||||
"dc": null,
|
"dc": null,
|
||||||
"scaling": "power"
|
"scaling": "power"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"mountable": {
|
||||||
|
"armor": {
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
"hp": {
|
||||||
|
"value": 0,
|
||||||
|
"max": 0,
|
||||||
|
"dt": null,
|
||||||
|
"conditions": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backpack": {
|
"backpack": {
|
||||||
|
@ -425,20 +508,20 @@
|
||||||
"templates": ["itemDescription", "physicalItem", "activatedEffect", "action"],
|
"templates": ["itemDescription", "physicalItem", "activatedEffect", "action"],
|
||||||
"consumableType": "potion",
|
"consumableType": "potion",
|
||||||
"uses": {
|
"uses": {
|
||||||
"value": 0,
|
"autoDestroy": false
|
||||||
"max": 0,
|
|
||||||
"per": null,
|
|
||||||
"autoUse": true,
|
|
||||||
"autoDestroy": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"equipment": {
|
"equipment": {
|
||||||
"templates": ["itemDescription", "physicalItem", "activatedEffect", "action"],
|
"templates": ["itemDescription", "physicalItem", "activatedEffect", "action", "mountable"],
|
||||||
"armor": {
|
"armor": {
|
||||||
"type": "light",
|
"type": "light",
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"dex": null
|
"dex": null
|
||||||
},
|
},
|
||||||
|
"speed": {
|
||||||
|
"value": null,
|
||||||
|
"conditions": ""
|
||||||
|
},
|
||||||
"strength": 0,
|
"strength": 0,
|
||||||
"stealth": false,
|
"stealth": false,
|
||||||
"proficient": true
|
"proficient": true
|
||||||
|
@ -456,8 +539,7 @@
|
||||||
},
|
},
|
||||||
"species": {
|
"species": {
|
||||||
"templates": ["speciesDescription"]
|
"templates": ["speciesDescription"]
|
||||||
|
},
|
||||||
},
|
|
||||||
"tool": {
|
"tool": {
|
||||||
"templates": ["itemDescription", "physicalItem"],
|
"templates": ["itemDescription", "physicalItem"],
|
||||||
"ability": "int",
|
"ability": "int",
|
||||||
|
@ -487,13 +569,13 @@
|
||||||
"prepared": false
|
"prepared": false
|
||||||
},
|
},
|
||||||
"scaling": {
|
"scaling": {
|
||||||
"mode": null,
|
"mode": "none",
|
||||||
"formula": null
|
"formula": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"weapon": {
|
"weapon": {
|
||||||
"templates": ["itemDescription", "physicalItem" ,"activatedEffect", "action"],
|
"templates": ["itemDescription", "physicalItem" ,"activatedEffect", "action", "mountable"],
|
||||||
"weaponType": "simpleM",
|
"weaponType": "simpleVW",
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"proficient": true
|
"proficient": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,31 +4,34 @@
|
||||||
<header class="sheet-header flexrow">
|
<header class="sheet-header flexrow">
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||||
|
|
||||||
<div class="header-details flexrow">
|
<section class="header-details flexrow">
|
||||||
<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>
|
||||||
|
|
||||||
<div class="charlevel">
|
<aside class="header-exp flexcol">
|
||||||
<div class="level {{#if disableExperience}}noxp{{/if}}">
|
<div class="charlevel">
|
||||||
<label>{{ localize "SW5E.Level" }} {{data.details.level}}</label>
|
<label>{{ localize "SW5E.Level" }} {{data.details.level}}</label>
|
||||||
|
<span class="levels">{{classLabels}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#unless disableExperience}}
|
{{#unless disableExperience}}
|
||||||
<div class="experience">
|
<div class="experience flexrow">
|
||||||
<input name="data.details.xp.value" type="text" value="{{data.details.xp.value}}"
|
<input name="data.details.xp.value" type="text" value="{{data.details.xp.value}}"
|
||||||
data-dtype="Number" placeholder="0"/>
|
data-dtype="Number" placeholder="0"/>
|
||||||
<span class="max"> / {{data.details.xp.max}}</span>
|
<span class="sep">/</span>
|
||||||
|
<span class="max">{{data.details.xp.max}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="xpbar">
|
<div class="xpbar">
|
||||||
<span class="bar" style="width: {{data.details.xp.pct}}%"></span>
|
<span class="bar" style="width: {{data.details.xp.pct}}%"></span>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</aside>
|
||||||
|
|
||||||
{{!-- Character Summary --}}
|
{{!-- Character Summary --}}
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.details.race" value="{{data.details.race}}" placeholder="{{ localize 'SW5E.Species' }}"/>
|
<input type="text" name="data.details.species" value="{{data.details.species}}" placeholder="{{ localize 'SW5E.Species' }}"/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.details.background" value="{{data.details.background}}" placeholder="{{ localize 'SW5E.Background' }}"/>
|
<input type="text" name="data.details.background" value="{{data.details.background}}" placeholder="{{ localize 'SW5E.Background' }}"/>
|
||||||
|
@ -36,6 +39,9 @@
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="proficiency">
|
||||||
|
<span>{{ localize "SW5E.Proficiency" }} {{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{!-- Header Attributes --}}
|
{{!-- Header Attributes --}}
|
||||||
|
@ -57,6 +63,17 @@
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="attribute">
|
||||||
|
<h4 class="attribute-name box-title">{{ localize "SW5E.HitDice" }}</h4>
|
||||||
|
<div class="attribute-value multiple">
|
||||||
|
<label class="hit-dice">{{data.attributes.hd}} <span class="sep"> / </span> {{data.details.level}}</label>
|
||||||
|
</div>
|
||||||
|
<footer class="attribute-footer">
|
||||||
|
<a class="rest short-rest">{{ localize "SW5E.RestS" }}</a>
|
||||||
|
<a class="rest long-rest">{{ localize "SW5E.RestL" }}</a>
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
|
|
||||||
<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">
|
||||||
|
@ -64,8 +81,7 @@
|
||||||
data-dtype="Number" placeholder="10"/>
|
data-dtype="Number" placeholder="10"/>
|
||||||
</div>
|
</div>
|
||||||
<footer class="attribute-footer">
|
<footer class="attribute-footer">
|
||||||
<span>{{ localize "SW5E.Proficiency" }}</span>
|
<span class="power-dc">{{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}</span>
|
||||||
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -81,18 +97,19 @@
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="attribute">
|
<li class="attribute initiative">
|
||||||
<h4 class="attribute-name box-title">{{ localize "SW5E.HitDice" }}</h4>
|
<h4 class="attribute-name box-title">{{ localize "SW5E.Initiative" }}</h4>
|
||||||
<div class="attribute-value multiple">
|
<div class="attribute-value">
|
||||||
<label class="hit-dice">{{data.attributes.hd}} / {{data.details.level}}</label>
|
<span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
|
||||||
</div>
|
</div>
|
||||||
<footer class="attribute-footer">
|
<footer class="attribute-footer">
|
||||||
<a class="rest short-rest">{{ localize "SW5E.RestS" }}</a>
|
<span>{{ localize "SW5E.Modifier" }}</span>
|
||||||
<a class="rest long-rest">{{ localize "SW5E.RestL" }}</a>
|
<input name="data.attributes.init.value" type="text" placeholder="0" data-dtype="Number"
|
||||||
|
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
|
||||||
</footer>
|
</footer>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</section>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{{!-- NPC Sheet Navigation --}}
|
{{!-- NPC Sheet Navigation --}}
|
||||||
|
@ -118,7 +135,7 @@
|
||||||
<div class="ability-modifiers flexrow">
|
<div class="ability-modifiers flexrow">
|
||||||
<span class="ability-mod" title="Modifier">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
<span class="ability-mod" title="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.proficient}}" data-dtype="Number"/>
|
||||||
<a class="proficiency-toggle ability-proficiency" title="Proficiency">{{{ability.icon}}}</a>
|
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
||||||
<span class="ability-save" title="Saving Throw">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
<span class="ability-save" title="Saving Throw">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -133,13 +150,13 @@
|
||||||
<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">{{skill.label}}</h4>
|
<h4 class="skill-name rollable">{{skill.label}}</h4>
|
||||||
<span class="skill-ability">{{skill.ability}}</span>
|
<span class="skill-ability">{{skill.ability}}</span>
|
||||||
<span class="skill-mod">{{numberFormat skill.mod decimals=0 sign=true}}</span>
|
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||||
<span class="skill-passive">({{skill.passive}})</span>
|
<span class="skill-passive">({{skill.passive}})</span>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<section class="center-pane">
|
<section class="center-pane flexcol">
|
||||||
|
|
||||||
{{!-- Body Attributes --}}
|
{{!-- Body Attributes --}}
|
||||||
<ul class="attributes flexrow">
|
<ul class="attributes flexrow">
|
||||||
|
@ -149,39 +166,27 @@
|
||||||
<input name="data.resources.{{res.name}}.label" type="text" value="{{res.label}}"
|
<input name="data.resources.{{res.name}}.label" type="text" value="{{res.label}}"
|
||||||
placeholder="{{res.placeholder}}" />
|
placeholder="{{res.placeholder}}" />
|
||||||
</h4>
|
</h4>
|
||||||
<div class="attribute-value multiple">
|
<div class="attribute-value">
|
||||||
|
<label class="recharge checkbox">
|
||||||
|
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox" {{checked res.sr}}/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input name="data.resources.{{res.name}}.value" type="text" value="{{res.value}}"
|
<input name="data.resources.{{res.name}}.value" type="text" value="{{res.value}}"
|
||||||
data-dtype="Number" placeholder="0"/>
|
data-dtype="Number" placeholder="0"/>
|
||||||
<span class="sep"> / </span>
|
<span class="sep"> / </span>
|
||||||
<input name="data.resources.{{res.name}}.max" type="text" value="{{res.max}}"
|
<input name="data.resources.{{res.name}}.max" type="text" value="{{res.max}}"
|
||||||
data-dtype="Number" placeholder="0"/>
|
data-dtype="Number" placeholder="0"/>
|
||||||
</div>
|
|
||||||
<footer class="attribute-footer">
|
<label class="recharge checkbox">
|
||||||
<label class="checkbox">
|
|
||||||
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox" {{checked res.sr}}/>
|
|
||||||
</label>
|
|
||||||
<label class="checkbox">
|
|
||||||
{{ localize "SW5E.AbbreviationLR" }} <input name="data.resources.{{res.name}}.lr" type="checkbox" {{checked res.lr}}/>
|
{{ localize "SW5E.AbbreviationLR" }} <input name="data.resources.{{res.name}}.lr" type="checkbox" {{checked res.lr}}/>
|
||||||
</label>
|
</label>
|
||||||
</footer>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<li class="attribute initiative">
|
|
||||||
<h4 class="attribute-name box-title">{{ localize "SW5E.Initiative" }}</h4>
|
|
||||||
<div class="attribute-value">
|
|
||||||
<span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
|
|
||||||
</div>
|
|
||||||
<footer class="attribute-footer">
|
|
||||||
<span>{{ localize "SW5E.Modifier" }}</span>
|
|
||||||
<input name="data.attributes.init.value" type="text" placeholder="0" data-dtype="Number"
|
|
||||||
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
|
|
||||||
</footer>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{!-- Counters --}}
|
{{!-- Counters --}}
|
||||||
<div class="counters flexrow">
|
<div class="counters">
|
||||||
<div class="counter flexrow death-saves">
|
<div class="counter flexrow death-saves">
|
||||||
<h4 class="death-save rollable">{{ localize "SW5E.DeathSave" }}</h4>
|
<h4 class="death-save rollable">{{ localize "SW5E.DeathSave" }}</h4>
|
||||||
<div class="counter-value">
|
<div class="counter-value">
|
||||||
|
@ -193,14 +198,14 @@
|
||||||
value="{{data.attributes.death.failure}}"/>
|
value="{{data.attributes.death.failure}}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="counter flexrow">
|
<div class="counter flexrow exhaustion">
|
||||||
<h4>{{ localize "SW5E.Exhaustion" }}</h4>
|
<h4>{{ localize "SW5E.Exhaustion" }}</h4>
|
||||||
<div class="counter-value">
|
<div class="counter-value">
|
||||||
<input type="text" name="data.attributes.exhaustion" data-dtype="Number" placeholder="0"
|
<input type="text" name="data.attributes.exhaustion" data-dtype="Number" placeholder="0"
|
||||||
value="{{data.attributes.exhaustion}}" />
|
value="{{data.attributes.exhaustion}}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="counter flexrow">
|
<div class="counter flexrow inspiration">
|
||||||
<h4>{{ localize "SW5E.Inspiration" }}</h4>
|
<h4>{{ localize "SW5E.Inspiration" }}</h4>
|
||||||
<div class="counter-value">
|
<div class="counter-value">
|
||||||
<input type="checkbox" name="data.attributes.inspiration" data-dtype="Boolean"
|
<input type="checkbox" name="data.attributes.inspiration" data-dtype="Boolean"
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
<form class="{{cssClass}}" autocomplete="off">
|
<form class="{{cssClass}} flexcol limited" autocomplete="off">
|
||||||
|
|
||||||
<!-- HEADER -->
|
{{!-- Sheet Header --}}
|
||||||
<header class="sheet-header">
|
<header class="sheet-header flexrow">
|
||||||
<h1 class="charname">
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
|
||||||
</h1>
|
<section class="header-details flexrow">
|
||||||
|
<h1 class="charname">
|
||||||
|
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- BODY -->
|
{{!-- Sheet Body --}}
|
||||||
<section class="sheet-body">
|
<section class="sheet-body">
|
||||||
<div class="tab biography">
|
<div class="tab biography">
|
||||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- SIDEBAR -->
|
|
||||||
<section class="sheet-sidebar">
|
|
||||||
<img class="sheet-profile" src="{{actor.img}}" title="{{actor.name}}" height="220" width="220" data-edit="img"/>
|
|
||||||
</section>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,33 +4,79 @@
|
||||||
<header class="sheet-header flexrow">
|
<header class="sheet-header flexrow">
|
||||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||||
|
|
||||||
<div class="header-details flexrow">
|
<section class="header-details flexrow">
|
||||||
<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>
|
||||||
|
|
||||||
<div class="charlevel">
|
<aside class="header-exp flexcol">
|
||||||
<div class="level">
|
<div class="cr">
|
||||||
<label>{{ localize "SW5E.AbbreviationCR" }}</label>
|
<label>{{ localize "SW5E.AbbreviationCR" }}</label>
|
||||||
<input name="data.details.cr" type="text" value="{{labels.cr}}" placeholder="1"/>
|
<input name="data.details.cr" type="text" value="{{labels.cr}}" placeholder="1"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="experience">
|
<div class="experience">
|
||||||
<span>{{data.details.xp.value}} XP</span>
|
<span>{{data.details.xp.value}} XP</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
|
|
||||||
<ul class="summary">
|
{{!-- Character Summary --}}
|
||||||
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.details.type" value="{{data.details.type}}" placeholder="{{ localize 'SW5E.Type' }}"/>
|
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" name="data.details.type" value="{{data.details.type}}" placeholder="{{ localize 'SW5E.Type' }}"/>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.details.source" value="{{data.details.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
<input type="text" name="data.details.source" value="{{data.details.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
|
{{!-- Header Attributes --}}
|
||||||
|
<ul class="attributes flexrow">
|
||||||
|
<li class="attribute health">
|
||||||
|
<h4 class="attribute-name box-title rollable">{{ localize "SW5E.Health" }}</h4>
|
||||||
|
<div class="attribute-value multiple">
|
||||||
|
<input name="data.attributes.hp.value" type="text" value="{{data.attributes.hp.value}}"
|
||||||
|
data-dtype="Number" placeholder="10"/>
|
||||||
|
<span class="sep"> / </span>
|
||||||
|
<input name="data.attributes.hp.max" type="text" value="{{data.attributes.hp.max}}"
|
||||||
|
data-dtype="Number" placeholder="10"/>
|
||||||
|
</div>
|
||||||
|
<footer class="attribute-footer">
|
||||||
|
<input name="data.attributes.hp.formula" class="hpformula" type="text" placeholder="{{ localize 'SW5E.HealthFormula' }}"
|
||||||
|
value="{{data.attributes.hp.formula}}"/>
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="attribute">
|
||||||
|
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
||||||
|
<div class="attribute-value">
|
||||||
|
<input name="data.attributes.ac.value" type="text" value="{{data.attributes.ac.value}}"
|
||||||
|
data-dtype="Number" placeholder="10"/>
|
||||||
|
</div>
|
||||||
|
<footer class="attribute-footer">
|
||||||
|
<span>{{ localize "SW5E.Proficiency" }}</span>
|
||||||
|
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="attribute">
|
||||||
|
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
|
||||||
|
<div class="attribute-value">
|
||||||
|
<input name="data.attributes.speed.value" type="text"
|
||||||
|
value="{{data.attributes.speed.value}}" placeholder="0"/>
|
||||||
|
</div>
|
||||||
|
<footer class="attribute-footer">
|
||||||
|
<input type="text" class="speed" name="data.attributes.speed.special"
|
||||||
|
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{{!-- NPC Sheet Navigation --}}
|
{{!-- NPC Sheet Navigation --}}
|
||||||
|
@ -54,7 +100,7 @@
|
||||||
<div class="ability-modifiers flexrow">
|
<div class="ability-modifiers flexrow">
|
||||||
<span class="ability-mod" title="Modifier">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
<span class="ability-mod" title="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.proficient}}" data-dtype="Number"/>
|
||||||
<a class="proficiency-toggle ability-proficiency" title="Proficiency">{{{ability.icon}}}</a>
|
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
||||||
<span class="ability-save" title="Saving Throw">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
<span class="ability-save" title="Saving Throw">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -69,58 +115,16 @@
|
||||||
<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">{{skill.label}}</h4>
|
<h4 class="skill-name rollable">{{skill.label}}</h4>
|
||||||
<span class="skill-ability">{{skill.ability}}</span>
|
<span class="skill-ability">{{skill.ability}}</span>
|
||||||
<span class="skill-mod">{{numberFormat skill.mod decimals=0 sign=true}}</span>
|
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||||
<span class="skill-passive">({{skill.passive}})</span>
|
<span class="skill-passive">({{skill.passive}})</span>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<section class="center-pane">
|
<section class="center-pane flexcol">
|
||||||
|
|
||||||
{{!-- Attributes --}}
|
|
||||||
<ul class="attributes flexrow">
|
|
||||||
<li class="attribute health">
|
|
||||||
<h4 class="attribute-name box-title rollable">{{ localize "SW5E.Health" }}</h4>
|
|
||||||
<div class="attribute-value multiple">
|
|
||||||
<input name="data.attributes.hp.value" type="text" value="{{data.attributes.hp.value}}"
|
|
||||||
data-dtype="Number" placeholder="10"/>
|
|
||||||
<span class="sep"> / </span>
|
|
||||||
<input name="data.attributes.hp.max" type="text" value="{{data.attributes.hp.max}}"
|
|
||||||
data-dtype="Number" placeholder="10"/>
|
|
||||||
</div>
|
|
||||||
<footer class="attribute-footer">
|
|
||||||
<input name="data.attributes.hp.formula" class="hpformula" type="text" placeholder="{{ localize 'SW5E.HealthFormula' }}"
|
|
||||||
value="{{data.attributes.hp.formula}}"/>
|
|
||||||
</footer>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="attribute">
|
|
||||||
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
|
||||||
<div class="attribute-value">
|
|
||||||
<input name="data.attributes.ac.value" type="text" value="{{data.attributes.ac.value}}"
|
|
||||||
data-dtype="Number" placeholder="10"/>
|
|
||||||
</div>
|
|
||||||
<footer class="attribute-footer">
|
|
||||||
<span>{{ localize "SW5E.Proficiency" }}</span>
|
|
||||||
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
|
||||||
</footer>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="attribute">
|
|
||||||
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
|
|
||||||
<div class="attribute-value">
|
|
||||||
<input name="data.attributes.speed.value" type="text"
|
|
||||||
value="{{data.attributes.speed.value}}" placeholder="0"/>
|
|
||||||
</div>
|
|
||||||
<footer class="attribute-footer">
|
|
||||||
<input type="text" class="speed" name="data.attributes.speed.special"
|
|
||||||
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
|
|
||||||
</footer>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{{!-- Legendary Actions --}}
|
{{!-- Legendary Actions --}}
|
||||||
<div class="counters flexrow">
|
<div class="counters">
|
||||||
<div class="counter flexrow legendary">
|
<div class="counter flexrow legendary">
|
||||||
<h4>{{ localize "SW5E.LegAct" }}</h4>
|
<h4>{{ localize "SW5E.LegAct" }}</h4>
|
||||||
<div class="counter-value">
|
<div class="counter-value">
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<div class="inventory-filters">
|
{{#unless isVehicle}}
|
||||||
|
<div class="inventory-filters flexrow">
|
||||||
<ul class="filter-list flexrow" data-filter="features">
|
<ul class="filter-list flexrow" data-filter="features">
|
||||||
<li class="filter-title">{{localize "SW5E.Filter"}}</li>
|
|
||||||
<li class="filter-item" data-filter="action">{{localize "SW5E.Action"}}</li>
|
<li class="filter-item" data-filter="action">{{localize "SW5E.Action"}}</li>
|
||||||
<li class="filter-item" data-filter="bonus">{{localize "SW5E.BonusAction"}}</li>
|
<li class="filter-item" data-filter="bonus">{{localize "SW5E.BonusAction"}}</li>
|
||||||
<li class="filter-item" data-filter="reaction">{{localize "SW5E.Reaction"}}</li>
|
<li class="filter-item" data-filter="reaction">{{localize "SW5E.Reaction"}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
<ol class="inventory-list">
|
<ol class="inventory-list">
|
||||||
{{#each sections as |section sid|}}
|
{{#each sections as |section sid|}}
|
||||||
|
@ -17,6 +18,12 @@
|
||||||
<div class="item-detail item-action">{{localize "SW5E.Usage"}}</div>
|
<div class="item-detail item-action">{{localize "SW5E.Usage"}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if section.columns}}
|
||||||
|
{{#each section.columns}}
|
||||||
|
<div class="item-detail {{css}}">{{label}}</div>
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if ../owner}}
|
{{#if ../owner}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a class="item-control item-create" title="{{localize 'SW5E.FeatureAdd'}}" {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
|
<a class="item-control item-create" title="{{localize 'SW5E.FeatureAdd'}}" {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
|
||||||
|
@ -56,13 +63,34 @@
|
||||||
<div class="item-detail player-class">
|
<div class="item-detail player-class">
|
||||||
{{item.data.subclass}}
|
{{item.data.subclass}}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-detail">
|
<div class="item-detail item-action">
|
||||||
Level {{item.data.levels}}
|
Level {{item.data.levels}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if section.columns}}
|
||||||
|
{{#each section.columns}}
|
||||||
|
<div class="item-detail {{css}}">
|
||||||
|
{{#with (getProperty item property)}}
|
||||||
|
{{#if ../editable}}
|
||||||
|
<input type="text" value="{{this}}" placeholder="—"
|
||||||
|
data-dtype="{{../editable}}">
|
||||||
|
{{else}}
|
||||||
|
{{this}}
|
||||||
|
{{/if}}
|
||||||
|
{{/with}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if ../../owner}}
|
{{#if ../../owner}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
|
{{#if section.crewable}}
|
||||||
|
<a class="item-control item-toggle {{item.toggleClass}}"
|
||||||
|
title="{{item.toggleTitle}}">
|
||||||
|
<i class="fas fa-sun"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
<a class="item-control item-edit" title="Edit Item"><i class="fas fa-edit"></i></a>
|
<a class="item-control item-edit" title="Edit Item"><i class="fas fa-edit"></i></a>
|
||||||
<a class="item-control item-delete" title="Delete Item"><i class="fas fa-trash"></i></a>
|
<a class="item-control item-delete" title="Delete Item"><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<div class="inventory-filters powerbook-filters">
|
<div class="inventory-filters powerbook-filters flexrow">
|
||||||
<div class="form-group powercasting-ability">
|
<div class="form-group powercasting-ability">
|
||||||
<h3>{{localize "SW5E.PowerAbility"}}</h3>
|
{{#unless isNPC}}
|
||||||
|
<label>{{localize "SW5E.PowerAbility"}}</label>
|
||||||
|
{{else}}
|
||||||
|
<label>{{localize "SW5E.Level"}}</label>
|
||||||
|
<input class="powercasting-level" type="text" name="data.details.powerLevel"
|
||||||
|
value="{{data.details.powerLevel}}" data-dtype="Number" placeholder="0"/>
|
||||||
|
{{/unless}}
|
||||||
<select name="data.attributes.powercasting" data-type="String">
|
<select name="data.attributes.powercasting" data-type="String">
|
||||||
{{#select data.attributes.powercasting}}
|
{{#select data.attributes.powercasting}}
|
||||||
<option value="">{{localize "SW5E.None"}}</option>
|
<option value="">{{localize "SW5E.None"}}</option>
|
||||||
|
@ -9,21 +15,15 @@
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/select}}
|
{{/select}}
|
||||||
</select>
|
</select>
|
||||||
{{#if isNPC}}
|
|
||||||
<h3>{{localize "SW5E.PowercasterLevel"}}</h3>
|
|
||||||
<input class="powercasting-level" type="text" name="data.details.powerLevel"
|
|
||||||
value="{{data.details.powerLevel}}" data-dtype="Number" placeholder="0"/>
|
|
||||||
{{/if}}
|
|
||||||
<h3 class="power-dc">{{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="filter-list flexrow" data-filter="powerbook">
|
<ul class="filter-list flexrow" data-filter="powerbook">
|
||||||
<li class="filter-title">{{localize "SW5E.Filter"}}</li>
|
|
||||||
<li class="filter-item" data-filter="action">{{localize "SW5E.Action"}}</li>
|
<li class="filter-item" data-filter="action">{{localize "SW5E.Action"}}</li>
|
||||||
<li class="filter-item" data-filter="bonus">{{localize "SW5E.BonusAction"}}</li>
|
<li class="filter-item" data-filter="bonus">{{localize "SW5E.BonusAction"}}</li>
|
||||||
<li class="filter-item" data-filter="reaction">{{localize "SW5E.Reaction"}}</li>
|
<li class="filter-item" data-filter="reaction">{{localize "SW5E.Reaction"}}</li>
|
||||||
<li class="filter-item" data-filter="concentration">{{localize "SW5E.AbbreviationConc"}}</li>
|
<li class="filter-item" data-filter="concentration">{{localize "SW5E.AbbreviationConc"}}</li>
|
||||||
<li class="filter-item" data-filter="ritual">{{localize "SW5E.Ritual"}}</li>
|
<li class="filter-item" data-filter="ritual">{{localize "SW5E.Ritual"}}</li>
|
||||||
|
<li class="filter-item" data-filter="prepared">{{localize "SW5E.Prepared"}}{{#if preparedPowers}} ({{preparedPowers}}){{/if}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
</a>
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<span class="power-slots">{{{section.uses}}}</span>
|
<span>{{{section.uses}}}</span>
|
||||||
<span class="sep"> / </span>
|
<span class="sep"> / </span>
|
||||||
<span class="power-max">{{{section.slots}}}</span>
|
<span class="power-max">{{{section.slots}}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
<div class="item-name flexrow rollable">
|
<div class="item-name flexrow rollable">
|
||||||
<div class="item-image" style="background-image: url({{item.img}})"></div>
|
<div class="item-image" style="background-image: url({{item.img}})"></div>
|
||||||
<h4>{{item.name}}</h4>
|
<h4>{{item.name}}</h4>
|
||||||
{{#if item.data.uses.value }}
|
{{#if item.data.uses.per }}
|
||||||
<div class="item-detail power-uses">Uses {{item.data.uses.value}} / {{item.data.uses.max}}</div>
|
<div class="item-detail power-uses">Uses {{item.data.uses.value}} / {{item.data.uses.max}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,95 +10,115 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#unless isVehicle}}
|
||||||
<div class="form-group {{#unless data.traits.senses}}inactive{{/unless}}">
|
<div class="form-group {{#unless data.traits.senses}}inactive{{/unless}}">
|
||||||
<label>{{localize "SW5E.Senses"}}</label>
|
<label>{{localize "SW5E.Senses"}}</label>
|
||||||
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/>
|
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group {{data.traits.languages.cssClass}}">
|
<div class="form-group {{data.traits.languages.cssClass}}">
|
||||||
<label for="data.traits.languages">{{localize "SW5E.Languages"}}</label>
|
<label>{{localize "SW5E.Languages"}}</label>
|
||||||
|
<a class="trait-selector" data-options="languages" data-target="data.traits.languages">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.languages.selected as |v k|}}
|
{{#each data.traits.languages.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="languages"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
<div class="form-group {{data.traits.di.cssClass}}">
|
<div class="form-group {{data.traits.di.cssClass}}">
|
||||||
<label for="data.traits.di">{{localize "SW5E.DamImm"}}</label>
|
<label>{{localize "SW5E.DamImm"}}</label>
|
||||||
|
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.di">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.di.selected as |v k|}}
|
{{#each data.traits.di.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="damageTypes"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group {{data.traits.dr.cssClass}}">
|
<div class="form-group {{data.traits.dr.cssClass}}">
|
||||||
<label for="data.traits.dr">{{localize "SW5E.DamRes"}}</label>
|
<label>{{localize "SW5E.DamRes"}}</label>
|
||||||
|
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dr">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.dr.selected as |v k|}}
|
{{#each data.traits.dr.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="damageTypes"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group {{data.traits.dv.cssClass}}">
|
<div class="form-group {{data.traits.dv.cssClass}}">
|
||||||
<label for="data.traits.dv">{{localize "SW5E.DamVuln"}}</label>
|
<label>{{localize "SW5E.DamVuln"}}</label>
|
||||||
|
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dv">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.dv.selected as |v k|}}
|
{{#each data.traits.dv.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="damageTypes"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group {{data.traits.ci.cssClass}}">
|
<div class="form-group {{data.traits.ci.cssClass}}">
|
||||||
<label for="data.traits.ci">{{localize "SW5E.ConImm"}}</label>
|
<label>{{localize "SW5E.ConImm"}}</label>
|
||||||
|
<a class="trait-selector" data-options="conditionTypes" data-target="data.traits.ci">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.ci.selected as |v k|}}
|
{{#each data.traits.ci.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="conditionTypes"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if isCharacter}}
|
{{#if isCharacter}}
|
||||||
<div class="form-group {{data.traits.weaponProf.cssClass}}">
|
<div class="form-group {{data.traits.weaponProf.cssClass}}">
|
||||||
<label for="data.traits.weaponProf">{{localize "SW5E.TraitWeaponProf"}}</label>
|
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||||
|
<a class="trait-selector" data-options="weaponProficiencies" data-target="data.traits.weaponProf">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.weaponProf.selected as |v k|}}
|
{{#each data.traits.weaponProf.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="weaponProficiencies"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group {{data.traits.armorProf.cssClass}}">
|
<div class="form-group {{data.traits.armorProf.cssClass}}">
|
||||||
<label for="data.traits.armorProf">{{localize "SW5E.TraitArmorProf"}}</label>
|
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
||||||
|
<a class="trait-selector" data-options="armorProficiencies" data-target="data.traits.armorProf">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.armorProf.selected as |v k|}}
|
{{#each data.traits.armorProf.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="armorProficiencies"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group {{data.traits.toolProf.cssClass}}">
|
<div class="form-group {{data.traits.toolProf.cssClass}}">
|
||||||
<label for="data.traits.toolProf">{{localize "SW5E.TraitToolProf"}}</label>
|
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
||||||
|
<a class="trait-selector" data-options="toolProficiencies" data-target="data.traits.toolProf">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
<ul class="traits-list">
|
<ul class="traits-list">
|
||||||
{{#each data.traits.toolProf.selected as |v k|}}
|
{{#each data.traits.toolProf.selected as |v k|}}
|
||||||
<li class="tag {{k}}">{{v}}</li>
|
<li class="tag {{k}}">{{v}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
<a class="trait-selector" data-options="toolProficiencies"><i class="fas fa-edit"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#unless isVehicle}}
|
||||||
<div class="form-group ">
|
<div class="form-group ">
|
||||||
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
||||||
<a class="configure-flags"><i class="fas fa-cog"></i></a>
|
<a class="configure-flags"><i class="fas fa-cog"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
|
159
templates/actors/vehicle-sheet.html
Normal file
159
templates/actors/vehicle-sheet.html
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||||
|
<header class="sheet-header flexrow">
|
||||||
|
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
||||||
|
data-edit="img">
|
||||||
|
<section class="header-details flexrow">
|
||||||
|
<h1 class="charnam">
|
||||||
|
<input name="name" type="text" value="{{actor.name}}"
|
||||||
|
placeholder="{{localize 'SW5E.Name'}}">
|
||||||
|
</h1>
|
||||||
|
<ul class="summary flexrow">
|
||||||
|
<li>
|
||||||
|
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>{{localize 'SW5E.Vehicle'}}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" name="data.traits.dimensions"
|
||||||
|
value="{{data.traits.dimensions}}"
|
||||||
|
placeholder="{{localize 'SW5E.Dimensions'}}">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="text" name="data.details.source"
|
||||||
|
value="{{data.details.source}}"
|
||||||
|
placeholder="{{localize 'SW5E.Source'}}">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="attributes flexrow">
|
||||||
|
<li class="attribute health">
|
||||||
|
<h4 class="attribute-name box-title">{{localize 'SW5E.health'}}</h4>
|
||||||
|
<div class="attribute-value multiple">
|
||||||
|
<input name="data.attributes.hp.value" type="text" placeholder="—"
|
||||||
|
value="{{data.attributes.hp.value}}" data-dtype="Number">
|
||||||
|
<span class="sep"> / </span>
|
||||||
|
<input name="data.attributes.hp.max" type="text" placeholder="—"
|
||||||
|
value="{{data.attributes.hp.max}}" data-dtype="Number">
|
||||||
|
</div>
|
||||||
|
<footer class="attribute-footer">
|
||||||
|
<input name="data.attributes.hp.dt" type="text" class="temphp"
|
||||||
|
placeholder="{{localize 'SW5E.Threshold'}}"
|
||||||
|
value="{{data.attributes.hp.dt}}" data-dtype="Number">
|
||||||
|
<input name="data.attributes.hp.mt" type="text" class="temphp"
|
||||||
|
placeholder="{{localize 'SW5E.VehicleMishap'}}"
|
||||||
|
value="{{data.attributes.hp.mt}}" data-dtype="Number">
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
|
<li class="attribute">
|
||||||
|
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
||||||
|
<div class="attribute-value">
|
||||||
|
<input name="data.attributes.ac.value" type="text" placeholder="—"
|
||||||
|
value="{{data.attributes.ac.value}}" data-dtype="Number">
|
||||||
|
</div>
|
||||||
|
<footer class="attribute-footer">
|
||||||
|
<input type="text" name="data.attributes.ac.motionless"
|
||||||
|
placeholder="—" value="{{data.attributes.ac.motionless}}">
|
||||||
|
</footer>
|
||||||
|
</li>
|
||||||
|
<li class="attribute">
|
||||||
|
<h4 class="attribute-name box-title">{{localize 'SW5E.Speed'}}</h4>
|
||||||
|
<div class="attribute-value">
|
||||||
|
<input name="data.attributes.speed.value" type="text" placeholder="—"
|
||||||
|
value="{{data.attributes.speed.value}}">
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="sheet-navigation tabs" data-group="primary">
|
||||||
|
<a class="item active" data-tab="attributes">{{localize 'SW5E.Attributes'}}</a>
|
||||||
|
<a class="item" data-tab="features">{{localize 'SW5E.Features'}}</a>
|
||||||
|
<a class="item" data-tab="cargo">{{localize 'SW5E.VehicleCargoCrew'}}</a>
|
||||||
|
<a class="item" data-tab="biography">{{localize 'SW5E.Description'}}</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="sheet-body">
|
||||||
|
<div class="tab attributes flexrow" data-group="primary" data-tab="attributes">
|
||||||
|
<ul class="ability-scores flexrow">
|
||||||
|
{{#each data.abilities as |ability id|}}
|
||||||
|
<li class="ability" data-ability="{{id}}">
|
||||||
|
<h4 class="ability-name box-title rollable">{{ability.label}}</h4>
|
||||||
|
<input class="ability-score" name="data.abilities.{{id}}.value" type="text"
|
||||||
|
value="{{ability.value}}" data-dtype="Number" placeholder="0">
|
||||||
|
<div class="ability-modifiers flexrow">
|
||||||
|
<span class="ability-mod" title="{{localize 'SW5E.Modifier'}}">
|
||||||
|
{{numberFormat ability.mod decimals=0 sign=true}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
<section class="center-pane flexcol">
|
||||||
|
<div class="counters">
|
||||||
|
<div class="counter flexrow creature-cap">
|
||||||
|
<h4>{{localize 'SW5E.VehicleCreatureCapacity'}}</h4>
|
||||||
|
<div class="counter-value">
|
||||||
|
<input type="text" placeholder="—"
|
||||||
|
name="data.attributes.capacity.creature"
|
||||||
|
value="{{data.attributes.capacity.creature}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="counter flexrow cargo-cap">
|
||||||
|
<h4>{{localize 'SW5E.VehicleCargoCapacity'}}</h4>
|
||||||
|
<div class="counter-value">
|
||||||
|
<input type="text" name="data.attributes.capacity.cargo" placeholder="0"
|
||||||
|
data-dtype="Number" value="{{data.attributes.capacity.cargo}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="counter flexrow stations">
|
||||||
|
<h4>{{localize 'SW5E.VehicleActionStations'}}</h4>
|
||||||
|
<div class="counter-value">
|
||||||
|
<input name="data.attributes.actions.stations" type="checkbox"
|
||||||
|
data-dtype="Boolean" value="{{data.attributes.actions.stations}}"
|
||||||
|
{{checked data.attributes.actions.stations}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="counter flexrow actions">
|
||||||
|
<h4>{{localize 'SW5E.ActionPl'}}</h4>
|
||||||
|
<div class="counter-value">
|
||||||
|
<input type="text" name="data.attributes.actions.value" placeholder="0"
|
||||||
|
data-dtype="Number" value="{{data.attributes.actions.value}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="counter flexrow action-thresholds">
|
||||||
|
<h4>{{localize 'SW5E.VehicleActionThresholds'}}</h4>
|
||||||
|
<div class="counter-value">
|
||||||
|
<span class="sep"><</span>
|
||||||
|
<input type="text" placeholder="—" data-dtype="Number"
|
||||||
|
value="{{data.attributes.actions.thresholds.[2]}}"
|
||||||
|
name="data.attributes.actions.thresholds.2">
|
||||||
|
<span class="sep"><</span>
|
||||||
|
<input type="text" placeholder="—" data-dtype="Number"
|
||||||
|
value="{{data.attributes.actions.thresholds.[1]}}"
|
||||||
|
name="data.attributes.actions.thresholds.1">
|
||||||
|
<span class="sep"><</span>
|
||||||
|
<input type="text" placeholder="—" data-dtype="Number"
|
||||||
|
value="{{data.attributes.actions.thresholds.[0]}}"
|
||||||
|
name="data.attributes.actions.thresholds.0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{> 'systems/sw5e/templates/actors/parts/actor-traits.html'}}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab features flexcol" data-group="primary" data-tab="features">
|
||||||
|
{{> 'systems/sw5e/templates/actors/parts/actor-features.html' sections=features}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
||||||
|
{{> 'systems/sw5e/templates/actors/parts/actor-inventory.html' sections=cargo}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||||
|
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||||
|
button=true owner=owner editable=editable}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</form>
|
|
@ -1,26 +1,43 @@
|
||||||
<form id="ability-use-form">
|
<form id="ability-use-form">
|
||||||
<p>{{ localize "SW5E.AbilityUseHint" }} <strong>{{item.name}}</strong> {{ localize "SW5E.Ability" }}.</p>
|
<p>{{ title }}</p>
|
||||||
|
<p class="notes">{{note}}</p>
|
||||||
<p type="note">
|
{{#each errors}}
|
||||||
{{#if recharges}}
|
<p class="notification error">{{localize this}}</p>
|
||||||
{{ localize "SW5E.AbilityUseRechargeHint" }} <strong>{{#if isCharged}}{{ localize "SW5E.AbilityUseCharged" }}{{else}}{{ localize "SW5E.AbilityUseDepleted" }}{{/if}}</strong>.</p>
|
{{/each}}
|
||||||
{{else}}
|
|
||||||
{{ localize "SW5E.AbilityUseWarnStart" }} <strong>{{uses.value}} {{ localize "SW5E.of" }} {{uses.max}}</strong> {{ localize "SW5E.AbilityUseWarnEnd" }} {{perLabel}}.
|
|
||||||
{{/if}}
|
|
||||||
{{#unless canUse}}
|
|
||||||
{{ localize "SW5E.AbilityUseCantUse" }}
|
|
||||||
{{/unless}}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
{{#if hasPowerSlots}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="checkbox">{{ localize "SW5E.AbilityUseConsume" }} <input type="checkbox" name="consume" {{checked consume}}/></label>
|
<label>{{ localize "SW5E.PowerCastUpcast" }}</label>
|
||||||
|
<div class="form-fields">
|
||||||
{{#if hasPlaceableTemplate}}
|
<select name="level" {{#unless canUpcast}}disabled{{/unless}}>
|
||||||
<div class="form-group">
|
{{#select item.data.level}}
|
||||||
<label class="checkbox">{{ localize "SW5E.PlaceTemplate" }}
|
{{#each powerLevels as |l|}}
|
||||||
<input type="checkbox" name="placeTemplate" checked/>
|
<option value="{{l.level}}" {{#unless l.canCast}}disabled{{/unless}}>{{l.label}}</option>
|
||||||
</label>
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if canUpcast}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox"><input type="checkbox" name="consumeSlot" checked/>{{ localize "SW5E.PowerCastConsume" }}</label>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if hasLimitedUses}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox"><input type="checkbox" name="consumeUse" checked/>{{ localize "SW5E.AbilityUseConsume" }}</label>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if hasPlaceableTemplate}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" name="placeTemplate" checked/>
|
||||||
|
{{ localize "SW5E.PlaceTemplate" }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/>
|
<input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<p class="notes">{{flag.hint}}</p>
|
<p class="notes">{{localize flag.hint}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
20
templates/apps/long-rest.html
Normal file
20
templates/apps/long-rest.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<form id="long-rest" class="dialog-content" onsubmit="event.preventDefault();">
|
||||||
|
<p>Take a long rest? On a long rest you will recover hit points, half your maximum hit dice, class resources, limited use item charges, and spell slots.</p>
|
||||||
|
|
||||||
|
{{#if promptNewDay}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Is New Day?</label>
|
||||||
|
<input type="checkbox" name="newDay" {{checked newDay}}/>
|
||||||
|
<p class="hint">Recover limited use abilities which recharge "per day"?</p>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="dialog-buttons">
|
||||||
|
{{#each buttons as |button id|}}
|
||||||
|
<button class="dialog-button" data-button="{{id}}">
|
||||||
|
{{{button.icon}}}
|
||||||
|
{{{button.label}}}
|
||||||
|
</button>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -18,6 +18,15 @@
|
||||||
<p class="notes">{{ localize "SW5E.ShortRestNoHD" }}</p>
|
<p class="notes">{{ localize "SW5E.ShortRestNoHD" }}</p>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if promptNewDay}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Is New Day?</label>
|
||||||
|
<input type="checkbox" name="newDay" {{checked newDay}}/>
|
||||||
|
<p class="hint">Recover limited use abilities which recharge "per day"?</p>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<div class="dialog-buttons">
|
<div class="dialog-buttons">
|
||||||
{{#each buttons as |button id|}}
|
{{#each buttons as |button id|}}
|
||||||
<button class="dialog-button" data-button="{{id}}">
|
<button class="dialog-button" data-button="{{id}}">
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ol>
|
</ol>
|
||||||
|
{{#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}}
|
||||||
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.Save"}}</button>
|
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.Save"}}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li></li>
|
<li></li>
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<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>
|
||||||
|
@ -113,10 +113,6 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- Granted Abilities (TODO) --}}
|
|
||||||
<h3 class="form-header">{{ localize "SW5E.GrantedAbilities" }}</h3>
|
|
||||||
<p class="notification warning">This is still to-do</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
{{lookup config.consumableTypes data.consumableType }}
|
{{lookup config.consumableTypes data.consumableType }}
|
||||||
</li>
|
</li>
|
||||||
|
@ -73,14 +73,8 @@
|
||||||
|
|
||||||
{{!-- Item Activation Template --}}
|
{{!-- Item Activation Template --}}
|
||||||
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
|
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
|
||||||
|
|
||||||
{{!-- Consumable Usage --}}
|
|
||||||
{{#if data.activation.type}}
|
{{#if data.activation.type}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.ItemConsumableUsage" }}</label>
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" name="data.uses.autoUse" {{checked data.uses.autoUse}}/> {{ localize "SW5E.ItemConsumeOnUse" }}
|
|
||||||
</label>
|
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input type="checkbox" name="data.uses.autoDestroy" {{checked data.uses.autoDestroy}}/> {{ localize "SW5E.ItemDestroyEmpty" }}
|
<input type="checkbox" name="data.uses.autoDestroy" {{checked data.uses.autoDestroy}}/> {{ localize "SW5E.ItemDestroyEmpty" }}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
{{lookup config.equipmentTypes data.armor.type }}
|
{{lookup config.equipmentTypes data.armor.type }}
|
||||||
</li>
|
</li>
|
||||||
|
@ -57,6 +57,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#unless isMountable}}
|
||||||
{{!-- Equipment Status --}}
|
{{!-- Equipment Status --}}
|
||||||
<div class="form-group stacked">
|
<div class="form-group stacked">
|
||||||
<label>{{ localize "SW5E.ItemEquipmentStatus" }}</label>
|
<label>{{ localize "SW5E.ItemEquipmentStatus" }}</label>
|
||||||
|
@ -73,33 +74,34 @@
|
||||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
{{#unless isWeapon }}
|
|
||||||
{{!-- ArmorProperties Formula --}}
|
{{#unless isWeapon }}
|
||||||
<div class="form-group stacked weapon-properties">
|
{{!-- ArmorProperties Formula --}}
|
||||||
<h4 class="armorproperties-header">
|
<div class="form-group stacked weapon-properties">
|
||||||
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
<h4 class="armorproperties-header">
|
||||||
<a class="armorproperties-control add-armorproperties"><i class="fas fa-plus"></i></a>
|
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
||||||
</h4>
|
<a class="armorproperties-control add-armorproperties"><i class="fas fa-plus"></i></a>
|
||||||
<ol class="armorproperties-parts form-group">
|
</h4>
|
||||||
{{#each data.armorproperties.parts as |part i| }}
|
<ol class="armorproperties-parts form-group">
|
||||||
<li class="armorproperties-part flexrow" data-armorproperties-part="{{i}}">
|
{{#each data.armorproperties.parts as |part i| }}
|
||||||
|
<li class="armorproperties-part flexrow" data-armorproperties-part="{{i}}">
|
||||||
<select name="data.armorproperties.parts.{{i}}.1">
|
|
||||||
{{#select (lookup this "1") }}
|
<select name="data.armorproperties.parts.{{i}}.1">
|
||||||
<option value="">{{ localize "SW5E.None" }}</option>
|
{{#select (lookup this "1") }}
|
||||||
{{#each ../config.armorpropertiesTypes as |name type|}}
|
<option value="">{{ localize "SW5E.None" }}</option>
|
||||||
<option value="{{type}}">{{name}}</option>
|
{{#each ../config.armorpropertiesTypes as |name type|}}
|
||||||
{{/each}}
|
<option value="{{type}}">{{name}}</option>
|
||||||
{{/select}}
|
{{/each}}
|
||||||
</select>
|
{{/select}}
|
||||||
<input type="text" name="data.armorproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
</select>
|
||||||
<a class="armorproperties-control delete-armorproperties"><i class="fas fa-minus"></i></a>
|
<input type="text" name="data.armorproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
||||||
</li>
|
<a class="armorproperties-control delete-armorproperties"><i class="fas fa-minus"></i></a>
|
||||||
{{/each}}
|
</li>
|
||||||
</ol>
|
{{/each}}
|
||||||
</div>
|
</ol>
|
||||||
{{/unless}}
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
{{!-- Armor Class --}}
|
{{!-- Armor Class --}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -109,6 +111,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#unless isMountable}}
|
||||||
{{!-- Dexterity Modifier --}}
|
{{!-- Dexterity Modifier --}}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ localize "SW5E.ItemEquipmentDexMod" }}</label>
|
<label>{{ localize "SW5E.ItemEquipmentDexMod" }}</label>
|
||||||
|
@ -130,6 +133,21 @@
|
||||||
<label>{{ localize "SW5E.ItemEquipmentStealthDisav" }}</label>
|
<label>{{ localize "SW5E.ItemEquipmentStealthDisav" }}</label>
|
||||||
<input type="checkbox" name="data.stealth" value="1" {{checked data.stealth}}/>
|
<input type="checkbox" name="data.stealth" value="1" {{checked data.stealth}}/>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#if isMountable}}
|
||||||
|
{{> 'systems/sw5e/templates/items/parts/item-mountable.html'}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize 'SW5E.Speed'}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input type="text" name="data.speed.value" value="{{data.speed.value}}"
|
||||||
|
placeholder="0" data-dtype="Number">
|
||||||
|
<span class="sep">{{localize 'SW5E.FeetAbbr'}}</span>
|
||||||
|
<input type="text" name="data.speed.conditions"
|
||||||
|
value="{{data.speed.conditions}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<h3 class="form-header">{{ localize "SW5E.ItemEquipmentUsage" }}</h3>
|
<h3 class="form-header">{{ localize "SW5E.ItemEquipmentUsage" }}</h3>
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
{{labels.featType}}
|
{{labels.featType}}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -23,6 +23,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if isCrewed}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize 'SW5E.Cover'}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="data.cover" data-dtype="Number">
|
||||||
|
{{#select data.cover}}
|
||||||
|
<option value="">—</option>
|
||||||
|
{{#each config.cover as |v k|}}
|
||||||
|
<option value="{{k}}">{{v}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{!-- Ability Target --}}
|
{{!-- Ability Target --}}
|
||||||
<div class="form-group input-select-select">
|
<div class="form-group input-select-select">
|
||||||
|
@ -99,4 +114,28 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
|
||||||
|
{{!-- Consumption --}}
|
||||||
|
<div class="form-group uses-per">
|
||||||
|
<label>{{ localize "SW5E.ConsumeTitle" }}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="data.consume.type">
|
||||||
|
{{#select data.consume.type}}
|
||||||
|
<option value=""></option>
|
||||||
|
{{#each config.abilityConsumptionTypes as |name key|}}
|
||||||
|
<option value="{{key}}">{{name}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
|
<select name="data.consume.target">
|
||||||
|
{{#select data.consume.target}}
|
||||||
|
<option value=""></option>
|
||||||
|
{{#each abilityConsumptionTargets as |name key|}}
|
||||||
|
<option value="{{key}}">{{name}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{/select}}
|
||||||
|
</select>
|
||||||
|
<input type="text" name="data.consume.amount" value="{{data.consume.amount}}" data-dtype="Number"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
19
templates/items/parts/item-mountable.html
Normal file
19
templates/items/parts/item-mountable.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize 'SW5E.Health'}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input type="text" name="data.hp.value" value="{{data.hp.value}}"
|
||||||
|
placeholder="0" data-dtype="Number">
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<input type="text" name="data.hp.max" value="{{data.hp.max}}" placeholder="0"
|
||||||
|
data-dtype="Number">
|
||||||
|
<input type="text" name="data.hp.dt" value="{{data.hp.dt}}" data-dtype="Number"
|
||||||
|
placeholder="{{localize 'SW5E.Threshold'}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize 'SW5E.HealthConditions'}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input type="text" name="data.hp.conditions" value="{{data.hp.conditions}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
{{labels.level}}
|
{{labels.level}}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<span class="item-status">{{itemStatus}}</span>
|
<span class="item-status">{{itemStatus}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="summary">
|
<ul class="summary flexrow">
|
||||||
<li>
|
<li>
|
||||||
{{lookup config.weaponTypes data.weaponType }}
|
{{lookup config.weaponTypes data.weaponType }}
|
||||||
</li>
|
</li>
|
||||||
|
@ -56,22 +56,26 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#unless isMountable}}
|
||||||
{{!-- Weapon Status --}}
|
{{!-- Weapon Status --}}
|
||||||
<div class="form-group stacked">
|
<div class="form-group stacked">
|
||||||
<label>{{ localize "SW5E.ItemWeaponStatus" }}</label>
|
<label>{{ localize "SW5E.ItemWeaponStatus" }}</label>
|
||||||
<label class="checkbox">
|
<div class="form-fields">
|
||||||
<input type="checkbox" name="data.proficient" {{checked data.proficient}}/> {{ localize "SW5E.Proficient" }}
|
<label class="checkbox">
|
||||||
</label>
|
<input type="checkbox" name="data.proficient" {{checked data.proficient}}/> {{ localize "SW5E.Proficient" }}
|
||||||
<label class="checkbox">
|
</label>
|
||||||
<input type="checkbox" name="data.equipped" {{checked data.equipped}}/> {{ localize "SW5E.Equipped" }}
|
<label class="checkbox">
|
||||||
</label>
|
<input type="checkbox" name="data.equipped" {{checked data.equipped}}/> {{ localize "SW5E.Equipped" }}
|
||||||
<label class="checkbox">
|
</label>
|
||||||
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
<label class="checkbox">
|
||||||
</label>
|
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
||||||
<label class="checkbox">
|
</label>
|
||||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
<label class="checkbox">
|
||||||
</label>
|
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
{{#if sss}}
|
{{#if sss}}
|
||||||
{{!-- Weapon Properties --}}
|
{{!-- Weapon Properties --}}
|
||||||
|
@ -85,33 +89,44 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if isWeapon }}
|
{{#if isWeapon }}
|
||||||
<div class="form-group stacked weapon-properties">
|
<div class="form-group stacked weapon-properties">
|
||||||
{{!-- weaponproperties Formula --}}
|
{{!-- weaponproperties Formula --}}
|
||||||
<h4 class="weaponproperties-header">
|
<h4 class="weaponproperties-header">
|
||||||
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
||||||
<a class="weaponproperties-control add-weaponproperties"><i class="fas fa-plus"></i></a>
|
<a class="weaponproperties-control add-weaponproperties"><i class="fas fa-plus"></i></a>
|
||||||
</h4>
|
</h4>
|
||||||
<ol class="weaponproperties-parts form-group">
|
<ol class="weaponproperties-parts form-group">
|
||||||
{{#each data.weaponproperties.parts as |part i| }}
|
{{#each data.weaponproperties.parts as |part i| }}
|
||||||
<li class="weaponproperties-part flexrow" data-weaponproperties-part="{{i}}">
|
<li class="weaponproperties-part flexrow" data-weaponproperties-part="{{i}}">
|
||||||
|
|
||||||
<select name="data.weaponproperties.parts.{{i}}.1">
|
<select name="data.weaponproperties.parts.{{i}}.1">
|
||||||
{{#select (lookup this "1") }}
|
{{#select (lookup this "1") }}
|
||||||
<option value="">{{ localize "SW5E.None" }}</option>
|
<option value="">{{ localize "SW5E.None" }}</option>
|
||||||
{{#each ../config.weaponProperties as |name type|}}
|
{{#each ../config.weaponProperties as |name type|}}
|
||||||
<option value="{{type}}">{{name}}</option>
|
<option value="{{type}}">{{name}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/select}}
|
{{/select}}
|
||||||
</select>
|
</select>
|
||||||
<input type="text" name="data.weaponproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
<input type="text" name="data.weaponproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
||||||
<a class="weaponproperties-control delete-weaponproperties"><i class="fas fa-minus"></i></a>
|
<a class="weaponproperties-control delete-weaponproperties"><i class="fas fa-minus"></i></a>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ol>
|
</ol>
|
||||||
{{/if}}
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</div>
|
{{#if isMountable}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize 'SW5E.ArmorClass'}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input type="text" name="data.armor.value" value="{{data.armor.value}}"
|
||||||
|
data-dtype="Number">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{> 'systems/dnd5e/templates/items/parts/item-mountable.html'}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<h3 class="form-header">{{ localize "SW5E.ItemWeaponUsage" }}</h3>
|
<h3 class="form-header">{{ localize "SW5E.ItemWeaponUsage" }}</h3>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue