From d392b568db847acd0032db7ef100fb09d5ca6abd Mon Sep 17 00:00:00 2001 From: Professor Bunbury <69010799+professorbunbury@users.noreply.github.com> Date: Fri, 23 Oct 2020 17:45:27 -0400 Subject: [PATCH] Spot the link / entityClass error! --- .gitignore | 1 + README.md | 28 +- gulpfile.js | 32 + lang/en.json | 485 +-- lang/en.json.bak | 597 ---- module/actor/entity.js | 976 ++++-- module/actor/sheets/base.js | 323 +- module/actor/sheets/npc.js | 58 +- module/actor/sheets/vehicle.js | 381 +++ module/apps/ability-use-dialog.js | 152 +- module/apps/actor-flags.js | 31 +- module/apps/long-rest.js | 69 + module/apps/short-rest.js | 53 +- module/apps/spell-cast-dialog.js | 102 - module/apps/trait-selector.js | 2 +- module/canvas.js | 64 +- module/chat.js | 50 +- module/classFeatures.js | 35 + module/combat.js | 59 +- module/config.js.bak | 839 ----- module/dice.js | 359 ++- module/macros.js | 60 + module/migration.js | 58 + module/pixi/ability-template.js | 9 +- module/settings.js | 29 +- module/templates.js | 4 +- package-lock.json | 3101 +++++++++++++++++++ packs/Icons/Portraits/Ezrukh Khim'bar.webp | Bin 0 -> 38964 bytes packs/Icons/Portraits/Ki-Adi Codux.webp | Bin 0 -> 37276 bytes template.json | 190 +- template.json.bak | 488 --- templates/actors/character-sheet.html | 145 +- templates/actors/limited-sheet.html | 23 +- templates/actors/npc-sheet.html | 126 +- templates/actors/parts/actor-inventory.html | 52 +- templates/actors/parts/actor-powerbook.html | 23 +- templates/actors/parts/actor-traits.html | 54 +- templates/actors/vehicle-sheet.html | 159 + templates/apps/ability-use.html | 55 +- templates/apps/actor-flags.html | 2 +- templates/apps/long-rest.html | 20 + templates/apps/short-rest.html | 9 + templates/apps/spell-cast.html | 34 - templates/apps/trait-selector.html | 2 + templates/chat/item-card.html | 2 +- templates/items/backpack.html | 2 +- templates/items/classfeature.html | 73 + templates/items/equipment.html | 57 +- templates/items/feat.html | 2 +- templates/items/loot.html | 2 +- templates/items/parts/item-activation.html | 51 +- templates/items/parts/item-mountable.html | 19 + templates/items/power.html | 23 +- templates/items/tool.html | 2 +- templates/items/weapon.html | 69 +- ui/parchment.jpg | Bin 37349 -> 0 bytes 56 files changed, 6353 insertions(+), 3288 deletions(-) create mode 100644 gulpfile.js delete mode 100644 lang/en.json.bak create mode 100644 module/actor/sheets/vehicle.js create mode 100644 module/apps/long-rest.js delete mode 100644 module/apps/spell-cast-dialog.js create mode 100644 module/classFeatures.js delete mode 100644 module/config.js.bak create mode 100644 module/macros.js create mode 100644 package-lock.json create mode 100644 packs/Icons/Portraits/Ezrukh Khim'bar.webp create mode 100644 packs/Icons/Portraits/Ki-Adi Codux.webp delete mode 100644 template.json.bak create mode 100644 templates/actors/vehicle-sheet.html create mode 100644 templates/apps/long-rest.html delete mode 100644 templates/apps/spell-cast.html create mode 100644 templates/items/classfeature.html create mode 100644 templates/items/parts/item-mountable.html delete mode 100644 ui/parchment.jpg diff --git a/.gitignore b/.gitignore index b8f6700e..7c9eefc3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ hs_err_pid* # IDE Folders .idea/ .vs/ +node_modules diff --git a/README.md b/README.md index 936a4572..da0a6445 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,27 @@ -# sw5e -SW5E Foundry VTT System +# Foundry Virtual Tabletop - SW5e Game System -For easy install +This game system for [Foundry Virtual Tabletop](http://foundryvtt.com) provides character sheet and game system +support for the SW5E roleplaying game. -In the Foundry Configuration and Setup window go to the Game Systems tab, click on the "Install System" button, and -place the following link into the Manifest URL at the bottom +This system provides character sheet support for Actors and Items, mechanical support for dice and rules necessary to +play games of SW5E, and compendium content for Monsters, Heroes, Items, Powers, Class Features, Monster +Features, and more! + +The software component of this system is distributed under the GNUv3 license. + +## Installation Instructions + +To install and use the SW5e system for Foundry Virtual Tabletop, simply paste the following URL into the +**Install System** dialog on the Setup menu of the application. https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json + +If you wish to manually install the system, you must clone or extract it into the ``Data/systems/sw5e`` folder. You +may do this by cloning the repository or downloading a zip archive from the +[Releases Page](https://github.com/unrealkakeman89/sw5e). + +## Community Contribution + +Code and content contributions are accepted. Please feel free to submit issues to the issue tracker or submit merge +requests for code changes. Approval for such requests involves code and (if necessary) design review by The Dev Team. +Please reach out on the SW5E Foundry Dev Discord with any questions. diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..509c8d30 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,32 @@ +const gulp = require('gulp'); +const less = require('gulp-less'); + +/* ----------------------------------------- */ +/* Compile LESS +/* ----------------------------------------- */ + +const SW5E_LESS = ["less/*.less"]; +function compileLESS() { + return gulp.src("less/sw5e.less") + .pipe(less()) + .pipe(gulp.dest("./")) +} +const css = gulp.series(compileLESS); + +/* ----------------------------------------- */ +/* Watch Updates +/* ----------------------------------------- */ + +function watchUpdates() { + gulp.watch(SW5E_LESS, css); +} + +/* ----------------------------------------- */ +/* Export Tasks +/* ----------------------------------------- */ + +exports.default = gulp.series( + gulp.parallel(css), + watchUpdates +); +exports.css = css; diff --git a/lang/en.json b/lang/en.json index 44cef657..55629375 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,29 +1,58 @@ { +"ACTOR.TypeCharacter": "Player Character", +"ACTOR.TypeNpc": "Non-Player Character", +"ACTOR.TypeVehicle": "Vehicle", +"ITEM.TypeBackpack": "Backpack", +"ITEM.TypeClass": "Class", +"ITEM.TypeConsumable": "Consumable", +"ITEM.TypeEquipment": "Equipment", +"ITEM.TypeFeat": "Feature", +"ITEM.TypeLoot": "Loot", +"ITEM.TypePower": "Power", +"ITEM.TypeTool": "Tool", +"ITEM.TypeWeapon": "Weapon", + "Star Wars 5th Edition": "Star Wars 5th Edition", "SW5E.title": "Star Wars 5th Edition", "SW5E.AbbreviationCR": "CR", "SW5E.AbbreviationConc": "Conc.", "SW5E.AbbreviationDC": "DC", "SW5E.AbbreviationLR": "LR", +"SW5E.AbbreviationLevel": "Lvl.", "SW5E.AbbreviationLbs": "lbs.", "SW5E.AbbreviationSR": "SR", "SW5E.Ability": "Ability", -"SW5E.AbilityCha": "Charisma", -"SW5E.AbilityCon": "Constitution", -"SW5E.AbilityDex": "Dexterity", -"SW5E.AbilityInt": "Intelligence", -"SW5E.AbilityModifier": "Ability Modifier", "SW5E.AbilityStr": "Strength", -"SW5E.AbilityUseCantUse": "You are not currently able to use this ability!", -"SW5E.AbilityUseCharged": "charged", -"SW5E.AbilityUseConsume": "Consume Available Usage?", -"SW5E.AbilityUseDepleted": "depleted", -"SW5E.AbilityUseHint": "Configure how you would like to use the", -"SW5E.AbilityUseRechargeHint": "This ability uses a recharge mechanic and is currently", -"SW5E.AbilityUseWarnEnd": "available uses per", -"SW5E.AbilityUseWarnStart": "This ability has", +"SW5E.AbilityStrAbbr": "str", +"SW5E.AbilityCon": "Constitution", +"SW5E.AbilityConAbbr": "con", +"SW5E.AbilityDex": "Dexterity", +"SW5E.AbilityDexAbbr": "dex", +"SW5E.AbilityInt": "Intelligence", +"SW5E.AbilityIntAbbr": "int", "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 Power", +"SW5E.AbilityUseUse": "Use Ability", + "SW5E.Action": "Action", +"SW5E.ActionPl": "Actions", "SW5E.ActionAbil": "Ability Check", "SW5E.ActionHeal": "Healing", "SW5E.ActionMPAK": "Melee Power Attack", @@ -33,6 +62,8 @@ "SW5E.ActionRWAK": "Ranged Weapon Attack", "SW5E.ActionSave": "Saving Throw", "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.Advantage": "Advantage", "SW5E.Alignment": "Alignment", @@ -44,15 +75,18 @@ "SW5E.AlignmentLN": "Lawful Neutral", "SW5E.AlignmentND": "Neutral Dark", "SW5E.AlignmentNL": "Neutral Light", -"SW5E.AlignmentBN": "Balenced Neutral", +"SW5E.AlignmentBN": "Balanced Neutral", "SW5E.Archetypes": "Archetypes", +"SW5E.Appearance": "Appearance", "SW5E.ArmorClass": "Armor Class", +"SW5E.AC": "AC", "SW5E.ArmorProperties": "Armor Properties", "SW5E.ArmorProperAbsorptive": "Absorptive", "SW5E.ArmorProperAgile": "Agile", "SW5E.ArmorProperAnchor": "Anchor", "SW5E.ArmorProperAvoidant": "Avoidant", "SW5E.ArmorProperBarbed": "Barbed", +"SW5E.ArmorProperBulky": "Bulky", "SW5E.ArmorProperCharging": "Charging", "SW5E.ArmorProperConcealing": "Concealing", "SW5E.ArmorProperCumbersome": "Cumbersome", @@ -65,6 +99,7 @@ "SW5E.ArmorProperLightweight": "Lightweight", "SW5E.ArmorProperMagnetic": "Magnetic", "SW5E.ArmorProperObscured": "Obscured", +"SW5E.ArmorProperObtrusive": "Obtrusive", "SW5E.ArmorProperPowered": "Powered", "SW5E.ArmorProperReactive": "Reactive", "SW5E.ArmorProperRegulated": "Regulated", @@ -74,22 +109,26 @@ "SW5E.ArmorProperSilent": "Silent", "SW5E.ArmorProperSpiked": "Spiked", "SW5E.ArmorProperSteadfast": "Steadfast", +"SW5E.ArmorProperStrength": "Strength Rqmt.", "SW5E.ArmorProperVersatile": "Versatile", "SW5E.Attack": "Attack", +"SW5E.AttackPl": "Attacks", +"SW5E.AttackRoll": "Attack Roll", "SW5E.Attributes": "Attributes", "SW5E.Attuned": "Attuned", "SW5E.Background": "Background", "SW5E.Biography": "Biography", +"SW5E.Bonds": "Bonds", "SW5E.BonusAbilityCheck": "Global Ability Check Bonus", "SW5E.BonusAbilitySave": "Global Saving Throw Bonus", "SW5E.BonusAbilitySkill": "Global Skill Check Bonus", "SW5E.BonusAction": "Bonus Action", -"SW5E.BonusMSAttack": "Melee Power Attack Bonus", -"SW5E.BonusMSDamage": "Melee Power Damage Bonus", +"SW5E.BonusMPAttack": "Melee Power Attack Bonus", +"SW5E.BonusMPDamage": "Melee Power Damage Bonus", "SW5E.BonusMWAttack": "Melee Weapon Attack Bonus", "SW5E.BonusMWDamage": "Melee Weapon Damage Bonus", -"SW5E.BonusRSAttack": "Ranged Power Attack Bonus", -"SW5E.BonusRSDamage": "Ranged Power Damage Bonus", +"SW5E.BonusRPAttack": "Ranged Power Attack Bonus", +"SW5E.BonusRPDamage": "Ranged Power Damage Bonus", "SW5E.BonusRWAttack": "Ranged Weapon Attack Bonus", "SW5E.BonusRWDamage": "Ranged Weapon Damage Bonus", "SW5E.BonusSaveForm": "Update Bonuses", @@ -116,6 +155,7 @@ "SW5E.ConBlinded": "Blinded", "SW5E.ConCharmed": "Charmed", "SW5E.ConDeafened": "Deafened", +"SW5E.ConDiseased": "Diseased", "SW5E.ConExhaustion": "Exhaustion", "SW5E.ConFrightened": "Frightened", "SW5E.ConGrappled": "Grappled", @@ -128,9 +168,22 @@ "SW5E.ConProne": "Prone", "SW5E.ConRestrained": "Restrained", "SW5E.ConShocked": "Shocked", +"SW5E.ConSlowed": "Slowed", "SW5E.ConStunned": "Stunned", "SW5E.ConUnconscious": "Unconscious", "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.ConsumablePoison": "Poison", "SW5E.ConsumableAdrenal": "Adrenal", @@ -138,9 +191,19 @@ "SW5E.ConsumableMedpac": "Medpac", "SW5E.ConsumableTrinket": "Trinket", "SW5E.ConsumableTechnology": "Technology", -"SW5E.ConsumableAmmunition": "Ammunition", +"SW5E.ConsumableForce": "Force Points", +"SW5E.ConsumableTech": "Tech Points", +"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.CostGP": "Cost (GP)", +"SW5E.Cover": "Cover", +"SW5E.CoverHalf": "Half", +"SW5E.CoverThreeQuarters": "Three Quarters", +"SW5E.CoverTotal": "Total", +"SW5E.CostGP": "Cost (CR)", "SW5E.Critical": "Critical", "SW5E.CriticalHit": "Critical Hit", "SW5E.Currency": "Currency", @@ -151,24 +214,31 @@ "SW5E.DamRes": "Damage Resistances", "SW5E.DamVuln": "Damage Vulnerabilities", "SW5E.Damage": "Damage", - "SW5E.DamageAcid": "Acid", - "SW5E.DamageCold": "Cold", - "SW5E.DamageEnergy": "Energy", - "SW5E.DamageFire": "Fire", - "SW5E.DamageForce": "Force", - "SW5E.DamageIon": "ION", - "SW5E.DamageKinetic": "Kinetic", - "SW5E.DamageLightning": "Lightning", - "SW5E.DamageNecrotic": "Necrotic", - "SW5E.DamagePoison": "Poison", - "SW5E.DamagePsychic": "Psychic", - "SW5E.DamageSonic": "Sonic", +"SW5E.DamageAcid": "Acid", +"SW5E.DamageCold": "Cold", +"SW5E.DamageEnergy": "Energy", +"SW5E.DamageFire": "Fire", +"SW5E.DamageForce": "Force", +"SW5E.DamageIon": "Ion", +"SW5E.DamageKinetic": "Kinetic", +"SW5E.DamageLightning": "Lightning", +"SW5E.DamageNecrotic": "Necrotic", +"SW5E.DamagePoison": "Poison", +"SW5E.DamagePsychic": "Psychic", +"SW5E.DamageSonic": "Sonic", +"SW5E.DamageRoll": "Damage Roll", "SW5E.Day": "Day", "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.DefaultAbilityCheck": "Default Ability Check", "SW5E.Description": "Description", "SW5E.Details": "Details", +"SW5E.Dimensions": "Dimensions", "SW5E.Disadvantage": "Disadvantage", "SW5E.DistAny": "Any", "SW5E.DistFt": "Feet", @@ -176,6 +246,7 @@ "SW5E.DistSelf": "Self", "SW5E.DistTouch": "Touch", "SW5E.Duration": "Duration", +"SW5E.Effects": "Effects", "SW5E.EquipmentBonus": "Magical Bonus", "SW5E.EquipmentClothing": "Clothing", "SW5E.EquipmentHeavy": "Heavy Armor", @@ -185,19 +256,20 @@ "SW5E.EquipmentShield": "Shield", "SW5E.EquipmentShieldProficiency": "Shields", "SW5E.EquipmentTrinket": "Trinket", +"SW5E.EquipmentVehicle": "Vehicle Equipment", "SW5E.Equipped": "Equipped", "SW5E.Exhaustion": "Exhaustion", "SW5E.Expertise": "Expertise", "SW5E.FeatureActionRecharge": "Action Recharge", +"SW5E.Flaws": "Flaws", -<<<<<<< Updated upstream -======= "SW5E.ItemTypeArchetype": "Archetype", "SW5E.ItemTypeBackground": "Background", "Sw5E.ItemTypeBackgroundPl": "Backgrounds", ->>>>>>> Stashed changes "SW5E.ItemTypeClass": "Class", "SW5E.ItemTypeClassPl": "Class Levels", +"SW5E.ItemTypeClassFeat": "Class Feature", +"SW5E.ItemTypeClassFeats": "Class Features", "SW5E.ItemTypeConsumable": "Consumable", "SW5E.ItemTypeConsumablePl": "Consumables", "SW5E.ItemTypeContainer": "Container", @@ -214,6 +286,7 @@ "SW5E.ItemTypePowerPl": "Powers", "SW5E.ItemTypeWeapon": "Weapon", "SW5E.ItemTypeWeaponPl": "Weapons", +"SW5E.ItemNoUses": "{name} has no available uses remaining.", "SW5E.FeatureActive": "Active Abilities", "SW5E.FeatureAdd": "Create Feature", @@ -223,12 +296,37 @@ "SW5E.FeatureRechargeResult": "1d6 Result", "SW5E.FeatureUsage": "Feature Usage", "SW5E.Features": "Features", +"SW5E.FeetAbbr": "ft.", "SW5E.Filter": "Filter", "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.FlagsInstructions": "Configure character features and traits which fine-tune behaviors of the SW5e system.", "SW5E.FlagsSave": "Update 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.Formula": "Formula", "SW5E.GrantedAbilities": "Granted Abilities", @@ -236,9 +334,15 @@ "SW5E.Healing": "Healing", "SW5E.HealingTemp": "Healing (Temporary)", "SW5E.Health": "Health", +"SW5E.HP": "Health", +"SW5E.HealthConditions": "Health Conditions", "SW5E.HealthFormula": "Health Formula", +"SW5E.HPFormula": "Health Formula", "SW5E.HitDice": "Hit Dice", +"SW5E.HitDiceRoll": "Roll Hit Dice", "SW5E.HitDiceUsed": "Hit Dice Used", +"SW5E.HitDiceWarn": "{name} has no available {formula} Hit Dice remaining!", +"SW5E.Ideals": "Ideals", "SW5E.Identified": "Identified", "SW5E.Initiative": "Initiative", "SW5E.Inspiration": "Inspiration", @@ -248,12 +352,10 @@ "SW5E.ItemActivationCost": "Activation Cost", "SW5E.ItemAttackBonus": "Attack Roll Bonus", "SW5E.ItemConsumableActivation": "Consumable Activation", +"SW5E.ItemConsumableUsage": "Consumable Usage", "SW5E.ItemConsumableDetails": "Consumable Details", - "SW5E.ItemConsumableStatus": "Consumable Status", "SW5E.ItemConsumableType": "Consumable Type", -"SW5E.ItemConsumableUsage": "Consumable Usage", -"SW5E.ItemConsumeOnUse": "Consume on Use", "SW5E.ItemContainerCapacity": "Capacity", "SW5E.ItemContainerCapacityItems": "Items", "SW5E.ItemContainerCapacityType": "Capacity Type", @@ -276,6 +378,10 @@ "SW5E.ItemEquipmentUsage": "Equipment Usage", "SW5E.ItemName": "Item Name", +"SW5E.ItemNew": "New {type}", +"SW5E.ItemRechargeCheck": "{name} recharge check", +"SW5E.ItemRechargeFailure": "failure!", +"SW5E.ItemRechargeSuccess": "success!", "SW5E.ItemRequiredStr": "Required Strength", "SW5E.ItemToolProficiency": "Tool Proficiency", @@ -287,112 +393,9 @@ "SW5E.ItemWeaponType": "Weapon Type", "SW5E.ItemWeaponUsage": "Weapon Usage", "SW5E.JackOfAllTrades": "Jack of all Trades", -"SW5E.LairAct": "Lair Action", +"SW5E.LairAct": "Uses Lair Action", +"SW5E.LairActionLabel": "Lair Action", "SW5E.Languages": "Languages", -<<<<<<< Updated upstream - "SW5E.LanguagesAleena": "Aleena", - "SW5E.LanguagesAntarian": "Antarian", - "SW5E.LanguagesAnzellan": "Anzellan", - "SW5E.LanguagesAqualish": "Aqualish", - "SW5E.LanguagesArdennian": "Ardennian", - "SW5E.LanguagesBalosur": "Balosur", - "SW5E.LanguagesBarabel": "Barabel", - "SW5E.LanguagesBasic": "Basic", - "SW5E.LanguagesBesalisk": "Besalisk", - "SW5E.LanguagesBinary": "Binary", - "SW5E.LanguagesBith": "Bith", - "SW5E.LanguagesBocce": "Bocce", - "SW5E.LanguagesBothese": "Bothese", - "SW5E.LanguagesCatharese": "Catharese", - "SW5E.LanguagesCerean": "Cerean", - "SW5E.LanguagesChadra-Fan": "Chadra-Fan", - "SW5E.LanguagesChagri": "Chagri", - "SW5E.LanguagesCheunh": "Cheunh", - "SW5E.LanguagesChevin": "Chevin", - "SW5E.LanguagesChironan": "Chironan", - "SW5E.LanguagesClawdite": "Clawdite", - "SW5E.LanguagesCodruese": "Codruese", - "SW5E.LanguagesColicoid": "Colicoid", - "SW5E.LanguagesDashadi": "Dashadi", - "SW5E.LanguagesDefel": "Defel", - "SW5E.LanguagesDevaronese": "Devaronese", - "SW5E.LanguagesDosh": "Dosh", - "SW5E.LanguagesDraethos": "Draethos", - "SW5E.LanguagesDug": "Dug", - "SW5E.LanguagesDurese": "Durese", - "SW5E.LanguagesEwokese": "Ewokese", - "SW5E.LanguagesFalleen": "Falleen", - "SW5E.LanguagesFelucianese": "Felucianese", - "SW5E.LanguagesGamorrese": "Gamorrese", - "SW5E.LanguagesGand": "Gand", - "SW5E.LanguagesGeonosian": "Geonosian", - "SW5E.LanguagesGivin": "Givin", - "SW5E.LanguagesGran": "Gran", - "SW5E.LanguagesGungan": "Gungan", - "SW5E.LanguagesHapan": "Hapan", - "SW5E.LanguagesHarchese": "Harchese", - "SW5E.LanguagesHerglese": "Herglese", - "SW5E.LanguagesHonoghran": "Honoghran", - "SW5E.LanguagesHuttese": "Huttese", - "SW5E.LanguagesIktotchese": "Iktotchese", - "SW5E.LanguagesIthorese": "Ithorese", - "SW5E.LanguagesJawaese": "Jawaese", - "SW5E.LanguagesKaleesh": "Kaleesh", - "SW5E.LanguagesKaminoan": "Kaminoan", - "SW5E.LanguagesKarkaran": "Karkaran", - "SW5E.LanguagesKelDor": "Kel Dor", - "SW5E.LanguagesKharan": "Kharan", - "SW5E.LanguagesKillik": "Killik", - "SW5E.LanguagesKlatooinian": "Klatooinian", - "SW5E.LanguagesKubazian": "Kubazian", - "SW5E.LanguagesKushiban": "Kushiban", - "SW5E.LanguagesKyuzo": "Kyuzo", - "SW5E.LanguagesLannik": "Lannik", - "SW5E.LanguagesLasat": "Lasat", - "SW5E.LanguagesLowickese": "Lowickese", - "SW5E.LanguagesMandoa": "Mando'a", - "SW5E.LanguagesMiralukese": "Miralukese", - "SW5E.LanguagesMirialan": "Mirialan", - "SW5E.LanguagesMonCal": "Mon Cal", - "SW5E.LanguagesMustafarian": "Mustafarian", - "SW5E.LanguagesMuun": "Muun", - "SW5E.LanguagesNautila": "Nautila", - "SW5E.LanguagesOrtolan": "Ortolan", - "SW5E.LanguagesPakPak": "Pak Pak", - "SW5E.LanguagesPyke": "Pyke", - "SW5E.LanguagesQuarrenese": "Quarrenese", - "SW5E.LanguagesRakata": "Rakata", - "SW5E.LanguagesRattataki": "Rattataki", - "SW5E.LanguagesRishii": "Rishii", - "SW5E.LanguagesRodese": "Rodese", - "SW5E.LanguagesSelkatha": "Selkatha", - "SW5E.LanguagesSemblan": "Semblan", - "SW5E.LanguagesShyriiwook": "Shyriiwook", - "SW5E.LanguagesShistavanen": "Shistavanen", - "SW5E.LanguagesSith": "Sith", - "SW5E.LanguagesSquibbian": "Squibbian", - "SW5E.LanguagesSriluurian": "Sriluurian", - "SW5E.LanguagesSsi-ruuvi": "Ssi-ruuvi", - "SW5E.LanguagesSullustese": "Sullustese", - "SW5E.LanguagesTalzzi": "Talzzi", - "SW5E.LanguagesThisspiasian": "Thisspiasian", - "SW5E.LanguagesTogorese": "Togorese", - "SW5E.LanguagesTogruti": "Togruti", - "SW5E.LanguagesToydarian": "Toydarian", - "SW5E.LanguagesTusken": "Tusken", - "SW5E.LanguagesTwileki": "Twi'leki", - "SW5E.LanguagesUgnaught": "Ugnaught", - "SW5E.LanguagesUmbaran": "Umbaran", - "SW5E.LanguagesUtapese": "Utapese", - "SW5E.LanguagesVerpine": "Verpine", - "SW5E.LanguagesVong": "Vong", - "SW5E.LanguagesVoss": "Voss", - "SW5E.LanguagesYevethan": "Yevethan", - "SW5E.LanguagesZabraki": "Zabraki", - "SW5E.LanguagesZygerrian": "Zygerrian", -"SW5E.LegAct": "Legd. Actions", -"SW5E.LegRes": "Legd. Resistance", -======= "SW5E.LanguagesAleena": "Aleena", "SW5E.LanguagesAntarian": "Antarian", "SW5E.LanguagesAnzellan": "Anzellan", @@ -501,11 +504,18 @@ "SW5E.LegAct": "Legendary Actions", "SW5E.LegendaryActionLabel": "Legendary Action", "SW5E.LegRes": "Legendary Resistance", ->>>>>>> Stashed changes "SW5E.Level": "Level", "SW5E.LevelScaling": "Level Scaling", "SW5E.LimitedUses": "Limited Uses", "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.LongRestResultHitDice": "{name} takes a long rest and recovers {dice} Hit Dice.", +"SW5E.LongRestResultHitPoints": "{name} takes a long rest and recovers {health} Hit Points.", +"SW5E.LongRestResultShort": "{name} takes a long rest.", "SW5E.Max": "Max", "SW5E.Modifier": "Modifier", "SW5E.Name": "Character Name", @@ -514,6 +524,9 @@ "SW5E.Normal": "Normal", "SW5E.NotProficient": "Not Proficient", "SW5E.OtherFormula": "Other Formula", +"SW5E.PactMagic": "Pact Magic", +"SW5E.Passive": "Passive", +"SW5E.PersonalityTraits": "Personality Traits", "SW5E.PlaceTemplate": "Place Measured Template", "SW5E.Polymorph": "Polymorph", "SW5E.PolymorphAcceptSettings": "Custom Settings", @@ -531,9 +544,13 @@ "SW5E.PolymorphMergeSkills": "Merge skill proficiencies (take the highest)", "SW5E.PolymorphPromptTitle": "Transforming Actor", "SW5E.PolymorphRestoreTransformation": "Restore Transformation", +"SW5E.PolymorphRevertWarn": "You do not have permission to revert this Actor's polymorphed state.", "SW5E.PolymorphTmpClass": "Temporary Class", "SW5E.PolymorphTokens": "Transform all linked tokens?", +"SW5E.PolymorphWarn": "You are not allowed to polymorph this actor!", "SW5E.PolymorphWildShape": "Wild Shape", +"SW5E.Prepared": "Prepared", +"SW5E.Concentrate": "Concentrate", "SW5E.Concentrated": "Concentrate", "SW5E.Price": "Price", "SW5E.Proficiency": "Proficiency", @@ -543,6 +560,8 @@ "SW5E.Range": "Range", "SW5E.Rarity": "Rarity", "SW5E.Reaction": "Reaction", +"SW5E.ReactionPl": "Reactions", +"SW5E.Recharge": "Recharge", "SW5E.RequiredMaterials": "Required Materials", "SW5E.Requirements": "Requirements", "SW5E.ResourcePrimary": "Resource 1", @@ -556,6 +575,14 @@ "SW5E.RollMode": "Roll Mode", "SW5E.RollSituationalBonus": "Situational Bonus?", "SW5E.Save": "Save", +"SW5E.SheetClassCharacter": "Default Character Sheet", +"SW5E.SheetClassNPC": "Default NPC Sheet", +"SW5E.SheetClassVehicle": "Default Vehicle Sheet", +"SW5E.SheetClassItem": "Default Item Sheet", + +"SW5E.SavingThrow": "Saving Throw", +"SW5E.SaveDC": "DC {dc} {ability}", +"SW5E.SavePromptTitle": "{ability} Saving Throw", "SW5E.ScalingFormula": "Scaling Formula", "SW5E.SchoolLgt": "Light", "SW5E.SchoolUni": "Universal", @@ -568,8 +595,14 @@ "SW5E.SenseTS": "Tremorsense", "SW5E.Senses": "Senses", "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.ShortRestNoHD": "No Hit Dice remaining", +"SW5E.ShortRestResult": "{name} takes a short rest spending {dice} Hit Dice to recover {health} Hit Points.", +"SW5E.ShortRestResultShort": "{name} takes a short rest.", "SW5E.ShortRestSelect": "Select Dice to Roll", "SW5E.Size": "Size", "SW5E.SizeGargantuan": "Gargantuan", @@ -596,6 +629,7 @@ "SW5E.SkillSte": "Stealth", "SW5E.SkillSur": "Survival", "SW5E.SkillTec": "Technology", +"SW5E.SkillPromptTitle": "{skill} Skill Check", "SW5E.Slots": "Slots", "SW5E.Source": "Source", "SW5E.Special": "Special", @@ -628,7 +662,8 @@ "SW5E.PowerLevel7": "7th Level", "SW5E.PowerLevel8": "8th 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.PowerName": "Power Name", "SW5E.PowerNone": "None", @@ -636,6 +671,8 @@ "SW5E.PowerPrepInnate": "Innate Powercasting", "SW5E.PowerPrepPrepared": "Prepared", "SW5E.PowerPrepAlways": "Always Prepared", +"SW5E.PowerPreparationMode": "Power Preparation Mode", +"SW5E.PowerPrepared": "Prepared", "SW5E.PowerConcentrationMode": "Power Concentration Mode", "SW5E.PowerConcentrating": "Concentrating", "SW5E.PowerProgArt": "Artificer", @@ -649,6 +686,7 @@ "SW5E.Powerbook": "Powerbook", "SW5E.SpeciesDescription": "Description", "SW5E.SpeciesTraits": "Species Traits", +"SW5E.StealthDisadvantage": "Stealth Disadvantage", "SW5E.SubclassName": "Subclass Name", "SW5E.Supply": "Supply", "SW5E.Target": "Target", @@ -657,6 +695,7 @@ "SW5E.TargetCreature": "Creature", "SW5E.TargetCube": "Cube", "SW5E.TargetCylinder": "Cylinder", +"SW5E.TargetDroid": "Droid", "SW5E.TargetEnemy": "Enemy", "SW5E.TargetLine": "Line", "SW5E.TargetObject": "Object", @@ -666,8 +705,10 @@ "SW5E.TargetSphere": "Sphere", "SW5E.TargetSquare": "Square", "SW5E.TargetWall": "Wall", -"SW5E.TargetDroid": "Droid", +"SW5E.TargetWeapon": "Weapon", +"SW5E.TargetWidth": "Line Width", "SW5E.Temp": "Temp", +"SW5E.Threshold": "Threshold", "SW5E.TimeDay": "Days", "SW5E.TimeHour": "Hours", "SW5E.TimeInst": "Instantaneous", @@ -677,38 +718,40 @@ "SW5E.TimeRound": "Rounds", "SW5E.TimeTurn": "Turns", "SW5E.TimeYear": "Years", - "SW5E.ToolArmormech": "Armormech's Tools", - "SW5E.ToolArmstech": "Armstech's Tools", - "SW5E.ToolArtificer": "Artificer's Tools", - "SW5E.ToolArtist": "Artist's Tools", - "SW5E.ToolAstrotech": "Astrotech's Tools", - "SW5E.ToolBiotech": "Biotech's Tools", - "SW5E.ToolConstructor": "Constructor's Tools", - "SW5E.ToolCybertech": "Cybertech's Tools", - "SW5E.ToolJeweler": "Jeweler's Tools", - "SW5E.ToolSurveyor": "Surveyor's Tools", - "SW5E.ToolSynthweaver": "Synthweavers's Tools", - "SW5E.ToolTinker": "Tinker's Tools", - "SW5E.ToolAntitoxkit": "Antitoxkit", - "SW5E.ToolArchaeologistKit": "Archaeologist Kit", - "SW5E.ToolAudiotechKit": "Audiotech's Kit", - "SW5E.ToolBioanalysisKit": "Bioanalysis Kit", - "SW5E.ToolBrewerKit": "Brewer's Kit", - "SW5E.ToolChefKit": "Chef's Kit", - "SW5E.ToolDemolitionKit": "Demolition Kit", - "SW5E.ToolDisguiseKit": "Disguise Kit", - "SW5E.ToolForgeryKit": "Forgery Kit", - "SW5E.ToolMechanicKit": "Mechanic's Kit", - "SW5E.ToolMunitionsKit": "Munitions Kit", - "SW5E.ToolPoisonKit": "Poisoner's Kit", - "SW5E.ToolScavengingKit": "Scavenging Kit", - "SW5E.ToolSecurityKit": "Security Kit", - "SW5E.ToolSlicerKit": "Slicer's Kit", - "SW5E.ToolSpiceKit": "Spicer's Kit", +"SW5E.ToolArmormech": "Armormech's Tools", +"SW5E.ToolArmstech": "Armstech's Tools", +"SW5E.ToolArtificer": "Artificer's Tools", +"SW5E.ToolArtist": "Artist's Tools", +"SW5E.ToolAstrotech": "Astrotech's Tools", +"SW5E.ToolBiotech": "Biotech's Tools", +"SW5E.ToolCheck": "Tool Check", +"SW5E.ToolConstructor": "Constructor's Tools", +"SW5E.ToolCybertech": "Cybertech's Tools", +"SW5E.ToolJeweler": "Jeweler's Tools", +"SW5E.ToolSurveyor": "Surveyor's Tools", +"SW5E.ToolSynthweaver": "Synthweavers's Tools", +"SW5E.ToolTinker": "Tinker's Tools", +"SW5E.ToolAntitoxkit": "Antitoxkit", +"SW5E.ToolArchaeologistKit": "Archaeologist Kit", +"SW5E.ToolAudiotechKit": "Audiotech's Kit", +"SW5E.ToolBioanalysisKit": "Bioanalysis Kit", +"SW5E.ToolBrewerKit": "Brewer's Kit", +"SW5E.ToolChefKit": "Chef's Kit", +"SW5E.ToolDemolitionKit": "Demolition Kit", +"SW5E.ToolDisguiseKit": "Disguise Kit", +"SW5E.ToolForgeryKit": "Forgery Kit", +"SW5E.ToolMechanicKit": "Mechanic's Kit", +"SW5E.ToolMunitionsKit": "Munitions Kit", +"SW5E.ToolPoisonKit": "Poisoner's Kit", +"SW5E.ToolScavengingKit": "Scavenging Kit", +"SW5E.ToolSecurityKit": "Security Kit", +"SW5E.ToolSlicerKit": "Slicer's Kit", +"SW5E.ToolSpiceKit": "Spicer's Kit", "SW5E.ToolGamingSet": "Gaming Set", "SW5E.ToolMusicalInstrument": "Musical Instrument", "SW5E.ToolVehicle": "Vehicle (Land or Water)", "SW5E.TraitArmorProf": "Armor Proficiencies", +"SW5E.TraitSave": "Update", "SW5E.TraitSelectorSpecial": "Special (Split with Semi-Colon)", "SW5E.TraitToolProf": "Tool Proficiencies", "SW5E.TraitWeaponProf": "Weapon Proficiencies", @@ -718,6 +761,20 @@ "SW5E.Usage": "Usage", "SW5E.Use": "Use", "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.VersatileDamage": "Versatile Damage", "SW5E.VsDC": "vs DC.", @@ -728,43 +785,48 @@ "SW5E.WeaponMartialB": "Martial Blaster", "SW5E.WeaponMartialLW": "Martial Lightweapon", "SW5E.WeaponNatural": "Natural", - "SW5E.WeaponPropertiesAmm": "Ammunition", - "SW5E.WeaponPropertiesBur": "Burst", - "SW5E.WeaponPropertiesDef": "Defensive", - "SW5E.WeaponPropertiesDex": "Dexterity Rqmt.", - "SW5E.WeaponPropertiesDrm": "Disarming", - "SW5E.WeaponPropertiesDgd": "Disguised", - "SW5E.WeaponPropertiesDis": "Disintegrate", - "SW5E.WeaponPropertiesDpt": "Disruptive", - "SW5E.WeaponPropertiesDou": "Double", - "SW5E.WeaponPropertiesFin": "Finesse", - "SW5E.WeaponPropertiesFix": "Fixed", - "SW5E.WeaponPropertiesFoc": "Focus", - "SW5E.WeaponPropertiesHvy": "Heavy", - "SW5E.WeaponPropertiesHid": "Hidden", - "SW5E.WeaponPropertiesKen": "Keen", - "SW5E.WeaponPropertiesLgt": "Light", - "SW5E.WeaponPropertiesLum": "Luminous", - "SW5E.WeaponPropertiesPic": "Piercing", - "SW5E.WeaponPropertiesRan": "Range", - "SW5E.WeaponPropertiesRap": "Rapid", - "SW5E.WeaponPropertiesRch": "Reach", - "SW5E.WeaponPropertiesRel": "Reload", - "SW5E.WeaponPropertiesRet": "Returning", - "SW5E.WeaponPropertiesShk": "Shocking", - "SW5E.WeaponPropertiesSpc": "Special", - "SW5E.WeaponPropertiesStr": "Strength Rqmt.", - "SW5E.WeaponPropertiesThr": "Thrown", - "SW5E.WeaponPropertiesTwo": "Two-Handed", - "SW5E.WeaponPropertiesVer": "Versatile", - "SW5E.WeaponPropertiesVic": "Vicious", +"SW5E.WeaponSiege": "Siege", +"SW5E.WeaponPropertiesAmm": "Ammunition", +"SW5E.WeaponPropertiesAut": "Auto", +"SW5E.WeaponPropertiesBur": "Burst", +"SW5E.WeaponPropertiesDef": "Defensive", +"SW5E.WeaponPropertiesDex": "Dexterity Rqmt.", +"SW5E.WeaponPropertiesDir": "Dire", +"SW5E.WeaponPropertiesDrm": "Disarming", +"SW5E.WeaponPropertiesDgd": "Disguised", +"SW5E.WeaponPropertiesDis": "Disintegrate", +"SW5E.WeaponPropertiesDpt": "Disruptive", +"SW5E.WeaponPropertiesDou": "Double", +"SW5E.WeaponPropertiesFin": "Finesse", +"SW5E.WeaponPropertiesFix": "Fixed", +"SW5E.WeaponPropertiesFoc": "Focus", +"SW5E.WeaponPropertiesHvy": "Heavy", +"SW5E.WeaponPropertiesHid": "Hidden", +"SW5E.WeaponPropertiesKen": "Keen", +"SW5E.WeaponPropertiesLgt": "Light", +"SW5E.WeaponPropertiesLum": "Luminous", +"SW5E.WeaponPropertiesMig": "Mighty", +"SW5E.WeaponPropertiesPic": "Piercing", +"SW5E.WeaponPropertiesRan": "Range", +"SW5E.WeaponPropertiesRap": "Rapid", +"SW5E.WeaponPropertiesRch": "Reach", +"SW5E.WeaponPropertiesRel": "Reload", +"SW5E.WeaponPropertiesRet": "Returning", +"SW5E.WeaponPropertiesShk": "Shocking", +"SW5E.WeaponPropertiesSil": "Silent", +"SW5E.WeaponPropertiesSpc": "Special", +"SW5E.WeaponPropertiesStr": "Strength Rqmt.", +"SW5E.WeaponPropertiesThr": "Thrown", +"SW5E.WeaponPropertiesTwo": "Two-Handed", +"SW5E.WeaponPropertiesVer": "Versatile", +"SW5E.WeaponPropertiesVic": "Vicious", "SW5E.WeaponSimpleVW": "Simple Vibroweapon", "SW5E.WeaponSimpleProficiency": "Simple Weapons", "SW5E.WeaponSimpleB": "Simple Blaster", "SW5E.WeaponSimpleLW": "Simple Lightweapon", "SW5E.Weight": "Weight", "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.power": "power", "SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.", @@ -775,12 +837,19 @@ "SETTINGS.5eAutoPowerTemplateN": "Always place Power Template", "SETTINGS.5eCurWtL": "Carried currency affects character encumbrance following the rules on PHB pg. 143.", "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.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.5eInitTBN": "Initiative Dexterity Tiebreaker", "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)" } diff --git a/lang/en.json.bak b/lang/en.json.bak deleted file mode 100644 index 17618def..00000000 --- a/lang/en.json.bak +++ /dev/null @@ -1,597 +0,0 @@ -{ -"Star Wars 5th Edition": "Star Wars 5th Edition", -"SW5E.title": "Star Wars 5th Edition", -"SW5E.AbbreviationCR": "CR", -"SW5E.AbbreviationConc": "Conc.", -"SW5E.AbbreviationDC": "DC", -"SW5E.AbbreviationLR": "LR", -"SW5E.AbbreviationLbs": "lbs.", -"SW5E.AbbreviationSR": "SR", -"SW5E.Ability": "Ability", -"SW5E.AbilityCha": "Charisma", -"SW5E.AbilityCon": "Constitution", -"SW5E.AbilityDex": "Dexterity", -"SW5E.AbilityInt": "Intelligence", -"SW5E.AbilityModifier": "Ability Modifier", -"SW5E.AbilityStr": "Strength", -"SW5E.AbilityUseCantUse": "You are not currently able to use this ability!", -"SW5E.AbilityUseCharged": "charged", -"SW5E.AbilityUseConsume": "Consume Available Usage?", -"SW5E.AbilityUseDepleted": "depleted", -"SW5E.AbilityUseHint": "Configure how you would like to use the", -"SW5E.AbilityUseRechargeHint": "This ability uses a recharge mechanic and is currently", -"SW5E.AbilityUseWarnEnd": "available uses per", -"SW5E.AbilityUseWarnStart": "This ability has", -"SW5E.AbilityWis": "Wisdom", -"SW5E.Action": "Action", -"SW5E.ActionAbil": "Ability Check", -"SW5E.ActionHeal": "Healing", -"SW5E.ActionMPAK": "Melee Power Attack", -"SW5E.ActionMWAK": "Melee Weapon Attack", -"SW5E.ActionOther": "Other", -"SW5E.ActionRPAK": "Ranged Power Attack", -"SW5E.ActionRWAK": "Ranged Weapon Attack", -"SW5E.ActionSave": "Saving Throw", -"SW5E.ActionUtil": "Utility", -"SW5E.Add": "Add", -"SW5E.Advantage": "Advantage", -"SW5E.Alignment": "Alignment", -"SW5E.AlignmentCD": "Chaotic Dark", -"SW5E.AlignmentCL": "Chaotic Light", -"SW5E.AlignmentCN": "Chaotic Neutral", -"SW5E.AlignmentLD": "Lawful Dark", -"SW5E.AlignmentLL": "Lawful Light", -"SW5E.AlignmentLN": "Lawful Neutral", -"SW5E.AlignmentND": "Neutral Dark", -"SW5E.AlignmentNL": "Neutral Light", -"SW5E.AlignmentBN": "Balenced Neutral", -"SW5E.ArmorClass": "Armor Class", -"SW5E.ArmorProperties": "Armor Properties", -"SW5E.ArmorProperAbsorptive": "Absorptive", -"SW5E.ArmorProperAgile": "Agile", -"SW5E.ArmorProperAnchor": "Anchor", -"SW5E.ArmorProperAvoidant": "Avoidant", -"SW5E.ArmorProperBarbed": "Barbed", -"SW5E.ArmorProperCharging": "Charging", -"SW5E.ArmorProperConcealing": "Concealing", -"SW5E.ArmorProperCumbersome": "Cumbersome", -"SW5E.ArmorProperGauntleted": "Gauntleted", -"SW5E.ArmorProperImbalanced": "Imbalanced", -"SW5E.ArmorProperImpermeable": "Impermeable", -"SW5E.ArmorProperInsulated": "Insulated", -"SW5E.ArmorProperInterlocking": "Interlocking", -"SW5E.ArmorProperLambent": "Lambent", -"SW5E.ArmorProperLightweight": "Lightweight", -"SW5E.ArmorProperMagnetic": "Magnetic", -"SW5E.ArmorProperObscured": "Obscured", -"SW5E.ArmorProperPowered": "Powered", -"SW5E.ArmorProperReactive": "Reactive", -"SW5E.ArmorProperRegulated": "Regulated", -"SW5E.ArmorProperReinforced": "Reinforced", -"SW5E.ArmorProperResponsive": "Responsive", -"SW5E.ArmorProperRigid": "Rigid", -"SW5E.ArmorProperSilent": "Silent", -"SW5E.ArmorProperSpiked": "Spiked", -"SW5E.ArmorProperSteadfast": "Steadfast", -"SW5E.ArmorProperVersatile": "Versatile", -"SW5E.Attack": "Attack", -"SW5E.Attributes": "Attributes", -"SW5E.Attuned": "Attuned", -"SW5E.Background": "Background", -"SW5E.Biography": "Biography", -"SW5E.BonusAbilityCheck": "Global Ability Check Bonus", -"SW5E.BonusAbilitySave": "Global Saving Throw Bonus", -"SW5E.BonusAbilitySkill": "Global Skill Check Bonus", -"SW5E.BonusAction": "Bonus Action", -"SW5E.BonusMSAttack": "Melee Power Attack Bonus", -"SW5E.BonusMSDamage": "Melee Power Damage Bonus", -"SW5E.BonusMWAttack": "Melee Weapon Attack Bonus", -"SW5E.BonusMWDamage": "Melee Weapon Damage Bonus", -"SW5E.BonusRSAttack": "Ranged Power Attack Bonus", -"SW5E.BonusRSDamage": "Ranged Power Damage Bonus", -"SW5E.BonusRWAttack": "Ranged Weapon Attack Bonus", -"SW5E.BonusRWDamage": "Ranged Weapon Damage Bonus", -"SW5E.BonusSaveForm": "Update Bonuses", -"SW5E.BonusPowerDC": "Global Power DC Bonus", -"SW5E.BonusTitle": "Configure Actor Bonuses", -"SW5E.Bonuses": "Global Bonuses", -"SW5E.BonusesHint": "Define global bonuses as formulas which are added to certain rolls. For example: 1d4 + 2", -"SW5E.BonusesInstructions": "Configure character bonuses which are added to the appropriate dice roll", -"SW5E.ChallengeRating": "Challenge Rating", -"SW5E.Charged": "Charged", -"SW5E.Charges": "Charges", -"SW5E.ChatContextDamage": "Apply Damage", -"SW5E.ChatContextHealing": "Apply Healing", -"SW5E.ChatContextDoubleDamage": "Apply Double Damage", -"SW5E.ChatContextHalfDamage": "Apply Half Damage", -"SW5E.ChatFlavor": "Chat Message Flavor", -"SW5E.ClassLevels": "Class Levels", -"SW5E.ClassName": "Class Name", -"SW5E.ClassSkillsNumber": "Number of Starting Skills", -"SW5E.ClassSkillsChosen": "Chosen Class Skills", -"SW5E.ComponentMaterial": "Material", -"SW5E.ComponentSomatic": "Somatic", -"SW5E.ComponentVerbal": "Verbal", -"SW5E.ConBlinded": "Blinded", -"SW5E.ConCharmed": "Charmed", -"SW5E.ConDeafened": "Deafened", -"SW5E.ConExhaustion": "Exhaustion", -"SW5E.ConFrightened": "Frightened", -"SW5E.ConGrappled": "Grappled", -"SW5E.ConImm": "Condition Immunities", -"SW5E.ConIncapacitated": "Incapacitated", -"SW5E.ConInvisible": "Invisible", -"SW5E.ConParalyzed": "Paralyzed", -"SW5E.ConPetrified": "Petrified", -"SW5E.ConPoisoned": "Poisoned", -"SW5E.ConProne": "Prone", -"SW5E.ConRestrained": "Restrained", -"SW5E.ConShocked": "Shocked", -"SW5E.ConStunned": "Stunned", -"SW5E.ConUnconscious": "Unconscious", -"SW5E.Concentration": "Concentration", -"SW5E.ConsumableFood": "Food", -"SW5E.ConsumablePoison": "Poison", -"SW5E.ConsumableAdrenal": "Adrenal", -"SW5E.ConsumableExplosive": "Explosive", -"SW5E.ConsumableMedpac": "Medpac", -"SW5E.ConsumableTrinket": "Trinket", -"SW5E.ConsumableTechnology": "Technology", -"SW5E.ConsumableAmmunition": "Ammunition", -"SW5E.Consumed": "Consumed", -"SW5E.CostGP": "Cost (GP)", -"SW5E.Critical": "Critical", -"SW5E.CriticalHit": "Critical Hit", -"SW5E.Currency": "Currency", -"SW5E.CurrencyConvert": "Convert All Currency", -"SW5E.CurrencyConvertHint": "Convert all carried currency to the highest possible denomination to reduce the amount of coinage carried by the character. Be wary, this action cannot be undone.", -"SW5E.CurrencyGC": "Credits", -"SW5E.DamImm": "Damage Immunities", -"SW5E.DamRes": "Damage Resistances", -"SW5E.DamVuln": "Damage Vulnerabilities", -"SW5E.Damage": "Damage", - "SW5E.DamageAcid": "Acid", - "SW5E.DamageCold": "Cold", - "SW5E.DamageEnergy": "Energy", - "SW5E.DamageFire": "Fire", - "SW5E.DamageForce": "Force", - "SW5E.DamageIon": "ION", - "SW5E.DamageKinetic": "Kinetic", - "SW5E.DamageLightning": "Lightning", - "SW5E.DamageNecrotic": "Necrotic", - "SW5E.DamagePoison": "Poison", - "SW5E.DamagePsychic": "Psychic", - "SW5E.DamageSonic": "Sonic", -"SW5E.Day": "Day", -"SW5E.DeathSave": "Death Saves", -"SW5E.Default": "Default", -"SW5E.DefaultAbilityCheck": "Default Ability Check", -"SW5E.Description": "Description", -"SW5E.Details": "Details", -"SW5E.Disadvantage": "Disadvantage", -"SW5E.DistAny": "Any", -"SW5E.DistFt": "Feet", -"SW5E.DistMi": "Miles", -"SW5E.DistSelf": "Self", -"SW5E.DistTouch": "Touch", -"SW5E.Duration": "Duration", -"SW5E.EquipmentBonus": "Magical Bonus", -"SW5E.EquipmentClothing": "Clothing", -"SW5E.EquipmentHeavy": "Heavy Armor", -"SW5E.EquipmentLight": "Light Armor", -"SW5E.EquipmentMedium": "Medium Armor", -"SW5E.EquipmentNatural": "Natural Armor", -"SW5E.EquipmentShield": "Shield", -"SW5E.EquipmentShieldProficiency": "Shields", -"SW5E.EquipmentTrinket": "Trinket", -"SW5E.Equipped": "Equipped", -"SW5E.Exhaustion": "Exhaustion", -"SW5E.Expertise": "Expertise", -"SW5E.FeatureActionRecharge": "Action Recharge", - -"SW5E.ItemTypeClass": "Class", -"SW5E.ItemTypeClassPl": "Class Levels", -"SW5E.ItemTypeConsumable": "Consumable", -"SW5E.ItemTypeConsumablePl": "Consumables", -"SW5E.ItemTypeContainer": "Container", -"SW5E.ItemTypeContainerPl": "Containers", -"SW5E.ItemTypeEquipment": "Equipment", -"SW5E.ItemTypeEquipmentPl": "Equipment", -"SW5E.ItemTypeLoot": "Loot", -"SW5E.ItemTypeLootPl": "Loot", -"SW5E.ItemTypeSpecies": "Species", -"SW5E.ItemTypeSpeciesPl": "Species", -"SW5E.ItemTypeTool": "Tool", -"SW5E.ItemTypeToolPl": "Tools", -"SW5E.ItemTypePower": "Power", -"SW5E.ItemTypePowerPl": "Powers", -"SW5E.ItemTypeWeapon": "Weapon", -"SW5E.ItemTypeWeaponPl": "Weapons", - -"SW5E.FeatureActive": "Active Abilities", -"SW5E.FeatureAdd": "Create Feature", -"SW5E.FeatureAttack": "Feature Attack", -"SW5E.FeaturePassive": "Passive Abilities", -"SW5E.FeatureRechargeOn": "Recharge On", -"SW5E.FeatureRechargeResult": "1d6 Result", -"SW5E.FeatureUsage": "Feature Usage", -"SW5E.Features": "Features", -"SW5E.Filter": "Filter", -"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.FlagsInstructions": "Configure character features and traits which fine-tune behaviors of the SW5e system.", -"SW5E.FlagsSave": "Update Special Traits", -"SW5E.FlagsTitle": "Configure Special Traits", -"SW5E.Flat": "Flat", -"SW5E.Formula": "Formula", -"SW5E.GrantedAbilities": "Granted Abilities", -"SW5E.HalfProficient": "Half Proficient", -"SW5E.Healing": "Healing", -"SW5E.HealingTemp": "Healing (Temporary)", -"SW5E.Health": "Health", -"SW5E.HealthFormula": "Health Formula", -"SW5E.HitDice": "Hit Dice", -"SW5E.HitDiceUsed": "Hit Dice Used", -"SW5E.Identified": "Identified", -"SW5E.Initiative": "Initiative", -"SW5E.Inspiration": "Inspiration", -"SW5E.Inventory": "Inventory", -"SW5E.ItemActionType": "Action Type", -"SW5E.ItemActivationCondition": "Activation Condition", -"SW5E.ItemActivationCost": "Activation Cost", -"SW5E.ItemAttackBonus": "Attack Roll Bonus", -"SW5E.ItemConsumableActivation": "Consumable Activation", -"SW5E.ItemConsumableDetails": "Consumable Details", - -"SW5E.ItemConsumableStatus": "Consumable Status", -"SW5E.ItemConsumableType": "Consumable Type", -"SW5E.ItemConsumableUsage": "Consumable Usage", -"SW5E.ItemConsumeOnUse": "Consume on Use", -"SW5E.ItemContainerCapacity": "Capacity", -"SW5E.ItemContainerCapacityItems": "Items", -"SW5E.ItemContainerCapacityType": "Capacity Type", -"SW5E.ItemContainerCapacityWeight": "Weight", -"SW5E.ItemContainerDetails": "Container Details", - -"SW5E.ItemContainerProperties": "Container Properties", -"SW5E.ItemContainerWeightless": "Weightless Contents", -"SW5E.ItemCreate": "Create Item", -"SW5E.ItemDelete": "Delete Item", -"SW5E.ItemDestroyEmpty": "Destroy on Empty", -"SW5E.ItemEdit": "Edit Item", -"SW5E.ItemEquipmentAction": "Equipment Action", -"SW5E.ItemEquipmentDetails": "Equipment Details", -"SW5E.ItemEquipmentDexMod": "Max. Dexterity Modifier", - -"SW5E.ItemEquipmentStatus": "Equipment Status", -"SW5E.ItemEquipmentStealthDisav": "Imposes Stealth Disadvantage", -"SW5E.ItemEquipmentType": "Equipment Type", -"SW5E.ItemEquipmentUsage": "Equipment Usage", - -"SW5E.ItemName": "Item Name", -"SW5E.ItemRequiredStr": "Required Strength", -"SW5E.ItemToolProficiency": "Tool Proficiency", - -"SW5E.ItemWeaponAttack": "Weapon Attack", -"SW5E.ItemWeaponDetails": "Weapon Details", -"SW5E.ItemWeaponProperties": "Weapon Properties", - -"SW5E.ItemWeaponStatus": "Weapon Status", -"SW5E.ItemWeaponType": "Weapon Type", -"SW5E.ItemWeaponUsage": "Weapon Usage", -"SW5E.JackOfAllTrades": "Jack of all Trades", -"SW5E.LairAct": "Lair Action", -"SW5E.Languages": "Languages", - "SW5E.LanguagesBasic": "Basic", - "SW5E.LanguagesBinary": "Binary", - "SW5E.LanguagesBith": "Bith", - "SW5E.LanguagesBocce": "Bocce", - "SW5E.LanguagesBothese": "Bothese", - "SW5E.LanguagesCartharese": "Catharese", - "SW5E.LanguagesCheunh": "Cheunh", - "SW5E.LanguagesDurese": "Durese", - "SW5E.LanguagesDug": "Dug", - "SW5E.LanguagesEwokese": "Ewokese", - "SW5E.LanguagesGamorrese": "Gamorrese", - "SW5E.LanguagesGeonosian": "Geonosian", - "SW5E.LanguagesHapan": "Hapan", - "SW5E.LanguagesHuttese": "Huttese", - "SW5E.LanguagesJawaese": "Jawaese", - "SW5E.LanguagesKaleesh": "Kaleesh", - "SW5E.LanguagesKaminoan": "Kaminoan", - "SW5E.LanguagesKelDor": "Kel Dor", - "SW5E.LanguagesMandoa": "Mando'a", - "SW5E.LanguagesMonCal": "Mon Cal", - "SW5E.LanguagesPakPak": "Pak Pak", - "SW5E.LanguagesRodese": "Rodese", - "SW5E.LanguagesSith": "Sith", - "SW5E.LanguagesTogruti": "Togruti", - "SW5E.LanguagesDosh": "Dosh", - "SW5E.LanguagesTwileki": "Twi'leki", - "SW5E.LanguagesTusken": "Tusken", - "SW5E.LanguagesShyriiwook": "Shyriiwook", - "SW5E.LanguagesZabraki": "Zabraki", - "SW5E.LanguagesVong": "Vong", -"SW5E.LegAct": "Legd. Actions", -"SW5E.LegRes": "Legd. Resistance", -"SW5E.Level": "Level", -"SW5E.LevelScaling": "Level Scaling", -"SW5E.LimitedUses": "Limited Uses", -"SW5E.LongRest": "Long Rest", -"SW5E.Max": "Max", -"SW5E.Modifier": "Modifier", -"SW5E.Name": "Character Name", -"SW5E.NoCharges": "No Charges", -"SW5E.None": "None", -"SW5E.Normal": "Normal", -"SW5E.NotProficient": "Not Proficient", -"SW5E.OtherFormula": "Other Formula", -"SW5E.PlaceTemplate": "Place Measured Template", -"SW5E.Polymorph": "Polymorph", -"SW5E.PolymorphAcceptSettings": "Custom Settings", -"SW5E.PolymorphKeepBio": "Keep biography", -"SW5E.PolymorphKeepClass": "Keep proficiency bonus (leaves class items in sheet)", -"SW5E.PolymorphKeepFeats": "Keep features", -"SW5E.PolymorphKeepItems": "Keep equipment", -"SW5E.PolymorphKeepMental": "Keep mental ability scores (Int, Wis, Cha)", -"SW5E.PolymorphKeepPhysical": "Keep physical ability scores (Str, Dex, Con)", -"SW5E.PolymorphKeepSaves": "Keep saving throw proficiencies", -"SW5E.PolymorphKeepSkills": "Keep skill proficiencies", -"SW5E.PolymorphKeepPowers": "Keep powers", -"SW5E.PolymorphKeepVision": "Keep vision (character and token)", -"SW5E.PolymorphMergeSaves": "Merge saving throw proficiencies (take the highest)", -"SW5E.PolymorphMergeSkills": "Merge skill proficiencies (take the highest)", -"SW5E.PolymorphPromptTitle": "Transforming Actor", -"SW5E.PolymorphRestoreTransformation": "Restore Transformation", -"SW5E.PolymorphTmpClass": "Temporary Class", -"SW5E.PolymorphTokens": "Transform all linked tokens?", -"SW5E.PolymorphWildShape": "Wild Shape", - "SW5E.Concentrated": "Concentrate", -"SW5E.Price": "Price", -"SW5E.Proficiency": "Proficiency", -"SW5E.Proficient": "Proficient", -"SW5E.Quantity": "Quantity", -"SW5E.Species": "Species", -"SW5E.Range": "Range", -"SW5E.Rarity": "Rarity", -"SW5E.Reaction": "Reaction", -"SW5E.RequiredMaterials": "Required Materials", -"SW5E.Requirements": "Requirements", -"SW5E.ResourcePrimary": "Resource 1", -"SW5E.ResourceSecondary": "Resource 2", -"SW5E.ResourceTertiary": "Resource 3", -"SW5E.RestL": "L. Rest", -"SW5E.RestS": "S. Rest", -"SW5E.Ritual": "Ritual", -"SW5E.Roll": "Roll", -"SW5E.RollExample": "e.g. +1d4", -"SW5E.RollMode": "Roll Mode", -"SW5E.RollSituationalBonus": "Situational Bonus?", -"SW5E.Save": "Save", -"SW5E.ScalingFormula": "Scaling Formula", -"SW5E.SchoolLgt": "Light", -"SW5E.SchoolUni": "Universal", -"SW5E.SchoolDrk": "Dark", -"SW5E.SchoolTec": "Tech", -"SW5E.SchoolEnh": "Enhancement", -"SW5E.SenseBS": "Blindsight", -"SW5E.SenseDV": "Darkvision", -"SW5E.SenseTR": "Truesight", -"SW5E.SenseTS": "Tremorsense", -"SW5E.Senses": "Senses", -"SW5E.ShortRest": "Short Rest", -"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.ShortRestSelect": "Select Dice to Roll", -"SW5E.Size": "Size", -"SW5E.SizeGargantuan": "Gargantuan", -"SW5E.SizeHuge": "Huge", -"SW5E.SizeLarge": "Large", -"SW5E.SizeMedium": "Medium", -"SW5E.SizeSmall": "Small", -"SW5E.SizeTiny": "Tiny", -"SW5E.SkillAcr": "Acrobatics", -"SW5E.SkillAni": "Animal Handling", -"SW5E.SkillAth": "Athletics", -"SW5E.SkillDec": "Deception", -"SW5E.SkillIns": "Insight", -"SW5E.SkillInv": "Investigation", -"SW5E.SkillItm": "Intimidation", -"SW5E.SkillLor": "Lore", -"SW5E.SkillMed": "Medicine", -"SW5E.SkillNat": "Nature", -"SW5E.SkillPer": "Persuasion", -"SW5E.SkillPrc": "Perception", -"SW5E.SkillPrf": "Performance", -"SW5E.SkillPil": "Piloting", -"SW5E.SkillSlt": "Sleight of Hand", -"SW5E.SkillSte": "Stealth", -"SW5E.SkillSur": "Survival", -"SW5E.SkillTec": "Technology", -"SW5E.Slots": "Slots", -"SW5E.Source": "Source", -"SW5E.Special": "Special", -"SW5E.SpecialTraits": "Special Traits", -"SW5E.Speed": "Speed", -"SW5E.SpeedSpecial": "Special Movement", -"SW5E.PowerAbility": "Powercasting Ability", -"SW5E.PowerAdd": "Add Power", -"SW5E.PowerAtWill": "At-Will", -"SW5E.PowerCastConsume": "Consume Power Slot?", -"SW5E.PowerCastHint": "Configure how you would like to cast the", -"SW5E.PowerCastNoSlots": "You have no available power slots to cast this power", -"SW5E.PowerCastUpcast": "Cast at Level", -"SW5E.PowercasterLevel": "Powercaster Level", -"SW5E.PowerCastingHeader": "Power Casting", -"SW5E.Powercasting": "Powercasting", -"SW5E.PowerComponents": "Power Components", -"SW5E.PowerCreate": "Create Power", -"SW5E.PowerDC": "Power DC", -"SW5E.PowerDetails": "Power Details", -"SW5E.PowerEffects": "Power Effects", -"SW5E.PowerLevel": "Power Level", -"SW5E.PowerLevel0": "At-Will", -"SW5E.PowerLevel1": "1st Level", -"SW5E.PowerLevel2": "2nd Level", -"SW5E.PowerLevel3": "3rd Level", -"SW5E.PowerLevel4": "4th Level", -"SW5E.PowerLevel5": "5th Level", -"SW5E.PowerLevel6": "6th Level", -"SW5E.PowerLevel7": "7th Level", -"SW5E.PowerLevel8": "8th Level", -"SW5E.PowerLevel9": "9th Level", -"SW5E.PowerLevelPact": "Pact Slot", -"SW5E.PowerMaterials": "Powercasting Materials", -"SW5E.PowerName": "Power Name", -"SW5E.PowerNone": "None", -"SW5E.PowerPrepAtWill": "At-Will", -"SW5E.PowerPrepInnate": "Innate Powercasting", -"SW5E.PowerPrepPrepared": "Prepared", -"SW5E.PowerPrepAlways": "Always Prepared", -"SW5E.PowerConcentrationMode": "Power Concentration Mode", -"SW5E.PowerConcentrating": "Concentrating", -"SW5E.PowerProgArt": "Artificer", -"SW5E.PowerProgFull": "Full Caster", -"SW5E.PowerProgOverride": "Override slots", -"SW5E.PowerProgression": "Power Progression", -"SW5E.PowerSchool": "Power School", -"SW5E.PowerTarget": "Power Target", -"SW5E.PowerUnprepared": "Unprepared", -"SW5E.PowerUsage": "Power Usage", -"SW5E.Powerbook": "Powerbook", -"SW5E.SpeciesTraits": "Species Traits", -"SW5E.SubclassName": "Subclass Name", -"SW5E.Supply": "Supply", -"SW5E.Target": "Target", -"SW5E.TargetAlly": "Ally", -"SW5E.TargetCone": "Cone", -"SW5E.TargetCreature": "Creature", -"SW5E.TargetCube": "Cube", -"SW5E.TargetCylinder": "Cylinder", -"SW5E.TargetEnemy": "Enemy", -"SW5E.TargetLine": "Line", -"SW5E.TargetObject": "Object", -"SW5E.TargetRadius": "Radius", -"SW5E.TargetSelf": "Self", -"SW5E.TargetSpace": "Space", -"SW5E.TargetSphere": "Sphere", -"SW5E.TargetSquare": "Square", -"SW5E.TargetWall": "Wall", - "SW5E.TargetDroid": "Droid", -"SW5E.Temp": "Temp", -"SW5E.TimeDay": "Days", -"SW5E.TimeHour": "Hours", -"SW5E.TimeInst": "Instantaneous", -"SW5E.TimeMinute": "Minutes", -"SW5E.TimeMonth": "Months", -"SW5E.TimePerm": "Permanent", -"SW5E.TimeRound": "Rounds", -"SW5E.TimeTurn": "Turns", -"SW5E.TimeYear": "Years", - "SW5E.ToolArmormech": "Armormech's Tools", - "SW5E.ToolArmstech": "Armstech's Tools", - "SW5E.ToolArtificer": "Artificer's Tools", - "SW5E.ToolArtist": "Artist's Tools", - "SW5E.ToolAstrotech": "Astrotech's Tools", - "SW5E.ToolBiotech": "Biotech's Tools", - "SW5E.ToolConstructor": "Constructor's Tools", - "SW5E.ToolCybertech": "Cybertech's Tools", - "SW5E.ToolJeweler": "Jeweler's Tools", - "SW5E.ToolSurveyor": "Surveyor's Tools", - "SW5E.ToolSynthweaver": "Synthweavers's Tools", - "SW5E.ToolTinker": "Tinker's Tools", - "SW5E.ToolAntitoxkit": "Antitoxkit", - "SW5E.ToolArchaeologistKit": "Archaeologist Kit", - "SW5E.ToolAudiotechKit": "Audiotech's Kit", - "SW5E.ToolBioanalysisKit": "Bioanalysis Kit", - "SW5E.ToolBrewerKit": "Brewer's Kit", - "SW5E.ToolChefKit": "Chef's Kit", - "SW5E.ToolDemolitionKit": "Demolition Kit", - "SW5E.ToolDisguiseKit": "Disguise Kit", - "SW5E.ToolForgeryKit": "Forgery Kit", - "SW5E.ToolMechanicKit": "Mechanic's Kit", - "SW5E.ToolMunitionsKit": "Munitions Kit", - "SW5E.ToolPoisonKit": "Poisoner's Kit", - "SW5E.ToolScavengingKit": "Scavenging Kit", - "SW5E.ToolSecurityKit": "Security Kit", - "SW5E.ToolSlicerKit": "Slicer's Kit", - "SW5E.ToolSpiceKit": "Spicer's Kit", -"SW5E.ToolGamingSet": "Gaming Set", -"SW5E.ToolMusicalInstrument": "Musical Instrument", -"SW5E.ToolVehicle": "Vehicle (Land or Water)", -"SW5E.TraitArmorProf": "Armor Proficiencies", -"SW5E.TraitSelectorSpecial": "Special (Split with Semi-Colon)", -"SW5E.TraitToolProf": "Tool Proficiencies", -"SW5E.TraitWeaponProf": "Weapon Proficiencies", -"SW5E.Type": "Type", -"SW5E.Unequipped": "Not Equipped", -"SW5E.Unlimited": "Unlimited", -"SW5E.Usage": "Usage", -"SW5E.Use": "Use", -"SW5E.Uses": "Uses", -"SW5E.Versatile": "Versatile", -"SW5E.VersatileDamage": "Versatile Damage", -"SW5E.VsDC": "vs DC.", -"SW5E.WeaponAmmo": "Ammunition", -"SW5E.WeaponImprov": "Improvised", -"SW5E.WeaponMartialVW": "Martial Vibroweapon", -"SW5E.WeaponMartialProficiency": "Martial Weapons", -"SW5E.WeaponMartialB": "Martial Blaster", -"SW5E.WeaponMartialLW": "Martial Lightweapon", -"SW5E.WeaponNatural": "Natural", - "SW5E.WeaponPropertiesAmm": "Ammunition", - "SW5E.WeaponPropertiesBur": "Burst", - "SW5E.WeaponPropertiesDef": "Defensive", - "SW5E.WeaponPropertiesDex": "Dexterity Rqmt.", - "SW5E.WeaponPropertiesDrm": "Disarming", - "SW5E.WeaponPropertiesDgd": "Disguised", - "SW5E.WeaponPropertiesDis": "Disintegrate", - "SW5E.WeaponPropertiesDpt": "Disruptive", - "SW5E.WeaponPropertiesDou": "Double", - "SW5E.WeaponPropertiesFin": "Finesse", - "SW5E.WeaponPropertiesFix": "Fixed", - "SW5E.WeaponPropertiesFoc": "Focus", - "SW5E.WeaponPropertiesHvy": "Heavy", - "SW5E.WeaponPropertiesHid": "Hidden", - "SW5E.WeaponPropertiesKen": "Keen", - "SW5E.WeaponPropertiesLgt": "Light", - "SW5E.WeaponPropertiesLum": "Luminous", - "SW5E.WeaponPropertiesPic": "Piercing", - "SW5E.WeaponPropertiesRan": "Range", - "SW5E.WeaponPropertiesRap": "Rapid", - "SW5E.WeaponPropertiesRch": "Reach", - "SW5E.WeaponPropertiesRel": "Reload", - "SW5E.WeaponPropertiesRet": "Returning", - "SW5E.WeaponPropertiesShk": "Shocking", - "SW5E.WeaponPropertiesSpc": "Special", - "SW5E.WeaponPropertiesStr": "Strength Rqmt.", - "SW5E.WeaponPropertiesThr": "Thrown", - "SW5E.WeaponPropertiesTwo": "Two-Handed", - "SW5E.WeaponPropertiesVer": "Versatile", - "SW5E.WeaponPropertiesVic": "Vicious", -"SW5E.WeaponSimpleVW": "Simple Vibroweapon", -"SW5E.WeaponSimpleProficiency": "Simple Weapons", -"SW5E.WeaponSimpleB": "Simple Blaster", -"SW5E.WeaponSimpleLW": "Simple Lightweapon", -"SW5E.Weight": "Weight", -"SW5E.available": "available", -"SW5E.description": "A comprehensive game system for running games of Dungeons & Dragons 5th Edition in the Foundry VTT environment.", -"SW5E.of": "of", -"SW5E.power": "power", -"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.", -"SETTINGS.5eAllowPolymorphingN": "Allow Polymorphing", -"SETTINGS.5eAutoCollapseCardL": "Automatically collapse Item Card descriptions in the Chat Log", -"SETTINGS.5eAutoCollapseCardN": "Collapse Item Cards in Chat", -"SETTINGS.5eAutoPowerTemplateL": "When a power is cast, defaults to begin the process to create the corresponding Measured Template if any (requires TRUSTED or higher player role)", -"SETTINGS.5eAutoPowerTemplateN": "Always place Power Template", -"SETTINGS.5eCurWtL": "Carried currency affects character encumbrance following the rules on PHB pg. 143.", -"SETTINGS.5eCurWtN": "Apply Currency Weight", -"SETTINGS.5eDiagDMG": "Dungeon Master's Guide (5/10/5)", -"SETTINGS.5eDiagL": "Configure which diagonal movement rule should be used for games within this system.", -"SETTINGS.5eDiagN": "Diagonal Movement Rule", -"SETTINGS.5eDiagPHB": "Player's Handbook (5/5/5)", -"SETTINGS.5eInitTBL": "Append the raw Dexterity ability score to break ties in Initiative.", -"SETTINGS.5eInitTBN": "Initiative Dexterity Tiebreaker", -"SETTINGS.5eNoExpL": "Remove experience bars from character sheets.", -"SETTINGS.5eNoExpN": "Disable Experience Tracking" -} diff --git a/module/actor/entity.js b/module/actor/entity.js index 7778b824..ca4632a0 100644 --- a/module/actor/entity.js +++ b/module/actor/entity.js @@ -1,14 +1,14 @@ -import { Dice5e } from "../dice.js"; -import { ShortRestDialog } from "../apps/short-rest.js"; -import { PowerCastDialog } from "../apps/power-cast-dialog.js"; -import { AbilityTemplate } from "../pixi/ability-template.js"; +import { d20Roll, damageRoll } from "../dice.js"; +import ShortRestDialog from "../apps/short-rest.js"; +import LongRestDialog from "../apps/long-rest.js"; +import AbilityUseDialog from "../apps/ability-use-dialog.js"; +import AbilityTemplate from "../pixi/ability-template.js"; import {SW5E} from '../config.js'; - /** - * Extend the base Actor class to implement additional logic specialized for D&D5e. + * Extend the base Actor class to implement additional logic specialized for SW5e. */ -export class Actor5e extends Actor { +export default class Actor5e extends Actor { /** * Is this Actor currently polymorphed into some other creature? @@ -21,74 +21,258 @@ export class Actor5e extends Actor { /* -------------------------------------------- */ /** - * Augment the basic actor data with additional dynamic data. + * @override + * TODO: This becomes unnecessary after 0.7.x is released + */ + initialize() { + try { + this.prepareData(); + } catch(err) { + console.error(`Failed to initialize data for ${this.constructor.name} ${this.id}:`); + console.error(err); + } + } + + /* -------------------------------------------- */ + + /** + * @override + * TODO: This becomes unnecessary after 0.7.x is released */ prepareData() { - super.prepareData(); + const is07x = !isNewerVersion("0.7.1", game.data.version); + if ( is07x ) this.data = duplicate(this._data); + if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN; + if ( !this.data.name ) this.data.name = "New " + this.entity; + this.prepareBaseData(); + this.prepareEmbeddedEntities(); + if ( is07x ) this.applyActiveEffects(); + this.prepareDerivedData(); + } - // Get the Actor's data object + /* -------------------------------------------- */ + + /** + * @override + * TODO: This becomes unnecessary after 0.7.x is released + */ + applyActiveEffects() { + if (!isNewerVersion("0.7.1", game.data.version)) return super.applyActiveEffects(); + } + + /* -------------------------------------------- */ + + /** @override */ + prepareBaseData() { + switch ( this.data.type ) { + case "character": + return this._prepareCharacterData(this.data); + case "npc": + return this._prepareNPCData(this.data); + case "vehicle": + return this._prepareVehicleData(this.data); + } + } + + /* -------------------------------------------- */ + + /** @override */ + prepareDerivedData() { const actorData = this.data; const data = actorData.data; const flags = actorData.flags.sw5e || {}; + const bonuses = getProperty(data, "bonuses.abilities") || {}; - // Prepare Character data - if ( actorData.type === "character" ) this._prepareCharacterData(actorData); - else if ( actorData.type === "npc" ) this._prepareNPCData(actorData); + // Retrieve data for polymorphed actors + let originalSaves = null; + let originalSkills = null; + if (this.isPolymorphed) { + const transformOptions = this.getFlag('sw5e', 'transformOptions'); + const original = game.actors?.get(this.getFlag('sw5e', 'originalActor')); + if (original) { + if (transformOptions.mergeSaves) { + originalSaves = original.data.data.abilities; + } + if (transformOptions.mergeSkills) { + originalSkills = original.data.data.skills; + } + } + } // Ability modifiers and saves - // Character All Ability Check" and All Ability Save bonuses added when rolled since not a fixed value. - const saveBonus = parseInt(getProperty(data, "bonuses.abilities.save")) || 0; - for (let abl of Object.values(data.abilities)) { + const dcBonus = Number.isNumeric(data.bonuses?.power?.dc) ? parseInt(data.bonuses.power.dc) : 0; + const saveBonus = Number.isNumeric(bonuses.save) ? parseInt(bonuses.save) : 0; + const checkBonus = Number.isNumeric(bonuses.check) ? parseInt(bonuses.check) : 0; + for (let [id, abl] of Object.entries(data.abilities)) { abl.mod = Math.floor((abl.value - 10) / 2); abl.prof = (abl.proficient || 0) * data.attributes.prof; - abl.save = abl.mod + abl.prof + saveBonus; - } + abl.saveBonus = saveBonus; + abl.checkBonus = checkBonus; + abl.save = abl.mod + abl.prof + abl.saveBonus; + abl.dc = 8 + abl.mod + data.attributes.prof + dcBonus; - // Skill modifiers - const feats = SW5E.characterFlags; - const athlete = flags.remarkableAthlete; - const joat = flags.jackOfAllTrades; - const observant = flags.observantFeat; - let round = Math.floor; - for (let [id, skl] of Object.entries(data.skills)) { - skl.value = parseFloat(skl.value || 0); - skl.bonus = parseInt(skl.bonus || 0); - - // Apply Remarkable Athlete or Jack of all Trades - let multi = skl.value; - if ( athlete && (skl.value === 0) && feats.remarkableAthlete.abilities.includes(skl.ability) ) { - multi = 0.5; - round = Math.ceil; + // If we merged saves when transforming, take the highest bonus here. + if (originalSaves && abl.proficient) { + abl.save = Math.max(abl.save, originalSaves[id].save); } - if ( joat && (skl.value === 0 ) ) multi = 0.5; - - // Compute modifier - skl.mod = data.abilities[skl.ability].mod + skl.bonus + round(multi * data.attributes.prof); - - // Compute passive bonus - const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0; - skl.passive = 10 + skl.mod + passive; } + this._prepareSkills(actorData, bonuses, checkBonus, originalSkills); // Determine Initiative Modifier const init = data.attributes.init; + const athlete = flags.remarkableAthlete; + const joat = flags.jackOfAllTrades; init.mod = data.abilities.dex.mod; if ( joat ) init.prof = Math.floor(0.5 * data.attributes.prof); else if ( athlete ) init.prof = Math.ceil(0.5 * data.attributes.prof); else init.prof = 0; - init.bonus = init.value + (flags.initiativeAlert ? 5 : 0); + init.bonus = Number(init.value + (flags.initiativeAlert ? 5 : 0)); init.total = init.mod + init.prof + init.bonus; // Prepare power-casting data - data.attributes.powerdc = this.getPowerDC(data.attributes.powercasting); - // TODO: Only do this IF we have already processed item types (see Entity#initialize) - if ( this.items ) { - this._computePowercastingProgression(actorData); - } + this._computePowercastingDC(this.data); + this._computePowercastingProgression(this.data); } /* -------------------------------------------- */ + /** + * Return the amount of experience required to gain a certain character level. + * @param level {Number} The desired level + * @return {Number} The XP required + */ + getLevelExp(level) { + const levels = CONFIG.SW5E.CHARACTER_EXP_LEVELS; + return levels[Math.min(level, levels.length - 1)]; + } + + /* -------------------------------------------- */ + + /** + * Return the amount of experience granted by killing a creature of a certain CR. + * @param cr {Number} The creature's challenge rating + * @return {Number} The amount of experience granted per kill + */ + getCRExp(cr) { + if (cr < 1.0) return Math.max(200 * cr, 10); + return CONFIG.SW5E.CR_EXP_LEVELS[cr]; + } + + /* -------------------------------------------- */ + + /** @override */ + getRollData() { + const data = super.getRollData(); + data.classes = this.data.items.reduce((obj, i) => { + if ( i.type === "class" ) { + obj[i.name.slugify({strict: true})] = i.data; + } + return obj; + }, {}); + data.prof = this.data.data.attributes.prof; + return data; + } + + /* -------------------------------------------- */ + + /** + * Return the features which a character is awarded for each class level + * @param cls {Object} Data object for class, equivalent to Item5e.data or raw compendium entry + * @return {Promise} Array of Item5e entities + */ + static async getClassFeatures(cls) { + const level = cls.data.levels; + const className = cls.name.toLowerCase(); + + // Get the configuration of features which may be added + const clsConfig = CONFIG.SW5E.classFeatures[className]; + if (!clsConfig) return []; + let featureIDs = clsConfig["features"][level] || []; + const subclassName = cls.data.subclass.toLowerCase().slugify(); + + // Identify subclass features + if ( subclassName !== "" ) { + const subclassConfig = clsConfig["subclasses"][subclassName]; + if ( subclassConfig !== undefined ) { + const subclassFeatureIDs = subclassConfig["features"][level]; + if ( subclassFeatureIDs ) { + featureIDs = featureIDs.concat(subclassFeatureIDs); + } + } + else console.warn("Invalid subclass: " + subclassName); + } + + // Load item data for all identified features + const features = await Promise.all(featureIDs.map(id => fromUuid(id))); + + // Class powers should always be prepared + for ( const feature of features ) { + if ( feature.type === "power" ) { + const preparation = feature.data.data.preparation; + preparation.mode = "always"; + preparation.prepared = true; + } + } + return features; + } + + /* -------------------------------------------- */ + + /** @override */ + async updateEmbeddedEntity(embeddedName, data, options={}) { + const createItems = embeddedName === "OwnedItem" ? await this._createClassFeatures(data) : []; + let updated = await super.updateEmbeddedEntity(embeddedName, data, options); + if ( createItems.length ) await this.createEmbeddedEntity("OwnedItem", createItems); + return updated; + } + + /* -------------------------------------------- */ + + /** + * Create additional class features in the Actor when a class item is updated. + * @private + */ + async _createClassFeatures(updated) { + let toCreate = []; + for (let u of updated instanceof Array ? updated : [updated]) { + const item = this.items.get(u._id); + if (!item || (item.data.type !== "class")) continue; + const classData = duplicate(item.data); + let changed = false; + + // Get and create features for an increased class level + const newLevels = getProperty(u, "data.levels"); + if (newLevels && (newLevels > item.data.data.levels)) { + classData.data.levels = newLevels; + changed = true; + } + + // Get features for a newly changed subclass + const newSubclass = getProperty(u, "data.subclass"); + if (newSubclass && (newSubclass !== item.data.data.subclass)) { + classData.data.subclass = newSubclass; + changed = true; + } + + // Get the new features + if ( changed ) { + const features = await Actor5e.getClassFeatures(classData); + if ( features.length ) toCreate.push(...features); + } + } + + // De-dupe created items with ones that already exist (by name) + if ( toCreate.length ) { + const existing = new Set(this.items.map(i => i.name)); + toCreate = toCreate.filter(c => !existing.has(c.name)); + } + return toCreate + } + + /* -------------------------------------------- */ + /* Data Preparation Helpers */ + /* -------------------------------------------- */ + /** * Prepare Character type specific data */ @@ -117,6 +301,9 @@ export class Actor5e extends Actor { const required = xp.max - prior; const pct = Math.round((xp.value - prior) * 100 / required); xp.pct = Math.clamped(pct, 0, 100); + + // Inventory encumbrance + data.attributes.encumbrance = this._computeEncumbrance(actorData); } /* -------------------------------------------- */ @@ -134,18 +321,104 @@ export class Actor5e extends Actor { data.attributes.prof = Math.floor((Math.max(data.details.cr, 1) + 7) / 4); // Powercaster Level - if ( data.attributes.powercasting && !data.details.powerLevel ) { + if ( data.attributes.powercasting && !Number.isNumeric(data.details.powerLevel) ) { data.details.powerLevel = Math.max(data.details.cr, 1); } } /* -------------------------------------------- */ + /** + * Prepare vehicle type-specific data + * @param actorData + * @private + */ + _prepareVehicleData(actorData) {} + + /* -------------------------------------------- */ + + /** + * Prepare skill checks. + * @param actorData + * @param bonuses Global bonus data. + * @param checkBonus Ability check specific bonus. + * @param originalSkills A transformed actor's original actor's skills. + * @private + */ + _prepareSkills(actorData, bonuses, checkBonus, originalSkills) { + if (actorData.type === 'vehicle') return; + + const data = actorData.data; + const flags = actorData.flags.sw5e || {}; + + // Skill modifiers + const feats = SW5E.characterFlags; + const athlete = flags.remarkableAthlete; + const joat = flags.jackOfAllTrades; + const observant = flags.observantFeat; + const skillBonus = Number.isNumeric(bonuses.skill) ? parseInt(bonuses.skill) : 0; + let round = Math.floor; + for (let [id, skl] of Object.entries(data.skills)) { + skl.value = parseFloat(skl.value || 0); + + // Apply Remarkable Athlete or Jack of all Trades + let multi = skl.value; + if ( athlete && (skl.value === 0) && feats.remarkableAthlete.abilities.includes(skl.ability) ) { + multi = 0.5; + round = Math.ceil; + } + if ( joat && (skl.value === 0 ) ) multi = 0.5; + + // Compute modifier + skl.bonus = checkBonus + skillBonus; + skl.mod = data.abilities[skl.ability].mod; + skl.prof = round(multi * data.attributes.prof); + skl.total = skl.mod + skl.prof + skl.bonus; + + // If we merged skills when transforming, take the highest bonus here. + if (originalSkills && skl.value > 0.5) { + skl.total = Math.max(skl.total, originalSkills[id].total); + } + + // Compute passive bonus + const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0; + skl.passive = 10 + skl.total + passive; + } + } + + /* -------------------------------------------- */ + + /** + * Compute the powercasting DC for all item abilities which use power DC scaling + * @param {object} actorData The actor data being prepared + * @private + */ + _computePowercastingDC(actorData) { + + // Compute the powercasting DC + const data = actorData.data; + data.attributes.powerdc = data.attributes.powercasting ? data.abilities[data.attributes.powercasting].dc : 10; + + // Apply powercasting DC to any power items which use it + for ( let i of this.items ) { + const save = i.data.data.save; + if ( save?.ability ) { + if ( save.scaling === "power" ) save.dc = data.attributes.powerdc; + else if ( save.scaling !== "flat" ) save.dc = data.abilities[save.scaling]?.dc ?? 10; + const ability = CONFIG.SW5E.abilities[save.ability]; + i.labels.save = game.i18n.format("SW5E.SaveDC", {dc: save.dc || "", ability}); + } + } + } + + /* -------------------------------------------- */ + /** * Prepare data related to the power-casting capabilities of the Actor * @private */ _computePowercastingProgression (actorData) { + if (actorData.type === 'vehicle') return; const powers = actorData.data.powers; const isNPC = actorData.type === 'npc'; @@ -204,69 +477,67 @@ export class Actor5e extends Actor { lvl.value = Math.min(parseInt(lvl.value), lvl.max); } + // Determine the Actor's pact magic level (if any) + let pl = Math.clamped(progression.pact, 0, 20); + powers.pact = powers.pact || {}; + if ( (pl === 0) && isNPC && Number.isNumeric(powers.pact.override) ) pl = actorData.data.details.powerLevel; + // Determine the number of Warlock pact slots per level - const pl = Math.clamped(progression.pact, 0, 20); if ( pl > 0) { - powers.pact = powers.pact || {}; powers.pact.level = Math.ceil(Math.min(10, pl) / 2); if ( Number.isNumeric(powers.pact.override) ) powers.pact.max = Math.max(parseInt(powers.pact.override), 1); else powers.pact.max = Math.max(1, Math.min(pl, 2), Math.min(pl - 8, 3), Math.min(pl - 13, 4)); powers.pact.value = Math.min(powers.pact.value, powers.pact.max); + } else { + powers.pact.level = 0; + powers.pact.max = 0; } } /* -------------------------------------------- */ /** - * Return the amount of experience required to gain a certain character level. - * @param level {Number} The desired level - * @return {Number} The XP required + * 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 {Object} actorData The data object for the Actor being rendered + * @returns {{max: number, value: number, pct: number}} An object describing the character's encumbrance level + * @private */ - getLevelExp(level) { - const levels = CONFIG.SW5E.CHARACTER_EXP_LEVELS; - return levels[Math.min(level, levels.length - 1)]; - } + _computeEncumbrance(actorData) { - /* -------------------------------------------- */ + // Get the total weight from items + const physicalItems = ["weapon", "equipment", "consumable", "tool", "backpack", "loot"]; + let weight = actorData.items.reduce((weight, i) => { + if ( !physicalItems.includes(i.type) ) return weight; + const q = i.data.quantity || 0; + const w = i.data.weight || 0; + return weight + Math.round(q * w * 10) / 10; + }, 0); - /** - * Return the amount of experience granted by killing a creature of a certain CR. - * @param cr {Number} The creature's challenge rating - * @return {Number} The amount of experience granted per kill - */ - getCRExp(cr) { - if (cr < 1.0) return Math.max(200 * cr, 10); - return CONFIG.SW5E.CR_EXP_LEVELS[cr]; - } + // [Optional] add Currency Weight + if ( game.settings.get("sw5e", "currencyWeight") ) { + const currency = actorData.data.currency; + const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0); + weight += Math.round((numCoins * 10) / CONFIG.SW5E.encumbrance.currencyPerWeight) / 10; + } - /* -------------------------------------------- */ + // Determine the encumbrance size class + let mod = { + tiny: 0.5, + sm: 1, + med: 1, + lg: 2, + huge: 4, + grg: 8 + }[actorData.data.traits.size] || 1; + if ( this.getFlag("sw5e", "powerfulBuild") ) mod = Math.min(mod * 2, 8); - /** - * Return the power DC for this actor using a certain ability score - * @param {string} ability The ability score, i.e. "str" - * @return {number} The power DC - */ - getPowerDC(ability) { - const actorData = this.data.data; - const bonus = parseInt(getProperty(actorData, "bonuses.power.dc")) || 0; - ability = actorData.abilities[ability]; - const prof = actorData.attributes.prof; - return 8 + (ability ? ability.mod : 0) + prof + bonus; - } - - /* -------------------------------------------- */ - - /** @override */ - getRollData() { - const data = super.getRollData(); - data.classes = this.data.items.reduce((obj, i) => { - if ( i.type === "class" ) { - obj[i.name.slugify({strict: true})] = i.data; - } - return obj; - }, {}); - data.prof = this.data.data.attributes.prof; - return data; + // Compute Encumbrance percentage + const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod; + const pct = Math.clamped((weight* 100) / max, 0, 100); + return { value: weight, max, pct, encumbered: pct > (2/3) }; } /* -------------------------------------------- */ @@ -293,8 +564,11 @@ export class Actor5e extends Actor { /** @override */ async update(data, options={}) { + // TODO: 0.7.1 compatibility - remove when stable + if ( !data.hasOwnProperty("data") ) data = expandObject(data); + // Apply changes in Actor size to Token width/height - const newSize = data["data.traits.size"]; + const newSize = getProperty(data, "data.traits.size"); if ( newSize && (newSize !== getProperty(this.data, "data.traits.size")) ) { let size = CONFIG.SW5E.tokenSizes[newSize]; if ( this.isToken ) this.token.update({height: size, width: size}); @@ -303,6 +577,14 @@ export class Actor5e extends Actor { data["token.width"] = size; } } + + // Reset death save counters + if ( (this.data.data.attributes.hp.value <= 0) && (getProperty(data, "data.attributes.hp.value") > 0) ) { + setProperty(data, "data.attributes.death.success", 0); + setProperty(data, "data.attributes.death.failure", 0); + } + + // Perform the update return super.update(data, options); } @@ -323,30 +605,49 @@ export class Actor5e extends Actor { return super.createOwnedItem(itemData, options); } + + /* -------------------------------------------- */ + /* Gameplay Mechanics */ /* -------------------------------------------- */ /** @override */ async modifyTokenAttribute(attribute, value, isDelta, isBar) { - if ( attribute !== "attributes.hp" ) return super.modifyTokenAttribute(attribute, value, isDelta, isBar); - - // Get current and delta HP - const hp = getProperty(this.data.data, attribute); - const tmp = parseInt(hp.temp) || 0; - const current = hp.value + tmp; - const max = hp.max + (parseInt(hp.tempmax) || 0); - const delta = isDelta ? value : value - current; - - // For negative changes, deduct from temp HP - let dtmp = delta < 0 ? Math.max(-1*tmp, delta) : 0; - let dhp = delta - dtmp; - return this.update({ - "data.attributes.hp.temp": tmp + dtmp, - "data.attributes.hp.value": Math.clamped(hp.value + dhp, 0, max) - }); + if ( attribute === "attributes.hp" ) { + const hp = getProperty(this.data.data, attribute); + const delta = isDelta ? (-1 * value) : (hp.value + hp.temp) - value; + return this.applyDamage(delta); + } + return super.modifyTokenAttribute(attribute, value, isDelta, isBar); } /* -------------------------------------------- */ - /* Rolls */ + + /** + * Apply a certain amount of damage or healing to the health pool for Actor + * @param {number} amount An amount of damage (positive) or healing (negative) to sustain + * @param {number} multiplier A multiplier which allows for resistance, vulnerability, or healing + * @return {Promise} A Promise which resolves once the damage has been applied + */ + async applyDamage(amount=0, multiplier=1) { + amount = Math.floor(parseInt(amount) * multiplier); + const hp = this.data.data.attributes.hp; + + // Deduct damage from temp HP first + const tmp = parseInt(hp.temp) || 0; + const dt = amount > 0 ? Math.min(tmp, amount) : 0; + + // Remaining goes to health + const tmpMax = parseInt(hp.tempmax) || 0; + const dh = Math.clamped(hp.value - (amount - dt), 0, hp.max + tmpMax); + + // Update the Actor + const updates = { + "data.attributes.hp.temp": tmp - dt, + "data.attributes.hp.value": dh + }; + return this.update(updates); + } + /* -------------------------------------------- */ /** @@ -356,45 +657,60 @@ export class Actor5e extends Actor { */ async usePower(item, {configureDialog=true}={}) { if ( item.data.type !== "power" ) throw new Error("Wrong Item type"); + const itemData = item.data.data; - // Determine if the power uses slots - let lvl = item.data.data.level; - const usesSlots = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(item.data.data.preparation.mode); - if ( !usesSlots ) return item.roll(); - - // Configure the casting level and whether to consume a power slot - let consume = `power${lvl}`; + // Configure powercasting data + let lvl = itemData.level; + const usesSlots = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode); + const limitedUses = !!itemData.uses.per; + let consumeSlot = `power${lvl}`; + let consumeUse = false; let placeTemplate = false; // Configure power slot consumption and measured template placement from the form - if ( configureDialog ) { - const powerFormData = await PowerCastDialog.create(this, item); - const isPact = powerFormData.get('level') === 'pact'; - const lvl = isPact ? this.data.data.powers.pact.level : parseInt(powerFormData.get("level")); - if (Boolean(powerFormData.get("consume"))) { - consume = isPact ? 'pact' : `power${lvl}`; - } else { - consume = false; - } - placeTemplate = Boolean(powerFormData.get("placeTemplate")); + if ( configureDialog && (usesSlots || item.hasAreaTarget || limitedUses) ) { + const usage = await AbilityUseDialog.create(item); + if ( usage === null ) return; - // Create a temporary owned item to approximate the power at a higher level + // Determine consumption preferences + consumeSlot = Boolean(usage.get("consumeSlot")); + consumeUse = Boolean(usage.get("consumeUse")); + placeTemplate = Boolean(usage.get("placeTemplate")); + + // Determine the cast power level + const isPact = usage.get('level') === 'pact'; + const lvl = isPact ? this.data.data.powers.pact.level : parseInt(usage.get("level")); if ( lvl !== item.data.data.level ) { - item = item.constructor.createOwned(mergeObject(item.data, {"data.level": lvl}, {inplace: false}), this); + const upcastData = mergeObject(item.data, {"data.level": lvl}, {inplace: false}); + item = item.constructor.createOwned(upcastData, this); } + + // Denote the power slot being consumed + if ( consumeSlot ) consumeSlot = isPact ? "pact" : `power${lvl}`; } // Update Actor data - if ( consume && (lvl > 0) ) { + if ( usesSlots && consumeSlot && (lvl > 0) ) { + const slots = parseInt(this.data.data.powers[consumeSlot]?.value); + if ( slots === 0 || Number.isNaN(slots) ) { + return ui.notifications.error(game.i18n.localize("SW5E.PowerCastNoSlots")); + } await this.update({ - [`data.powers.${consume}.value`]: Math.max(parseInt(this.data.data.powers[consume].value) - 1, 0) + [`data.powers.${consumeSlot}.value`]: Math.max(slots - 1, 0) }); } + // Update Item data + if ( limitedUses && consumeUse ) { + const uses = parseInt(itemData.uses.value || 0); + if ( uses <= 0 ) ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: item.name})); + await item.update({"data.uses.value": Math.max(parseInt(item.data.data.uses.value || 0) - 1, 0)}) + } + // Initiate ability template placement workflow if selected - if (item.hasAreaTarget && placeTemplate) { + if ( placeTemplate && item.hasAreaTarget ) { const template = AbilityTemplate.fromItem(item); - if ( template ) template.drawPreview(event); + if ( template ) template.drawPreview(); if ( this.sheet.rendered ) this.sheet.minimize(); } @@ -409,28 +725,47 @@ export class Actor5e extends Actor { * Prompt the user for input regarding Advantage/Disadvantage and any Situational Bonus * @param {string} skillId The skill id (e.g. "ins") * @param {Object} options Options which configure how the skill check is rolled - * @return {Promise.} A Promise which resolves to the created Roll instance + * @return {Promise} A Promise which resolves to the created Roll instance */ rollSkill(skillId, options={}) { const skl = this.data.data.skills[skillId]; - const parts = ["@mod"]; - const data = {mod: skl.mod}; + const bonuses = getProperty(this.data.data, "bonuses.abilities") || {}; - // Include a global actor skill bonus - const actorBonus = getProperty(this.data.data.bonuses, "abilities.skill"); - if ( !!actorBonus ) { - parts.push("@skillBonus"); - data.skillBonus = actorBonus; + // Compose roll parts and data + const parts = ["@mod"]; + const data = {mod: skl.mod + skl.prof}; + + // Ability test bonus + if ( bonuses.check ) { + data["checkBonus"] = bonuses.check; + parts.push("@checkBonus"); } + // Skill check bonus + if ( bonuses.skill ) { + data["skillBonus"] = bonuses.skill; + parts.push("@skillBonus"); + } + + // Add provided extra roll parts now because they will get clobbered by mergeObject below + if (options.parts?.length > 0) { + parts.push(...options.parts); + } + + // Reliable Talent applies to any skill check we have full or better proficiency in + const reliableTalent = (skl.value >= 1 && this.getFlag("sw5e", "reliableTalent")); + // Roll and return - return Dice5e.d20Roll(mergeObject(options, { + const rollData = mergeObject(options, { parts: parts, data: data, - title: `${CONFIG.SW5E.skills[skillId]} Skill Check`, - speaker: ChatMessage.getSpeaker({actor: this}), - halflingLucky: this.getFlag("sw5e", "halflingLucky") - })); + title: game.i18n.format("SW5E.SkillPromptTitle", {skill: CONFIG.SW5E.skills[skillId]}), + halflingLucky: this.getFlag("sw5e", "halflingLucky"), + reliableTalent: reliableTalent, + messageData: {"flags.sw5e.roll": {type: "skill", skillId }} + }); + rollData.speaker = options.speaker || ChatMessage.getSpeaker({actor: this}); + return d20Roll(rollData); } /* -------------------------------------------- */ @@ -444,15 +779,15 @@ export class Actor5e extends Actor { rollAbility(abilityId, options={}) { const label = CONFIG.SW5E.abilities[abilityId]; new Dialog({ - title: `${label} Ability Check`, - content: `

What type of ${label} check?

`, + title: game.i18n.format("SW5E.AbilityPromptTitle", {ability: label}), + content: `

${game.i18n.format("SW5E.AbilityPromptText", {ability: label})}

`, buttons: { test: { - label: "Ability Test", + label: game.i18n.localize("SW5E.ActionAbil"), callback: () => this.rollAbilityTest(abilityId, options) }, save: { - label: "Saving Throw", + label: game.i18n.localize("SW5E.ActionSave"), callback: () => this.rollAbilitySave(abilityId, options) } } @@ -471,11 +806,13 @@ export class Actor5e extends Actor { rollAbilityTest(abilityId, options={}) { const label = CONFIG.SW5E.abilities[abilityId]; const abl = this.data.data.abilities[abilityId]; + + // Construct parts const parts = ["@mod"]; const data = {mod: abl.mod}; - const feats = this.data.flags.sw5e || {}; // Add feat-related proficiency bonuses + const feats = this.data.flags.sw5e || {}; if ( feats.remarkableAthlete && SW5E.characterFlags.remarkableAthlete.abilities.includes(abilityId) ) { parts.push("@proficiency"); data.proficiency = Math.ceil(0.5 * this.data.data.attributes.prof); @@ -486,20 +823,27 @@ export class Actor5e extends Actor { } // Add global actor bonus - let actorBonus = getProperty(this.data.data.bonuses, "abilities.check"); - if ( !!actorBonus ) { + const bonuses = getProperty(this.data.data, "bonuses.abilities") || {}; + if ( bonuses.check ) { parts.push("@checkBonus"); - data.checkBonus = actorBonus; + data.checkBonus = bonuses.check; + } + + // Add provided extra roll parts now because they will get clobbered by mergeObject below + if (options.parts?.length > 0) { + parts.push(...options.parts); } // Roll and return - return Dice5e.d20Roll(mergeObject(options, { + const rollData = mergeObject(options, { parts: parts, data: data, - title: `${label} Ability Test`, - speaker: ChatMessage.getSpeaker({actor: this}), - halflingLucky: feats.halflingLucky - })); + title: game.i18n.format("SW5E.AbilityPromptTitle", {ability: label}), + halflingLucky: feats.halflingLucky, + messageData: {"flags.sw5e.roll": {type: "ability", abilityId }} + }); + rollData.speaker = options.speaker || ChatMessage.getSpeaker({actor: this}); + return d20Roll(rollData); } /* -------------------------------------------- */ @@ -514,6 +858,8 @@ export class Actor5e extends Actor { rollAbilitySave(abilityId, options={}) { const label = CONFIG.SW5E.abilities[abilityId]; const abl = this.data.data.abilities[abilityId]; + + // Construct parts const parts = ["@mod"]; const data = {mod: abl.mod}; @@ -524,20 +870,27 @@ export class Actor5e extends Actor { } // Include a global actor ability save bonus - const actorBonus = getProperty(this.data.data.bonuses, "abilities.save"); - if ( !!actorBonus ) { + const bonuses = getProperty(this.data.data, "bonuses.abilities") || {}; + if ( bonuses.save ) { parts.push("@saveBonus"); - data.saveBonus = actorBonus; + data.saveBonus = bonuses.save; + } + + // Add provided extra roll parts now because they will get clobbered by mergeObject below + if (options.parts?.length > 0) { + parts.push(...options.parts); } // Roll and return - return Dice5e.d20Roll(mergeObject(options, { + const rollData = mergeObject(options, { parts: parts, data: data, - title: `${label} Saving Throw`, - speaker: ChatMessage.getSpeaker({actor: this}), - halflingLucky: this.getFlag("sw5e", "halflingLucky") - })); + title: game.i18n.format("SW5E.SavePromptTitle", {ability: label}), + halflingLucky: this.getFlag("sw5e", "halflingLucky"), + messageData: {"flags.sw5e.roll": {type: "save", abilityId }} + }); + rollData.speaker = options.speaker || ChatMessage.getSpeaker({actor: this}); + return d20Roll(rollData); } /* -------------------------------------------- */ @@ -549,51 +902,76 @@ export class Actor5e extends Actor { */ async rollDeathSave(options={}) { + // Display a warning if we are not at zero HP or if we already have reached 3 + const death = this.data.data.attributes.death; + if ( (this.data.data.attributes.hp.value > 0) || (death.failure >= 3) || (death.success >= 3)) { + ui.notifications.warn(game.i18n.localize("SW5E.DeathSaveUnnecessary")); + return null; + } + // Evaluate a global saving throw bonus - const speaker = ChatMessage.getSpeaker({actor: this}); const parts = []; const data = {}; - const bonus = getProperty(this.data.data.bonuses, "abilities.save"); - if ( bonus ) { + const speaker = options.speaker || ChatMessage.getSpeaker({actor: this}); + + // Include a global actor ability save bonus + const bonuses = getProperty(this.data.data, "bonuses.abilities") || {}; + if ( bonuses.save ) { parts.push("@saveBonus"); - data["saveBonus"] = bonus; + data.saveBonus = bonuses.save; } // Evaluate the roll - const roll = await Dice5e.d20Roll(mergeObject(options, { + const rollData = mergeObject(options, { parts: parts, data: data, - title: `Death Saving Throw`, + title: game.i18n.localize("SW5E.DeathSavingThrow"), speaker: speaker, halflingLucky: this.getFlag("sw5e", "halflingLucky"), - targetValue: 10 - })); + targetValue: 10, + messageData: {"flags.sw5e.roll": {type: "death"}} + }); + rollData.speaker = speaker; + const roll = await d20Roll(rollData); if ( !roll ) return null; // Take action depending on the result const success = roll.total >= 10; - const death = this.data.data.attributes.death; + const d20 = roll.dice[0].total; // Save success if ( success ) { - let successes = (death.success || 0) + (roll.total === 20 ? 2 : 1); - if ( successes === 3 ) { // Survival + let successes = (death.success || 0) + 1; + + // Critical Success = revive with 1hp + if ( d20 === 20 ) { await this.update({ "data.attributes.death.success": 0, "data.attributes.death.failure": 0, "data.attributes.hp.value": 1 }); - await ChatMessage.create({content: `${this.name} has survived with 3 death save successes!`, speaker}); + await ChatMessage.create({content: game.i18n.format("SW5E.DeathSaveCriticalSuccess", {name: this.name}), speaker}); } + + // 3 Successes = survive and reset checks + else if ( successes === 3 ) { + await this.update({ + "data.attributes.death.success": 0, + "data.attributes.death.failure": 0 + }); + await ChatMessage.create({content: game.i18n.format("SW5E.DeathSaveSuccess", {name: this.name}), speaker}); + } + + // Increment successes else await this.update({"data.attributes.death.success": Math.clamped(successes, 0, 3)}); } // Save failure else { - let failures = (death.failure || 0) + (roll.total === 1 ? 2 : 1); + let failures = (death.failure || 0) + (d20 === 1 ? 2 : 1); await this.update({"data.attributes.death.failure": Math.clamped(failures, 0, 3)}); - if ( failures === 3 ) { // Death - await ChatMessage.create({content: `${this.name} has died with 3 death save failures!`, speaker}); + if ( failures >= 3 ) { // 3 Failures = death + await ChatMessage.create({content: game.i18n.format("SW5E.DeathSaveFailure", {name: this.name}), speaker}); } } @@ -605,43 +983,60 @@ export class Actor5e extends Actor { /** * Roll a hit die of the appropriate type, gaining hit points equal to the die roll plus your CON modifier - * @param {string} formula The hit die type to roll. Example "d8" + * @param {string} [denomination] The hit denomination of hit die to roll. Example "d8". + * If no denomination is provided, the first available HD will be used + * @param {boolean} [dialog] Show a dialog prompt for configuring the hit die roll? + * @return {Promise} The created Roll instance, or null if no hit die was rolled */ - async rollHitDie(formula) { + async rollHitDie(denomination, {dialog=true}={}) { - // Find a class (if any) which has an available hit die of the requested denomination - const cls = this.items.find(i => { - const d = i.data.data; - return (d.hitDice === formula) && ((d.levels || 1) - (d.hitDiceUsed || 0) > 0); - }); + // If no denomination was provided, choose the first available + let cls = null; + if ( !denomination ) { + cls = this.itemTypes.class.find(c => c.data.data.hitDiceUsed < c.data.data.levels); + if ( !cls ) return null; + denomination = cls.data.data.hitDice; + } + + // Otherwise locate a class (if any) which has an available hit die of the requested denomination + else { + cls = this.items.find(i => { + const d = i.data.data; + return (d.hitDice === denomination) && ((d.hitDiceUsed || 0) < (d.levels || 1)); + }); + } // If no class is available, display an error notification if ( !cls ) { - return ui.notifications.error(`${this.name} has no available ${formula} Hit Dice remaining!`); + ui.notifications.error(game.i18n.format("SW5E.HitDiceWarn", {name: this.name, formula: denomination})); + return null; } // Prepare roll data - const parts = [formula, "@abilities.con.mod"]; - const title = `Roll Hit Die`; + const parts = [`1${denomination}`, "@abilities.con.mod"]; + const title = game.i18n.localize("SW5E.HitDiceRoll"); const rollData = duplicate(this.data.data); // Call the roll helper utility - const roll = await Dice5e.damageRoll({ + const roll = await damageRoll({ event: new Event("hitDie"), parts: parts, data: rollData, title: title, speaker: ChatMessage.getSpeaker({actor: this}), allowcritical: false, - dialogOptions: {width: 350} + fastForward: !dialog, + dialogOptions: {width: 350}, + messageData: {"flags.sw5e.roll": {type: "hitDie"}} }); - if ( !roll ) return; + if ( !roll ) return null; // Adjust actor data await cls.update({"data.hitDiceUsed": cls.data.data.hitDiceUsed + 1}); const hp = this.data.data.attributes.hp; const dhp = Math.min(hp.max - hp.value, roll.total); - return this.update({"data.attributes.hp.value": hp.value + dhp}); + await this.update({"data.attributes.hp.value": hp.value + dhp}); + return roll; } /* -------------------------------------------- */ @@ -651,56 +1046,84 @@ export class Actor5e extends Actor { * During a Short Rest resources and limited item uses may be recovered * @param {boolean} dialog Present a dialog window which allows for rolling hit dice as part of the Short Rest * @param {boolean} chat Summarize the results of the rest workflow as a chat message + * @param {boolean} autoHD Automatically spend Hit Dice if you are missing 3 or more hit points + * @param {boolean} autoHDThreshold A number of missing hit points which would trigger an automatic HD roll * @return {Promise} A Promise which resolves once the short rest workflow has completed */ - async shortRest({dialog=true, chat=true}={}) { - const data = this.data.data; + async shortRest({dialog=true, chat=true, autoHD=false, autoHDThreshold=3}={}) { // Take note of the initial hit points and number of hit dice the Actor has - const hd0 = data.attributes.hd; - const hp0 = data.attributes.hp.value; + const hp = this.data.data.attributes.hp; + const hd0 = this.data.data.attributes.hd; + const hp0 = hp.value; + let newDay = false; // Display a Dialog for rolling hit dice if ( dialog ) { - const rested = await ShortRestDialog.shortRestDialog({actor: this, canRoll: hd0 > 0}); - if ( !rested ) return; + try { + newDay = await ShortRestDialog.shortRestDialog({actor: this, canRoll: hd0 > 0}); + } catch(err) { + return; + } + } + + // Automatically spend hit dice + else if ( autoHD ) { + while ( (hp.value + autoHDThreshold) <= hp.max ) { + const r = await this.rollHitDie(undefined, {dialog: false}); + if ( r === null ) break; + } } // Note the change in HP and HD which occurred - const dhd = data.attributes.hd - hd0; - const dhp = data.attributes.hp.value - hp0; + const dhd = this.data.data.attributes.hd - hd0; + const dhp = this.data.data.attributes.hp.value - hp0; // Recover character resources const updateData = {}; - for ( let [k, r] of Object.entries(data.resources) ) { + for ( let [k, r] of Object.entries(this.data.data.resources) ) { if ( r.max && r.sr ) { updateData[`data.resources.${k}.value`] = r.max; } } // Recover pact slots. - const pact = data.powers.pact; + const pact = this.data.data.powers.pact; updateData['data.powers.pact.value'] = pact.override || pact.max; await this.update(updateData); // Recover item uses - const items = this.items.filter(item => item.data.data.uses && (item.data.data.uses.per === "sr")); + const recovery = newDay ? ["sr", "day"] : ["sr"]; + const items = this.items.filter(item => item.data.data.uses && recovery.includes(item.data.data.uses.per)); const updateItems = items.map(item => { return { _id: item._id, "data.uses.value": item.data.data.uses.max }; }); - await this.updateManyEmbeddedEntities("OwnedItem", updateItems); + await this.updateEmbeddedEntity("OwnedItem", updateItems); // Display a Chat Message summarizing the rest effects if ( chat ) { - let msg = `${this.name} takes a short rest spending ${-dhd} Hit Dice to recover ${dhp} Hit Points.`; + + // Summarize the rest duration + let restFlavor; + switch (game.settings.get("sw5e", "restVariant")) { + case 'normal': restFlavor = game.i18n.localize("SW5E.ShortRestNormal"); break; + case 'gritty': restFlavor = game.i18n.localize(newDay ? "SW5E.ShortRestOvernight" : "SW5E.ShortRestGritty"); break; + case 'epic': restFlavor = game.i18n.localize("SW5E.ShortRestEpic"); break; + } + + // Summarize the health effects + let srMessage = "SW5E.ShortRestResultShort"; + if ((dhd !== 0) && (dhp !== 0)) srMessage = "SW5E.ShortRestResult"; + + // Create a chat message ChatMessage.create({ user: game.user._id, speaker: {actor: this, alias: this.name}, - content: msg, - type: CONST.CHAT_MESSAGE_TYPES.OTHER + flavor: restFlavor, + content: game.i18n.format(srMessage, {name: this.name, dice: -dhd, health: dhp}) }); } @@ -709,7 +1132,8 @@ export class Actor5e extends Actor { dhd: dhd, dhp: dhp, updateData: updateData, - updateItems: updateItems + updateItems: updateItems, + newDay: newDay } } @@ -719,15 +1143,16 @@ export class Actor5e extends Actor { * Take a long rest, recovering HP, HD, resources, and power slots * @param {boolean} dialog Present a confirmation dialog window whether or not to take a long rest * @param {boolean} chat Summarize the results of the rest workflow as a chat message + * @param {boolean} newDay Whether the long rest carries over to a new day * @return {Promise} A Promise which resolves once the long rest workflow has completed */ - async longRest({dialog=true, chat=true}={}) { + async longRest({dialog=true, chat=true, newDay=true}={}) { const data = this.data.data; // Maybe present a confirmation dialog if ( dialog ) { try { - await ShortRestDialog.longRestDialog(this); + newDay = await LongRestDialog.longRestDialog({actor: this}); } catch(err) { return; } @@ -779,9 +1204,10 @@ export class Actor5e extends Actor { }, []); // Iterate over owned items, restoring uses per day and recovering Hit Dice + const recovery = newDay ? ["sr", "lr", "day"] : ["sr", "lr"]; for ( let item of this.items ) { const d = item.data.data; - if ( d.uses && ["sr", "lr"].includes(d.uses.per) ) { + if ( d.uses && recovery.includes(d.uses.per) ) { updateItems.push({_id: item.id, "data.uses.value": d.uses.max}); } else if ( d.recharge && d.recharge.value ) { @@ -791,14 +1217,27 @@ export class Actor5e extends Actor { // Perform the updates await this.update(updateData); - if ( updateItems.length ) await this.updateManyEmbeddedEntities("OwnedItem", updateItems); + if ( updateItems.length ) await this.updateEmbeddedEntity("OwnedItem", updateItems); // Display a Chat Message summarizing the rest effects + let restFlavor; + switch (game.settings.get("sw5e", "restVariant")) { + case 'normal': restFlavor = game.i18n.localize(newDay ? "SW5E.LongRestOvernight" : "SW5E.LongRestNormal"); break; + case 'gritty': restFlavor = game.i18n.localize("SW5E.LongRestGritty"); break; + case 'epic': restFlavor = game.i18n.localize("SW5E.LongRestEpic"); break; + } + + // Determine the chat message to display if ( chat ) { + let lrMessage = "SW5E.LongRestResultShort"; + if((dhp !== 0) && (dhd !== 0)) lrMessage = "SW5E.LongRestResult"; + else if ((dhp !== 0) && (dhd === 0)) lrMessage = "SW5E.LongRestResultHitPoints"; + else if ((dhp === 0) && (dhd !== 0)) lrMessage = "SW5E.LongRestResultHitDice"; ChatMessage.create({ user: game.user._id, speaker: {actor: this, alias: this.name}, - content: `${this.name} takes a long rest and recovers ${dhp} Hit Points and ${dhd} Hit Dice.` + flavor: restFlavor, + content: game.i18n.format(lrMessage, {name: this.name, health: dhp, dice: dhd}) }); } @@ -807,35 +1246,13 @@ export class Actor5e extends Actor { dhd: dhd, dhp: dhp, updateData: updateData, - updateItems: updateItems + updateItems: updateItems, + newDay: newDay } } - /* -------------------------------------------- */ - /** - * Convert all carried currency to the highest possible denomination to reduce the number of raw coins being - * carried by an Actor. - * @return {Promise} - */ - convertCurrency() { - const curr = duplicate(this.data.data.currency); - const convert = { - cp: {into: "sp", each: 10}, - sp: {into: "ep", each: 5 }, - ep: {into: "gp", each: 2 }, - gp: {into: "pp", each: 10} - }; - for ( let [c, t] of Object.entries(convert) ) { - let change = Math.floor(curr[c] / t.each); - curr[c] -= (change * t.each); - curr[t.into] += change; - } - return this.update({"data.currency": curr}); - } - - /* -------------------------------------------- */ /** * Transform this Actor into another one. @@ -862,12 +1279,13 @@ export class Actor5e extends Actor { // Ensure the player is allowed to polymorph const allowed = game.settings.get("sw5e", "allowPolymorphing"); if ( !allowed && !game.user.isGM ) { - return ui.notifications.warn(`You are not allowed to polymorph this actor!`); + return ui.notifications.warn(game.i18n.localize("SW5E.PolymorphWarn")); } // Get the original Actor data and the new source data const o = duplicate(this.data); o.flags.sw5e = o.flags.sw5e || {}; + o.flags.sw5e.transformOptions = {mergeSkills, mergeSaves}; const source = duplicate(target.data); // Prepare new data to merge from the source @@ -893,6 +1311,7 @@ export class Actor5e extends Actor { d.data.details.alignment = o.data.details.alignment; // Don't change alignment d.data.attributes.exhaustion = o.data.attributes.exhaustion; // Keep your prior exhaustion level d.data.attributes.inspiration = o.data.attributes.inspiration; // Keep inspiration + d.data.powers = o.data.powers; // Keep power slots // Handle wildcard if ( source.token.randomImg ) { @@ -913,29 +1332,38 @@ export class Actor5e extends Actor { const abilities = d.data.abilities; for ( let k of Object.keys(abilities) ) { const oa = o.data.abilities[k]; + const prof = abilities[k].proficient; if ( keepPhysical && ["str", "dex", "con"].includes(k) ) abilities[k] = oa; else if ( keepMental && ["int", "wis", "cha"].includes(k) ) abilities[k] = oa; if ( keepSaves ) abilities[k].proficient = oa.proficient; - else if ( mergeSaves ) abilities[k].proficient = Math.max(abilities[k].proficient, oa.proficient) + else if ( mergeSaves ) abilities[k].proficient = Math.max(prof, oa.proficient); } // Transfer skills - const skills = d.data.skills; if ( keepSkills ) d.data.skills = o.data.skills; else if ( mergeSkills ) { - for ( let [k, s] of Object.entries(skills) ) { - s.value = Math.max(s.proficient, o.data.skills[k].value); + for ( let [k, s] of Object.entries(d.data.skills) ) { + s.value = Math.max(s.value, o.data.skills[k].value); } } // Keep specific items from the original data d.items = d.items.concat(o.items.filter(i => { - if ( i.type === "class" ) return true; // Always keep class levels + if ( i.type === "class" ) return keepClass; else if ( i.type === "feat" ) return keepFeats; else if ( i.type === "power" ) return keepPowers; else return keepItems; })); + // Transfer classes for NPCs + if (!keepClass && d.data.details.cr) { + d.items.push({ + type: 'class', + name: game.i18n.localize('SW5E.PolymorphTmpClass'), + data: { levels: d.data.details.cr } + }); + } + // Keep biography if (keepBio) d.data.details.biography = o.data.details.biography; @@ -956,6 +1384,10 @@ export class Actor5e extends Actor { // Update regular Actors by creating a new Actor with the Polymorphed data await this.sheet.close(); + Hooks.callAll('sw5e.transformActor', this, target, d, { + keepPhysical, keepMental, keepSaves, keepSkills, mergeSaves, mergeSkills, + keepClass, keepFeats, keepPowers, keepItems, keepBio, keepVision, transformTokens + }); const newActor = await this.constructor.create(d, {renderSheet: true}); // Update placed Token instances @@ -968,7 +1400,7 @@ export class Actor5e extends Actor { newTokenData.actorId = newActor.id; return newTokenData; }); - return canvas.scene.updateManyEmbeddedEntities("Token", updates); + return canvas.scene.updateEmbeddedEntity("Token", updates); } /* -------------------------------------------- */ @@ -981,7 +1413,7 @@ export class Actor5e extends Actor { async revertOriginalForm() { if ( !this.isPolymorphed ) return; if ( !this.owner ) { - return ui.notifications.warn(`You do not have permission to revert this Actor's polymorphed state.`); + return ui.notifications.warn(game.i18n.localize("SW5E.PolymorphRevertWarn")); } // If we are reverting an unlinked token, simply replace it with the base actor prototype @@ -1004,7 +1436,7 @@ export class Actor5e extends Actor { tokenData.actorId = original.id; return tokenData; }); - canvas.scene.updateManyEmbeddedEntities("Token", tokenUpdates); + canvas.scene.updateEmbeddedEntity("Token", tokenUpdates); // Delete the polymorphed Actor and maybe re-render the original sheet const isRendered = this.sheet.rendered; @@ -1016,33 +1448,7 @@ export class Actor5e extends Actor { /* -------------------------------------------- */ /** - * 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} - */ - static async applyDamage(roll, multiplier) { - let value = Math.floor(parseFloat(roll.find('.dice-total').text()) * multiplier); - const promises = []; - for ( let t of canvas.tokens.controlled ) { - let a = t.actor, - hp = a.data.data.attributes.hp, - tmp = parseInt(hp.temp) || 0, - dt = value > 0 ? Math.min(tmp, value) : 0; - promises.push(t.actor.update({ - "data.attributes.hp.temp": tmp - dt, - "data.attributes.hp.value": Math.clamped(hp.value - (value - dt), 0, hp.max) - })); - } - return Promise.all(promises); - } - - /* -------------------------------------------- */ - - /** - * Add additional system-specific sidebar directory context menu options for D&D5e Actor entities + * Add additional system-specific sidebar directory context menu options for SW5e Actor entities * @param {jQuery} html The sidebar HTML * @param {Array} entryOptions The default array of context menu options */ @@ -1062,4 +1468,16 @@ export class Actor5e extends Actor { } }); } + + /* -------------------------------------------- */ + /* DEPRECATED METHODS */ + /* -------------------------------------------- */ + + /** + * @deprecated since sw5e 0.97 + */ + getPowerDC(ability) { + console.warn(`The Actor5e#getPowerDC(ability) method has been deprecated in favor of Actor5e#data.data.abilities[ability].dc`); + return this.data.data.abilities[ability]?.dc; + } } diff --git a/module/actor/sheets/base.js b/module/actor/sheets/base.js index f89aecea..7a3bdc9a 100644 --- a/module/actor/sheets/base.js +++ b/module/actor/sheets/base.js @@ -1,14 +1,14 @@ -import {TraitSelector} from "../../apps/trait-selector.js"; -import {ActorSheetFlags} from "../../apps/actor-flags.js"; +import Item5e from "../../item/entity.js"; +import TraitSelector from "../../apps/trait-selector.js"; +import ActorSheetFlags from "../../apps/actor-flags.js"; import {SW5E} from '../../config.js'; /** - * Extend the basic ActorSheet class to do all the D&D5e things! + * Extend the basic ActorSheet class to do all the SW5e things! * This sheet is an Abstract layer which is not used. - * - * @type {ActorSheet} + * @extends {ActorSheet} */ -export class ActorSheet5e extends ActorSheet { +export default class ActorSheet5e extends ActorSheet { constructor(...args) { super(...args); @@ -19,7 +19,8 @@ export class ActorSheet5e extends ActorSheet { this._filters = { inventory: new Set(), powerbook: new Set(), - features: new Set() + features: new Set(), + effects: new Set() }; } @@ -31,12 +32,20 @@ export class ActorSheet5e extends ActorSheet { scrollY: [ ".inventory .inventory-list", ".features .inventory-list", - ".powerbook .inventory-list" + ".powerbook .inventory-list", + ".effects .inventory-list" ], tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}] }); } + /* -------------------------------------------- */ + + /** @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 +62,7 @@ export class ActorSheet5e extends ActorSheet { cssClass: isOwner ? "editable" : "locked", isCharacter: this.entity.data.type === "character", isNPC: this.entity.data.type === "npc", + isVehicle: this.entity.data.type === 'vehicle', config: CONFIG.SW5E, }; @@ -74,12 +84,14 @@ export class ActorSheet5e extends ActorSheet { abl.label = CONFIG.SW5E.abilities[a]; } - // Update skill labels - for ( let [s, skl] of Object.entries(data.actor.data.skills)) { - skl.ability = data.actor.data.abilities[skl.ability].label.substring(0, 3); - skl.icon = this._getProficiencyIcon(skl.value); - skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value]; - skl.label = CONFIG.SW5E.skills[s]; + // Skills + if (data.actor.data.skills) { + for ( let [s, skl] of Object.entries(data.actor.data.skills)) { + skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability]; + skl.icon = this._getProficiencyIcon(skl.value); + skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value]; + skl.label = CONFIG.SW5E.skills[s]; + } } // Update traits @@ -88,17 +100,25 @@ export class ActorSheet5e extends ActorSheet { // Prepare owned items this._prepareItems(data); + // Prepare active effects + this._prepareEffects(data); + // Return data to the sheet return data } /* -------------------------------------------- */ + /** + * Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies + * @param {object} traits The raw traits data object from the actor data + * @private + */ _prepareTraits(traits) { const map = { - "dr": CONFIG.SW5E.damageTypes, - "di": CONFIG.SW5E.damageTypes, - "dv": CONFIG.SW5E.damageTypes, + "dr": CONFIG.SW5E.damageResistanceTypes, + "di": CONFIG.SW5E.damageResistanceTypes, + "dv": CONFIG.SW5E.damageResistanceTypes, "ci": CONFIG.SW5E.conditionTypes, "languages": CONFIG.SW5E.languages, "armorProf": CONFIG.SW5E.armorProficiencies, @@ -127,6 +147,43 @@ export class ActorSheet5e extends ActorSheet { /* -------------------------------------------- */ + /** + * Prepare the data structure for Active Effects which are currently applied to the Actor. + * @param {object} data The object of rendering data which is being prepared + * @private + */ + _prepareEffects(data) { + + // Define effect header categories + const categories = { + temporary: { + label: "Temporary Effects", + effects: [] + }, + passive: { + label: "Passive Effects", + effects: [] + }, + inactive: { + label: "Inactive Effects", + effects: [] + } + }; + + // Iterate over active effects, classifying them into categories + for ( let e of this.actor.effects ) { + e._getSourceName(); // Trigger a lookup for the source name + if ( e.data.disabled ) categories.inactive.effects.push(e); + else if ( e.isTemporary ) categories.temporary.effects.push(e); + else categories.passive.effects.push(e); + } + + // Add the prepared categories of effects to the rendering data + return data.effects = categories; + } + + /* -------------------------------------------- */ + /** * Insert a power into the powerbook object when rendering the character sheet * @param {Object} data The Actor data being prepared @@ -153,18 +210,18 @@ export class ActorSheet5e extends ActorSheet { }; // Format a powerbook entry for a certain indexed level - const registerSection = (sl, i, label, level={}) => { + const registerSection = (sl, i, label, {prepMode="prepared", value, max, override}={}) => { powerbook[i] = { order: i, label: label, usesSlots: i > 0, - canCreate: owner && (i >= 1), + canCreate: owner, canPrepare: (data.actor.type === "character") && (i >= 1), powers: [], - uses: useLabels[i] || level.value || 0, - slots: useLabels[i] || level.max || 0, - override: level.override || 0, - dataset: {"type": "power", "level": i}, + uses: useLabels[i] || value || 0, + slots: useLabels[i] || max || 0, + override: override || 0, + dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode}, prop: sl }; }; @@ -177,7 +234,7 @@ export class ActorSheet5e extends ActorSheet { return max; }, 0); - // Structure the powerbook for every level up to the maximum which has a slot + // Level-based powercasters have cantrips and leveled slots if ( maxLevel > 0 ) { registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); for (let lvl = 1; lvl <= maxLevel; lvl++) { @@ -185,9 +242,18 @@ export class ActorSheet5e extends ActorSheet { registerSection(sl, lvl, CONFIG.SW5E.powerLevels[lvl], levels[sl]); } } + + // Pact magic users have cantrips and a pact magic section if ( levels.pact && levels.pact.max ) { - registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); - registerSection("pact", sections.pact, CONFIG.SW5E.powerPreparationModes.pact, levels.pact); + if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]); + const l = levels.pact; + const config = CONFIG.SW5E.powerPreparationModes.pact; + registerSection("pact", sections.pact, config, { + prepMode: "pact", + value: l.value, + max: l.max, + override: l.override + }); } // Iterate over every power item, adding powers to the powerbook by section @@ -196,17 +262,24 @@ export class ActorSheet5e extends ActorSheet { let s = power.data.level || 0; const sl = `power${s}`; - // Powercasting mode specific headings + // Specialized powercasting modes (if they exist) if ( mode in sections ) { s = sections[mode]; if ( !powerbook[s] ){ - registerSection(sl, s, CONFIG.SW5E.powerPreparationModes[mode], levels[mode]); + const l = levels[mode] || {}; + const config = CONFIG.SW5E.powerPreparationModes[mode]; + registerSection(mode, s, config, { + prepMode: mode, + value: l.value, + max: l.max, + override: l.override + }); } } - // Higher-level power headings + // Sections for higher-level powers which the caster "should not" have, but power items exist for else if ( !powerbook[s] ) { - registerSection(sl, s, CONFIG.SW5E.powerLevels[s], levels[sl]); + registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]}); } // Add the power to the relevant heading @@ -252,7 +325,7 @@ export class ActorSheet5e extends ActorSheet { // Equipment-specific filters if ( filters.has("equipped") ) { - if (data.equipped && data.equipped !== true) return false; + if ( data.equipped !== true ) return false; } return true; }); @@ -295,8 +368,10 @@ export class ActorSheet5e extends ActorSheet { // Editable Only Listeners if ( this.isEditable ) { - // Relative updates for numeric fields - html.find('input[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this)); + // Input focus and update + const inputs = html.find("input"); + inputs.focus(ev => ev.currentTarget.select()); + inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this)); // Ability Proficiency html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this)); @@ -316,6 +391,10 @@ export class ActorSheet5e extends ActorSheet { html.find('.item-delete').click(this._onItemDelete.bind(this)); html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this)); html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this)); + + // Active Effect management + html.find(".effect-control").click(this._onManageActiveEffect.bind(this)); + } // Owner Only Listeners @@ -328,14 +407,6 @@ export class ActorSheet5e extends ActorSheet { // Roll Skill Checks 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 html.find('.item .item-image').click(event => this._onItemRoll(event)); html.find('.item .item-recharge').click(event => this._onItemRecharge(event)); @@ -424,37 +495,9 @@ export class ActorSheet5e extends ActorSheet { /* -------------------------------------------- */ /** @override */ - async _onDrop (event) { - event.preventDefault(); - - // 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) { + async _onDropActor(event, data) { + const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing')); + if ( !canPolymorph ) return false; // Get the target actor let sourceActor = null; @@ -521,6 +564,23 @@ export class ActorSheet5e extends ActorSheet { }).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; + } + + // 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 +694,7 @@ export class ActorSheet5e extends ActorSheet { const header = event.currentTarget; const type = header.dataset.type; const itemData = { - name: `New ${type.capitalize()}`, + name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}), type: type, data: duplicate(header.dataset) }; @@ -671,6 +731,28 @@ export class ActorSheet5e extends ActorSheet { /* -------------------------------------------- */ + /** + * Manage Active Effect instances through the Actor Sheet via effect control buttons. + * @param {MouseEvent} event The left-click event on the effect control + * @private + */ + _onManageActiveEffect(event) { + event.preventDefault(); + const a = event.currentTarget; + const li = a.closest(".effect"); + const effect = this.actor.effects.get(li.dataset.effectId); + switch ( a.dataset.action ) { + case "edit": + return effect.sheet.render(true); + case "delete": + return effect.delete(); + case "toggle": + return effect.update({disabled: !effect.data.disabled}); + } + } + + /* -------------------------------------------- */ + /** * Handle rolling an Ability check, either a test or a saving throw * @param {Event} event The originating click event @@ -736,11 +818,8 @@ export class ActorSheet5e extends ActorSheet { event.preventDefault(); const a = event.currentTarget; const label = a.parentElement.querySelector("label"); - const options = { - name: label.getAttribute("for"), - title: label.innerText, - choices: CONFIG.SW5E[a.dataset.options] - }; + const choices = CONFIG.SW5E[a.dataset.options]; + const options = { name: a.dataset.target, title: label.innerText, choices }; new TraitSelector(this.actor, options).render(true) } @@ -760,4 +839,90 @@ export class ActorSheet5e extends ActorSheet { }); 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); + } +} \ No newline at end of file diff --git a/module/actor/sheets/npc.js b/module/actor/sheets/npc.js index 9046bccb..023b5ce4 100644 --- a/module/actor/sheets/npc.js +++ b/module/actor/sheets/npc.js @@ -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 D&D5E system. + * An Actor sheet for NPC type characters in the SW5E system. * Extends the base ActorSheet5e class. - * @type {ActorSheet5e} + * @extends {ActorSheet5e} */ -export class ActorSheet5eNPC extends ActorSheet5e { +export default class ActorSheet5eNPC extends ActorSheet5e { - /** - * Define default rendering options for the NPC sheet - * @return {Object} - */ + /** @override */ static get defaultOptions() { return mergeObject(super.defaultOptions, { classes: ["sw5e", "sheet", "actor", "npc"], 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 const features = { - weapons: { label: "Attacks", items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, - actions: { label: "Actions", items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, - passive: { label: "Features", items: [], dataset: {type: "feat"} }, - equipment: { label: "Inventory", items: [], dataset: {type: "loot"}} + weapons: { label: game.i18n.localize("SW5E.AttackPl"), items: [] , hasActions: true, dataset: {type: "weapon", "weapon-type": "natural"} }, + actions: { label: game.i18n.localize("SW5E.ActionPl"), items: [] , hasActions: true, dataset: {type: "feat", "activation.type": "action"} }, + passive: { label: game.i18n.localize("SW5E.Features"), items: [], dataset: {type: "feat"} }, + equipment: { label: game.i18n.localize("SW5E.Inventory"), items: [], dataset: {type: "loot"}} }; // Start by classifying items into groups for rendering let [powers, other] = data.items.reduce((arr, item) => { item.img = item.img || DEFAULT_TOKEN; - item.isStack = 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.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)); @@ -86,9 +70,7 @@ export class ActorSheet5eNPC extends ActorSheet5e { /* -------------------------------------------- */ - /** - * Add some extra data when rendering the sheet to reduce the amount of logic required within the template. - */ + /** @override */ getData() { const data = super.getData(); @@ -103,12 +85,7 @@ export class ActorSheet5eNPC extends ActorSheet5e { /* Object Updates */ /* -------------------------------------------- */ - /** - * 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 - */ + /** @override */ _updateObject(event, formData) { // Format NPC Challenge Rating @@ -126,14 +103,9 @@ export class ActorSheet5eNPC extends ActorSheet5e { /* Event Listeners and Handlers */ /* -------------------------------------------- */ - /** - * Activate event listeners using the prepared sheet HTML - * @param html {HTML} The prepared HTML object ready to be rendered into the DOM - */ + /** @override */ activateListeners(html) { super.activateListeners(html); - - // Rollable Health Formula html.find(".health .rollable").click(this._onRollHealthFormula.bind(this)); } @@ -152,4 +124,4 @@ export class ActorSheet5eNPC extends ActorSheet5e { AudioHelper.play({src: CONFIG.sounds.dice}); this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp}); } -} \ No newline at end of file +} diff --git a/module/actor/sheets/vehicle.js b/module/actor/sheets/vehicle.js new file mode 100644 index 00000000..4fc5cdde --- /dev/null +++ b/module/actor/sheets/vehicle.js @@ -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|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} + * @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} + * @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} + * @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} + * @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} + * @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}); + } +}; diff --git a/module/apps/ability-use-dialog.js b/module/apps/ability-use-dialog.js index ec3510c8..13fe0963 100644 --- a/module/apps/ability-use-dialog.js +++ b/module/apps/ability-use-dialog.js @@ -2,7 +2,7 @@ * A specialized Dialog subclass for ability usage * @type {Dialog} */ -export class AbilityUseDialog extends Dialog { +export default class AbilityUseDialog extends Dialog { constructor(item, dialogData={}, options={}) { super(dialogData, options); this.options.classes = ["sw5e", "dialog"]; @@ -25,40 +25,156 @@ export class AbilityUseDialog extends Dialog { * @return {Promise} */ 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; - const recharge = item.data.data.recharge; + // Prepare data + 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; - // Render the ability usage template - const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", { + // Prepare dialog form data + const data = { item: item.data, - canUse: recharges ? recharge.charged : uses.value > 0, - consume: true, - uses: uses, - recharges: !!recharge.value, - isCharged: recharge.charged, + title: game.i18n.format("SW5E.AbilityUseHint", item.data), + note: this._getAbilityUseNote(item.data, uses, recharge), + hasLimitedUses: uses.max || recharges, + canUse: recharges ? recharge.charged : (quantity > 0 && !uses.value) || uses.value > 0, 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 + const icon = data.isPower ? "fa-magic" : "fa-fist-raised"; + const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use")); return new Promise((resolve) => { - let formData = null; const dlg = new this(item, { - title: `${item.name}: Ability Configuration`, + title: `${item.name}: Usage Configuration`, content: html, buttons: { use: { - icon: '', - label: "Use Ability", - callback: html => formData = new FormData(html[0].querySelector("#ability-use-form")) + icon: ``, + label: label, + callback: html => resolve(new FormData(html[0].querySelector("form"))) } }, default: "use", - close: () => resolve(formData) + close: () => resolve(null) }); 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); + + // If can't upcast, return early and don't bother calculating available power slots + if (!canUpcast) { + data = mergeObject(data, { isPower: true, canUpcast }); + return; + } + + // 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: 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: true, + hasSlots: pact.value > 0 + }); + } + const canCast = powerLevels.some(l => l.hasSlots); + + // Return merged data + data = mergeObject(data, { isPower: 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) { + + } } diff --git a/module/apps/actor-flags.js b/module/apps/actor-flags.js index 1e35d0e7..9d7a9da1 100644 --- a/module/apps/actor-flags.js +++ b/module/apps/actor-flags.js @@ -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; return mergeObject(options, { id: "actor-flags", @@ -68,10 +72,10 @@ export class ActorSheetFlags extends BaseEntitySheet { {name: "data.bonuses.mwak.damage", label: "SW5E.BonusMWDamage"}, {name: "data.bonuses.rwak.attack", label: "SW5E.BonusRWAttack"}, {name: "data.bonuses.rwak.damage", label: "SW5E.BonusRWDamage"}, - {name: "data.bonuses.msak.attack", label: "SW5E.BonusMSAttack"}, - {name: "data.bonuses.msak.damage", label: "SW5E.BonusMSDamage"}, - {name: "data.bonuses.rsak.attack", label: "SW5E.BonusRSAttack"}, - {name: "data.bonuses.rsak.damage", label: "SW5E.BonusRSDamage"}, + {name: "data.bonuses.mpak.attack", label: "SW5E.BonusMPAttack"}, + {name: "data.bonuses.mpak.damage", label: "SW5E.BonusMPDamage"}, + {name: "data.bonuses.rpak.attack", label: "SW5E.BonusRPAttack"}, + {name: "data.bonuses.rpak.damage", label: "SW5E.BonusRPDamage"}, {name: "data.bonuses.abilities.check", label: "SW5E.BonusAbilityCheck"}, {name: "data.bonuses.abilities.save", label: "SW5E.BonusAbilitySave"}, {name: "data.bonuses.abilities.skill", label: "SW5E.BonusAbilitySkill"}, @@ -91,7 +95,7 @@ export class ActorSheetFlags extends BaseEntitySheet { */ async _updateObject(event, formData) { const actor = this.object; - const updateData = expandObject(formData); + let updateData = expandObject(formData); // Unset any flags which are "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 = diffObject(this.object.data, updateData); + } await actor.update(updateData, {diff: false}); } } diff --git a/module/apps/long-rest.js b/module/apps/long-rest.js new file mode 100644 index 00000000..ce7e90d4 --- /dev/null +++ b/module/apps/long-rest.js @@ -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: '', + 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: '', + label: "Cancel", + callback: reject + } + }, + default: 'rest', + close: reject + }); + dlg.render(true); + }); + } +} \ No newline at end of file diff --git a/module/apps/short-rest.js b/module/apps/short-rest.js index 8d28ebbb..6394b531 100644 --- a/module/apps/short-rest.js +++ b/module/apps/short-rest.js @@ -1,8 +1,10 @@ +import LongRestDialog from "./long-rest.js"; + /** * 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={}) { super(dialogData, options); @@ -34,6 +36,8 @@ export class ShortRestDialog extends Dialog { /** @override */ getData() { const data = super.getData(); + + // Determine Hit Dice data.availableHD = this.actor.data.items.reduce((hd, item) => { if ( item.type === "class" ) { const d = item.data; @@ -45,6 +49,11 @@ export class ShortRestDialog extends Dialog { }, {}); data.canRoll = this.actor.data.data.attributes.hd > 0; 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; } @@ -56,7 +65,6 @@ export class ShortRestDialog extends Dialog { super.activateListeners(html); let btn = html.find("#roll-hd"); btn.click(this._onRollHitDie.bind(this)); - super.activateListeners(html); } /* -------------------------------------------- */ @@ -83,21 +91,27 @@ export class ShortRestDialog extends Dialog { * @return {Promise} */ static async shortRestDialog({actor}={}) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { const dlg = new this(actor, { title: "Short Rest", buttons: { rest: { icon: '', 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: { icon: '', label: "Cancel", - callback: () => resolve(false) + callback: reject } - } + }, + close: reject }); 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 * workflow has been resolved. + * @deprecated * @param {Actor5e} actor * @return {Promise} */ static async longRestDialog({actor}={}) { - const content = `

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 power slots.

`; - return new Promise((resolve, reject) => { - new Dialog({ - title: "Long Rest", - content: content, - buttons: { - rest: { - icon: '', - label: "Rest", - callback: resolve - }, - cancel: { - icon: '', - label: "Cancel", - callback: reject - }, - }, - default: 'rest', - close: reject - }, {classes: ["sw5e", "dialog"]}).render(true); - }); + console.warn("WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead."); + return LongRestDialog.longRestDialog(...arguments); } } diff --git a/module/apps/spell-cast-dialog.js b/module/apps/spell-cast-dialog.js deleted file mode 100644 index 5c925976..00000000 --- a/module/apps/spell-cast-dialog.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * A specialized Dialog subclass for casting a spell item at a certain level - * @type {Dialog} - */ -export class SpellCastDialog extends Dialog { - constructor(actor, item, dialogData={}, options={}) { - super(dialogData, options); - this.options.classes = ["dnd5e", "dialog"]; - - /** - * Store a reference to the Actor entity which is casting the spell - * @type {Actor5e} - */ - this.actor = actor; - - /** - * Store a reference to the Item entity which is the spell being cast - * @type {Item5e} - */ - this.item = item; - } - - /* -------------------------------------------- */ - /* Rendering */ - /* -------------------------------------------- */ - - /** - * A constructor function which displays the Spell Cast Dialog app for a given Actor and Item. - * Returns a Promise which resolves to the dialog FormData once the workflow has been completed. - * @param {Actor5e} actor - * @param {Item5e} item - * @return {Promise} - */ - static async create(actor, item) { - const ad = actor.data.data; - const id = item.data.data; - - // Determine whether the spell may be upcast - const lvl = id.level; - const canUpcast = (lvl > 0) && CONFIG.DND5E.spellUpcastModes.includes(id.preparation.mode); - - // Determine the levels which are feasible - let lmax = 0; - const spellLevels = Array.fromRange(10).reduce((arr, i) => { - if ( i < lvl ) return arr; - const l = ad.spells["spell"+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 ? `${CONFIG.DND5E.spellLevels[i]} (${slots} Slots)` : CONFIG.DND5E.spellLevels[i], - canCast: canUpcast && (max > 0), - hasSlots: slots > 0 - }); - return arr; - }, []).filter(sl => sl.level <= lmax); - - const pact = ad.spells.pact; - if (pact.level >= lvl) { - // If this character has pact slots, present them as an option for - // casting the spell. - spellLevels.push({ - level: 'pact', - label: game.i18n.localize('DND5E.SpellLevelPact') - + ` (${game.i18n.localize('DND5E.Level')} ${pact.level}) ` - + `(${pact.value} ${game.i18n.localize('DND5E.Slots')})`, - canCast: canUpcast, - hasSlots: pact.value > 0 - }); - } - - const canCast = spellLevels.some(l => l.hasSlots); - - // Render the Spell casting template - const html = await renderTemplate("systems/dnd5e/templates/apps/spell-cast.html", { - item: item.data, - canCast: canCast, - canUpcast: canUpcast, - spellLevels, - hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget - }); - - // Create the Dialog and return as a Promise - return new Promise((resolve, reject) => { - const dlg = new this(actor, item, { - title: `${item.name}: Spell Configuration`, - content: html, - buttons: { - cast: { - icon: '', - label: "Cast", - callback: html => resolve(new FormData(html[0].querySelector("#spell-config-form"))) - } - }, - default: "cast", - close: reject - }); - dlg.render(true); - }); - } -} diff --git a/module/apps/trait-selector.js b/module/apps/trait-selector.js index 8a947004..a4fc43d7 100644 --- a/module/apps/trait-selector.js +++ b/module/apps/trait-selector.js @@ -2,7 +2,7 @@ * A specialized form used to select from a checklist of attributes, traits, or properties * @extends {FormApplication} */ -export class TraitSelector extends FormApplication { +export default class TraitSelector extends FormApplication { /** @override */ static get defaultOptions() { diff --git a/module/canvas.js b/module/canvas.js index da882372..1767d4d8 100644 --- a/module/canvas.js +++ b/module/canvas.js @@ -1,32 +1,40 @@ -/** - * Measure the distance between two pixel coordinates - * See BaseGrid.measureDistance for more details - * - * @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)); +/** @override */ +export const measureDistances = function(segments, options={}) { + if ( !options.gridSpaces ) return BaseGrid.prototype.measureDistances.call(this, segments, options); - // Get the number of straight and diagonal moves - let nDiagonal = Math.min(nx, ny), - nStraight = Math.abs(ny - nx); + // Track the total number of diagonals + let nDiagonal = 0; + const rule = this.parent.diagonalRule; + const d = canvas.dimensions; - // Alternative DMG Movement - if ( this.parent.diagonalRule === "5105" ) { - let nd10 = Math.floor(nDiagonal / 2); - let spaces = (nd10 * 2) + (nDiagonal - nd10) + nStraight; - return spaces * canvas.dimensions.distance; - } + // Iterate over measured segments + return segments.map(s => { + let r = s.ray; - // Standard PHB Movement - else return (nStraight + nDiagonal) * canvas.scene.data.gridDistance; + // Determine the total distance traveled + 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) { const data = _TokenGetBarAttribute.bind(this)(...args); if ( data && (data.attribute === "attributes.hp") ) { - data.value += parseInt(data['temp'] || 0); - data.max += parseInt(data['tempmax'] || 0); + data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0); + data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0); } return data; }; diff --git a/module/chat.js b/module/chat.js index d1533b9d..0d4bcd3d 100644 --- a/module/chat.js +++ b/module/chat.js @@ -1,19 +1,19 @@ -import {Actor5e} from "./actor/entity.js"; /** * Highlight critical success or failure on d20 rolls */ 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 const roll = message.roll; - let d = roll.parts[0]; - const isD20Roll = d instanceof Die && (d.faces === 20) && (d.results.length === 1); - if ( !isD20Roll ) return; + if ( !roll.dice.length ) return; + const d = roll.dice[0]; - // Ensure it is not a modified roll - const isModifiedRoll = ("success" in d.rolls[0]) || d.options.marginSuccess || d.options.marginFailure; + // 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.results[0]) || d.options.marginSuccess || d.options.marginFailure; if ( isModifiedRoll ) return; // Highlight successes and failures @@ -33,6 +33,7 @@ export const highlightCriticalSuccessFailure = function(message, html, data) { export const displayChatActionButtons = function(message, html, data) { const chatCard = html.find(".sw5e.chat-card"); if ( chatCard.length > 0 ) { + html.find(".flavor-text").remove(); // If the user is the message author or the actor owner, proceed let actor = game.actors.get(data.message.speaker.actor); @@ -60,32 +61,55 @@ export const displayChatActionButtons = function(message, html, data) { * @return {Array} The extended options Array including new context choices */ 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( { name: game.i18n.localize("SW5E.ChatContextDamage"), icon: '', condition: canApply, - callback: li => Actor5e.applyDamage(li, 1) + callback: li => applyChatCardDamage(li, 1) }, { name: game.i18n.localize("SW5E.ChatContextHealing"), icon: '', condition: canApply, - callback: li => Actor5e.applyDamage(li, -1) + callback: li => applyChatCardDamage(li, -1) }, { name: game.i18n.localize("SW5E.ChatContextDoubleDamage"), icon: '', condition: canApply, - callback: li => Actor5e.applyDamage(li, 2) + callback: li => applyChatCardDamage(li, 2) }, { name: game.i18n.localize("SW5E.ChatContextHalfDamage"), icon: '', condition: canApply, - callback: li => Actor5e.applyDamage(li, 0.5) + callback: li => applyChatCardDamage(li, 0.5) } ); return options; -}; \ No newline at end of file +}; + +/* -------------------------------------------- */ + +/** + * 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); + })); +} + +/* -------------------------------------------- */ diff --git a/module/classFeatures.js b/module/classFeatures.js new file mode 100644 index 00000000..f5bdc02b --- /dev/null +++ b/module/classFeatures.js @@ -0,0 +1,35 @@ +export const ClassFeatures = { + "berserker": { + "archetypes": { + "addicted-approach": { + "label": "Addicted Approach", + "source": "PHB", + "features": { + "3": ["Compendium.sw5e.archetypes.PCwepUZqHYlxr4T3", "Compendium.sw5e.classfeatures.efOA0nrvUqKJOOeP", "Compendium.sw5e.classfeatures.nT6AfpQXSZ4IeChO"], + "6": ["Compendium.sw5e.classfeatures.GbJDWzoTKWL7sEpR"], + "10": ["Compendium.sw5e.classfeatures.3jqPPd5qJBBnonPw"], + "14": ["Compendium.sw5e.classfeatures.xzRNHB2M2HdOZzr7"] + } + } + }, + "features": { + "1": ["Compendium.sw5e.classfeatures.IDt6duVrBzL8euRc", "Compendium.sw5e.classfeatures.rPOLy96fW96N2UPg"], + "2": ["Compendium.sw5e.classfeatures.DlYiCiG39R0goG9u", "Compendium.sw5e.classfeatures.FbSpxpXm1xONn0na", "Compendium.sw5e.classfeatures.KDiQ8O2evV2Z1YTo", "Compendium.sw5e.classfeatures.Q1JyHnVs9iIEBs91", "Compendium.sw5e.classfeatures.ROdICoWR82v6A2Rf", "Compendium.sw5e.classfeatures.cdCx5Hvq2rYRMzRj", "Compendium.sw5e.classfeatures.dTdbL8dypa6BAdnP", "Compendium.sw5e.classfeatures.h1uDhP1tEOuvjRw6", "Compendium.sw5e.classfeatures.hMiA075EKBBOL2cv", "Compendium.sw5e.classfeatures.sgJdISZMtwv08WPJ", "Compendium.sw5e.classfeatures.v4CZJ8LBMl5PYZCO"], + "3": ["Compendium.sw5e.classfeatures.kzwSN9SabKgWZZvU"], + "4": ["Compendium.sw5e.classfeatures.9oyy0MMqEws2qoil"], + "5": ["Compendium.sw5e.classfeatures.dPWmHiWmpnhHTsgd"], + "7": ["Compendium.sw5e.classfeatures.Cid5ujSdnooH0vMm", "Compendium.sw5e.classfeatures.WTBhKJgkArQI3Tgv", "Compendium.sw5e.classfeatures.oiT3TJxzRWPKAX9E", "Compendium.sw5e.classfeatures.pMEmIt3NWThbee8k", "Compendium.sw5e.classfeatures.qWV5YogZcpZ3Y3xj"], + "9": ["Compendium.sw5e.classfeatures.bi8G8H5Ur9B3BAyM"], + "11": ["Compendium.sw5e.classfeatures.eWbTifdXJvvXT4CV"], + "13": ["Compendium.sw5e.classfeatures.Hg8zYh1iXL0DGUVq", "Compendium.sw5e.classfeatures.QRnYiJmRk18ekE9v", "Compendium.sw5e.classfeatures.sfEr8ZBFVddlfLeF", "Compendium.sw5e.classfeatures.yGC9VzT840qQWxca"], + "15": ["Compendium.sw5e.classfeatures.YHPUv9lN3nCapAgP"], + "18": ["Compendium.sw5e.classfeatures.fFKNqUAWh0ZOhvRc"], + "20": ["Compendium.sw5e.classfeatures.IWTDawTUf79eWbEV"] + } + }, + "consular": { + "features": { + "20": ["Compendium.sw5e.classfeatures.gSGeitc98ItAwhfF"] + } + } +}; \ No newline at end of file diff --git a/module/combat.js b/module/combat.js index 06b2ae0b..8b50b13a 100644 --- a/module/combat.js +++ b/module/combat.js @@ -1,6 +1,6 @@ /** - * Override the default Initiative formula to customize special behaviors of the D&D5e system. + * Override the default Initiative formula to customize special behaviors of the SW5e system. * Apply advantage, proficiency, or bonuses where appropriate * Apply the dexterity score as a decimal tiebreaker if requested * See Combat._getInitiativeFormula for more detail. @@ -9,8 +9,59 @@ export const _getInitiativeFormula = function(combatant) { const actor = combatant.actor; if ( !actor ) return "1d20"; const init = actor.data.data.attributes.init; - 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 ( CONFIG.Combat.initiative.tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100); + + let nd = 1; + let mods = ""; + + if (actor.getFlag("sw5e", "halflingLucky")) mods += "r=1"; + if (actor.getFlag("sw5e", "initiativeAdv")) { + nd = 2; + mods += "kh"; + } + + const parts = [`${nd}d20${mods}`, init.mod, (init.prof !== 0) ? init.prof : null, (init.bonus !== 0) ? init.bonus : null]; + + // 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(" + "); }; + + +/* -------------------------------------------- */ + + +/** + * 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; +}; \ No newline at end of file diff --git a/module/config.js.bak b/module/config.js.bak deleted file mode 100644 index 11d50a1a..00000000 --- a/module/config.js.bak +++ /dev/null @@ -1,839 +0,0 @@ -// Namespace D&D5e Configuration Values -export const SW5E = {}; - -// ASCII Artwork -SW5E.ASCII = `_______________________________ - _ - | | - ___| |_ __ _ _ ____ ____ _ _ __ ___ -/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __| -\__ \ || (_) | | \ V V / (_) | | \__ \ -|___/\__\__/_|_| \_/\_/ \__/_|_| |___/ -_______________________________`; - - -/** - * The set of Ability Scores used within the system - * @type {Object} - */ -SW5E.abilities = { - "str": "SW5E.AbilityStr", - "dex": "SW5E.AbilityDex", - "con": "SW5E.AbilityCon", - "int": "SW5E.AbilityInt", - "wis": "SW5E.AbilityWis", - "cha": "SW5E.AbilityCha" -}; - -/* -------------------------------------------- */ - -/** - * Character alignment options - * @type {Object} - */ -SW5E.alignments = { - 'll': "SW5E.AlignmentLL", - 'nl': "SW5E.AlignmentNL", - 'cl': "SW5E.AlignmentCL", - 'lb': "SW5E.AlignmentLB", - 'bn': "SW5E.AlignmentBN", - 'cb': "SW5E.AlignmentCB", - 'ld': "SW5E.AlignmentLD", - 'nd': "SW5E.AlignmentND", - 'cd': "SW5E.AlignmentCD" -}; - - -SW5E.weaponProficiencies = { - "sim": "SW5E.WeaponSimpleProficiency", - "bla": "SW5E.WeaponBlasterProficiency" -}; - -SW5E.toolProficiencies = { - "armor": "SW5E.ToolArmormech", - "arms": "SW5E.ToolArmstech", - "arti": "SW5E.ToolArtificer", - "art": "SW5E.ToolArtist", - "astro": "SW5E.ToolAstrotech", - "bio": "SW5E.ToolBiotech", - "con": "SW5E.ToolConstructor", - "cyb": "SW5E.ToolCybertech", - "jew": "SW5E.ToolJeweler", - "sur": "SW5E.ToolSurveyor", - "syn": "SW5E.ToolSynthweaver", - "tin": "SW5E.ToolTinker", - "ant": "SW5E.ToolAntitoxkit", - "arc": "SW5E.ToolArchaeologistKit", - "aud": "SW5E.ToolAudiotechKit", - "bioa": "SW5E.ToolBioanalysisKit", - "brew": "SW5E.ToolBrewerKit", - "chef": "SW5E.ToolChefKit", - "demo": "SW5E.ToolDemolitionKit", - "disg": "SW5E.ToolDisguiseKit", - "forg": "SW5E.ToolForgeryKit", - "mech": "SW5E.ToolMechanicKit", - "game": "SW5E.ToolGamingSet", - "poi": "SW5E.ToolPoisonKit", - "scav": "SW5E.ToolScavengingKit", - "secur": "SW5E.ToolSecurityKit", - "slic": "SW5E.ToolSlicerKit", - "spice": "SW5E.ToolSpiceKit", - "music": "SW5E.ToolMusicalInstrument", - "vehicle": "SW5E.ToolVehicle" -}; - - -/* -------------------------------------------- */ - -/** - * This Object defines the various lengths of time which can occur in D&D5e - * @type {Object} - */ -SW5E.timePeriods = { - "inst": "SW5E.TimeInst", - "turn": "SW5E.TimeTurn", - "round": "SW5E.TimeRound", - "minute": "SW5E.TimeMinute", - "hour": "SW5E.TimeHour", - "day": "SW5E.TimeDay", - "month": "SW5E.TimeMonth", - "year": "SW5E.TimeYear", - "perm": "SW5E.TimePerm", - "spec": "SW5E.Special" -}; - - -/* -------------------------------------------- */ - -/** - * This describes the ways that an ability can be activated - * @type {Object} - */ -SW5E.abilityActivationTypes = { - "none": "SW5E.None", - "action": "SW5E.Action", - "bonus": "SW5E.BonusAction", - "reaction": "SW5E.Reaction", - "minute": SW5E.timePeriods.minute, - "hour": SW5E.timePeriods.hour, - "day": SW5E.timePeriods.day, - "special": SW5E.timePeriods.spec, - "legendary": "SW5E.LegAct", - "lair": "SW5E.LairAct" -}; - -/* -------------------------------------------- */ - -// Creature Sizes -SW5E.actorSizes = { - "tiny": "SW5E.SizeTiny", - "sm": "SW5E.SizeSmall", - "med": "SW5E.SizeMedium", - "lg": "SW5E.SizeLarge", - "huge": "SW5E.SizeHuge", - "grg": "SW5E.SizeGargantuan" -}; - -SW5E.tokenSizes = { - "tiny": 1, - "sm": 1, - "med": 1, - "lg": 2, - "huge": 3, - "grg": 4 -}; - -/* -------------------------------------------- */ - -/** - * Classification types for item action types - * @type {Object} - */ -SW5E.itemActionTypes = { - "mwak": "SW5E.ActionMWAK", - "rwak": "SW5E.ActionRWAK", - "mpak": "SW5E.ActionMPAK", - "rpak": "SW5E.ActionRPAK", - "save": "SW5E.ActionSave", - "heal": "SW5E.ActionHeal", - "abil": "SW5E.ActionAbil", - "util": "SW5E.ActionUtil", - "other": "SW5E.ActionOther" -}; - -/* -------------------------------------------- */ - -SW5E.itemCapacityTypes = { - "items": "SW5E.ItemContainerCapacityItems", - "weight": "SW5E.ItemContainerCapacityWeight" -}; - -/* -------------------------------------------- */ - -/** - * Enumerate the lengths of time over which an item can have limited use ability - * @type {Object} - */ -SW5E.limitedUsePeriods = { - "sr": "SW5E.ShortRest", - "lr": "SW5E.LongRest", - "day": "SW5E.Day", - "charges": "SW5E.Charges" -}; - - -/* -------------------------------------------- */ - -/** - * The set of equipment types for armor, clothing, and other objects which can ber worn by the character - * @type {Object} - */ -SW5E.equipmentTypes = { - "light": "SW5E.EquipmentLight", - "medium": "SW5E.EquipmentMedium", - "heavy": "SW5E.EquipmentHeavy", - "bonus": "SW5E.EquipmentBonus", - "natural": "SW5E.EquipmentNatural", - "shield": "SW5E.EquipmentShield", - "clothing": "SW5E.EquipmentClothing", - "trinket": "SW5E.EquipmentTrinket" -}; - - -/* -------------------------------------------- */ - -/** - * The set of Armor Proficiencies which a character may have - * @type {Object} - */ -SW5E.armorProficiencies = { - "lgt": SW5E.equipmentTypes.light, - "med": SW5E.equipmentTypes.medium, - "hvy": SW5E.equipmentTypes.heavy, - "shl": "SW5E.EquipmentShieldProficiency" -}; - - -/* -------------------------------------------- */ - -/** - * Enumerate the valid consumable types which are recognized by the system - * @type {Object} - */ -SW5E.consumableTypes = { - "adrenal": "SW5E.ConsumableAdrenal", - "poison": "SW5E.ConsumablePoison", - "explosive": "SW5E.ConsumableExplosive", - "food": "SW5E.ConsumableFood", - "medpac": "SW5E.ConsumableMedpac", - "technology": "SW5E.ConsumableTechnology", - "ammunition": "SW5E.ConsumableAmmunition", - "trinket": "SW5E.ConsumableTrinket" -}; - - -/* -------------------------------------------- */ - -/** - * The valid currency denominations supported by the 5e system - * @type {Object} - */ -SW5E.currencies = { - "CR": "SW5E.CurrencyCR", - }; - -/* -------------------------------------------- */ - - -// Damage Types -SW5E.damageTypes = { - "acid": "SW5E.DamageAcid", - "cold": "SW5E.DamageCold", - "energy": "SW5E.DamageEnergy", - "fire": "SW5E.DamageFire", - "force": "SW5E.DamageForce", - "ion": "SW5E.DamageIon", - "kinetic": "SW5E.DamageKinetic", - "lightning": "SW5E.DamageLightning", - "necrotic": "SW5E.DamageNecrotic", - "poison": "SW5E.DamagePoison", - "psychic": "SW5E.DamagePsychic", - "Sonic": "SW5E.DamageSonic" -}; - -/* -------------------------------------------- */ - -// armor Types -SW5E.armorpropertiesTypes = { -"Absorptive": "SW5E.ArmorProperAbsorptive", -"Agile": "SW5E.ArmorProperAgile", -"Anchor": "SW5E.ArmorProperAnchor", -"Avoidant": "SW5E.ArmorProperAvoidant", -"Barbed": "SW5E.ArmorProperBarbed", -"Charging": "SW5E.ArmorProperCharging", -"Concealing": "SW5E.ArmorProperConcealing", -"Cumbersome": "SW5E.ArmorProperCumbersome", -"Gauntleted": "SW5E.ArmorProperGauntleted", -"Imbalanced": "SW5E.ArmorProperImbalanced", -"Impermeable": "SW5E.ArmorProperImpermeable", -"Insulated": "SW5E.ArmorProperInsulated", -"Interlocking": "SW5E.ArmorProperInterlocking", -"Lambent": "SW5E.ArmorProperLambent", -"Lightweight": "SW5E.ArmorProperLightweight", -"Magnetic": "SW5E.ArmorProperMagnetic", -"Obscured": "SW5E.ArmorProperObscured", -"Powered": "SW5E.ArmorProperPowered", -"Reactive": "SW5E.ArmorProperReactive", -"Regulated": "SW5E.ArmorProperRegulated", -"Reinforced": "SW5E.ArmorProperReinforced", -"Responsive": "SW5E.ArmorProperResponsive", -"Rigid": "SW5E.ArmorProperRigid", -"Silent": "SW5E.ArmorProperSilent", -"Spiked": "SW5E.ArmorProperSpiked", -"Steadfast": "SW5E.ArmorProperSteadfast", -"Versatile": "SW5E.ArmorProperVersatile" -}; - -/* -------------------------------------------- */ - -SW5E.distanceUnits = { - "none": "SW5E.None", - "self": "SW5E.DistSelf", - "touch": "SW5E.DistTouch", - "ft": "SW5E.DistFt", - "mi": "SW5E.DistMi", - "spec": "SW5E.Special", - "any": "SW5E.DistAny" -}; - -/* -------------------------------------------- */ - - -/** - * Configure aspects of encumbrance calculation so that it could be configured by modules - * @type {Object} - */ -SW5E.encumbrance = { - currencyPerWeight: 50, - strMultiplier: 15 -}; - -/* -------------------------------------------- */ - -/** - * This Object defines the types of single or area targets which can be applied in D&D5e - * @type {Object} - */ -SW5E.targetTypes = { - "none": "SW5E.None", - "self": "SW5E.TargetSelf", - "creature": "SW5E.TargetCreature", - "droid": "SW5E.TargetDroid", - "ally": "SW5E.TargetAlly", - "enemy": "SW5E.TargetEnemy", - "object": "SW5E.TargetObject", - "space": "SW5E.TargetSpace", - "radius": "SW5E.TargetRadius", - "sphere": "SW5E.TargetSphere", - "cylinder": "SW5E.TargetCylinder", - "cone": "SW5E.TargetCone", - "square": "SW5E.TargetSquare", - "cube": "SW5E.TargetCube", - "line": "SW5E.TargetLine", - "wall": "SW5E.TargetWall", -}; - - -/* -------------------------------------------- */ - - -/** - * Map the subset of target types which produce a template area of effect - * The keys are SW5E target types and the values are MeasuredTemplate shape types - * @type {Object} - */ -SW5E.areaTargetTypes = { - cone: "cone", - cube: "rect", - cylinder: "circle", - line: "ray", - radius: "circle", - sphere: "circle", - square: "rect", - wall: "ray" -}; - - -/* -------------------------------------------- */ - -// Healing Types -SW5E.healingTypes = { - "healing": "SW5E.Healing", - "temphp": "SW5E.HealingTemp" -}; - - -/* -------------------------------------------- */ - - -/** - * Enumerate the denominations of hit dice which can apply to classes in the D&D5E system - * @type {Array.} - */ -SW5E.hitDieTypes = ["d6", "d8", "d10", "d12"]; - - -/* -------------------------------------------- */ - -/** - * Character senses options - * @type {Object} - */ -SW5E.senses = { - "bs": "SW5E.SenseBS", - "dv": "SW5E.SenseDV", - "ts": "SW5E.SenseTS", - "tr": "SW5E.SenseTR" -}; - - -/* -------------------------------------------- */ - -/** - * The set of skill which can be trained in D&D5e - * @type {Object} - */ -SW5E.skills = { - "acr": "SW5E.SkillAcr", - "ani": "SW5E.SkillAni", - "ath": "SW5E.SkillAth", - "dec": "SW5E.SkillDec", - "ins": "SW5E.SkillIns", - "itm": "SW5E.SkillItm", - "inv": "SW5E.SkillInv", - "lor": "SW5E.SkillLor", - "med": "SW5E.SkillMed", - "nat": "SW5E.SkillNat", - "prc": "SW5E.SkillPrc", - "prf": "SW5E.SkillPrf", - "per": "SW5E.SkillPer", - "pil": "SW5E.SkillPil", - "slt": "SW5E.SkillSlt", - "ste": "SW5E.SkillSte", - "sur": "SW5E.SkillSur", - "tec": "SW5E.SkillTec", -}; - - -/* -------------------------------------------- */ - -SW5E.powerPreparationModes = { - "always": "SW5E.PowerPrepAlways", - "atwill": "SW5E.PowerPrepAtWill", - "innate": "SW5E.PowerPrepInnate", - "prepared": "SW5E.PowerPrepPrepared" -}; - -SW5E.powerUpcastModes = ["always"]; - - -SW5E.powerProgression = { - "none": "SW5E.PowerNone", - "full": "SW5E.PowerProgFull", - "artificer": "SW5E.PowerProgArt" -}; - -/* -------------------------------------------- */ - -/** - * The available choices for how power damage scaling may be computed - * @type {Object} - */ -SW5E.powerScalingModes = { - "none": "SW5E.PowerNone", - "atwill": "SW5E.PowerAtWill", - "level": "SW5E.PowerLevel" -}; - -/* -------------------------------------------- */ - -// Weapon Types -SW5E.weaponTypes = { - "simpleVW": "SW5E.WeaponSimpleVW", - "simpleB": "SW5E.WeaponSimpleB", - "simpleLW": "SW5E.WeaponSimpleLW", - "martialVW": "SW5E.WeaponMartialVW", - "martialB": "SW5E.WeaponMartialB", - "martialLW": "SW5E.WeaponMartialLW", - "natural": "SW5E.WeaponNatural", - "improv": "SW5E.WeaponImprov", - "ammo": "SW5E.WeaponAmmo" -}; - - -/* -------------------------------------------- */ - -/** - * Define the set of weapon property flags which can exist on a weapon - * @type {Object} - */ -SW5E.weaponProperties = { - "amm": "SW5E.WeaponPropertiesAmm", - "bur": "SW5E.WeaponPropertiesBur", - "def": "SW5E.WeaponPropertiesDef", - "dex": "SW5E.WeaponPropertiesDex", - "drm": "SW5E.WeaponPropertiesDrm", - "dgd": "SW5E.WeaponPropertiesDgd", - "dis": "SW5E.WeaponPropertiesDis", - "dpt": "SW5E.WeaponPropertiesDpt", - "dou": "SW5E.WeaponPropertiesDou", - "hvy": "SW5E.WeaponPropertiesHvy", - "hid": "SW5E.WeaponPropertiesHid", - "fin": "SW5E.WeaponPropertiesFin", - "fix": "SW5E.WeaponPropertiesFix", - "foc": "SW5E.WeaponPropertiesFoc", - "ken": "SW5E.WeaponPropertiesKen", - "lgt": "SW5E.WeaponPropertiesLgt", - "lum": "SW5E.WeaponPropertiesLum", - "pic": "SW5E.WeaponPropertiesPic", - "rap": "SW5E.WeaponPropertiesRap", - "rch": "SW5E.WeaponPropertiesRch", - "rel": "SW5E.WeaponPropertiesRel", - "ret": "SW5E.WeaponPropertiesRet", - "shk": "SW5E.WeaponPropertiesShk", - "spc": "SW5E.WeaponPropertiesSpc", - "str": "SW5E.WeaponPropertiesStr", - "thr": "SW5E.WeaponPropertiesThr", - "two": "SW5E.WeaponPropertiesTwo", - "ver": "SW5E.WeaponPropertiesVer", - "vic": "SW5E.WeaponPropertiesVic" -}; - - -// Power Components -SW5E.powerComponents = { - "V": "SW5E.ComponentVerbal", - "S": "SW5E.ComponentSomatic", - "M": "SW5E.ComponentMaterial" -}; - -// Power Schools -SW5E.powerSchools = { - "lgt": "SW5E.SchoolLgt", - "uni": "SW5E.SchoolUni", - "drk": "SW5E.SchoolDrk", - "tec": "SW5E.SchoolTec", - "enh": "SW5E.SchoolEnh" -}; - - -// Power Levels -SW5E.powerLevels = { - 0: "SW5E.PowerLevel0", - 1: "SW5E.PowerLevel1", - 2: "SW5E.PowerLevel2", - 3: "SW5E.PowerLevel3", - 4: "SW5E.PowerLevel4", - 5: "SW5E.PowerLevel5", - 6: "SW5E.PowerLevel6", - 7: "SW5E.PowerLevel7", - 8: "SW5E.PowerLevel8", - 9: "SW5E.PowerLevel9" -}; - -/** - * Define the standard slot progression by character level. - * The entries of this array represent the power slot progression for a full power-caster. - * @type {Array[]} - */ -SW5E.SPELL_SLOT_TABLE = [ - [2], - [3], - [4, 2], - [4, 3], - [4, 3, 2], - [4, 3, 3], - [4, 3, 3, 1], - [4, 3, 3, 2], - [4, 3, 3, 3, 1], - [4, 3, 3, 3, 2], - [4, 3, 3, 3, 2, 1], - [4, 3, 3, 3, 2, 1], - [4, 3, 3, 3, 2, 1, 1], - [4, 3, 3, 3, 2, 1, 1], - [4, 3, 3, 3, 2, 1, 1, 1], - [4, 3, 3, 3, 2, 1, 1, 1], - [4, 3, 3, 3, 2, 1, 1, 1, 1], - [4, 3, 3, 3, 3, 1, 1, 1, 1], - [4, 3, 3, 3, 3, 2, 1, 1, 1], - [4, 3, 3, 3, 3, 2, 2, 1, 1] -]; - -/* -------------------------------------------- */ - -// Polymorph options. -SW5E.polymorphSettings = { - keepPhysical: 'SW5E.PolymorphKeepPhysical', - keepMental: 'SW5E.PolymorphKeepMental', - keepSaves: 'SW5E.PolymorphKeepSaves', - keepSkills: 'SW5E.PolymorphKeepSkills', - mergeSaves: 'SW5E.PolymorphMergeSaves', - mergeSkills: 'SW5E.PolymorphMergeSkills', - keepClass: 'SW5E.PolymorphKeepClass', - keepFeats: 'SW5E.PolymorphKeepFeats', - keepPowers: 'SW5E.PolymorphKeepPowers', - keepItems: 'SW5E.PolymorphKeepItems', - keepBio: 'SW5E.PolymorphKeepBio', - keepVision: 'SW5E.PolymorphKeepVision' -}; - -/* -------------------------------------------- */ - -/** - * Skill, ability, and tool proficiency levels - * Each level provides a proficiency multiplier - * @type {Object} - */ -SW5E.proficiencyLevels = { - 0: "SW5E.NotProficient", - 1: "SW5E.Proficient", - 0.5: "SW5E.HalfProficient", - 2: "SW5E.Expertise" -}; - -/* -------------------------------------------- */ - - -// Condition Types -SW5E.conditionTypes = { - "blinded": "SW5E.ConBlinded", - "charmed": "SW5E.ConCharmed", - "deafened": "SW5E.ConDeafened", - "exhaustion": "SW5E.ConExhaustion", - "frightened": "SW5E.ConFrightened", - "grappled": "SW5E.ConGrappled", - "incapacitated": "SW5E.ConIncapacitated", - "invisible": "SW5E.ConInvisible", - "paralyzed": "SW5E.ConParalyzed", - "petrified": "SW5E.ConPetrified", - "poisoned": "SW5E.ConPoisoned", - "prone": "SW5E.ConProne", - "restrained": "SW5E.ConRestrained", - "shocked": "SW5E.ConShocked", - "stunned": "SW5E.ConStunned", - "unconscious": "SW5E.ConUnconscious" -}; - -// Languages -SW5E.languages = { - "aleena": "SW5E.LanguagesAleena", - "antarian": "SW5E.LanguagesAntarian", - "anzellan": "SW5E.LanguagesAnzellan", - "aqualish": "SW5E.LanguagesAqualish", - "ardennian": "SW5E.LanguagesArdennian", - "balosur": "SW5E.LanguagesBalosur", - "barabel": "SW5E.LanguagesBarabel", - "basic": "SW5E.LanguagesBasic", -"besalisk": "SW5E.LanguagesBesalisk", - "binary": "SW5E.LanguagesBinary", - "bith": "SW5E.LanguagesBith", - "bocce": "SW5E.LanguagesBocce", - "bothese": "SW5E.LanguagesBothese", - "catharese": "SW5E.LanguagesCartharese", - "cerean": "SW5E.LanguagesCerean", - "chadra-fan": "SW5E.LanguagesChadra-Fan", - "chagri": "SW5E.LanguagesChagri", - "cheunh": "SW5E.LanguagesCheunh", - "chevin": "SW5E.LanguagesChevin", - "chironan": "SW5E.LanguagesChironan", - "clawdite": "SW5E.LanguagesClawdite", - "codruese": "SW5E.LanguagesCodruese", - "colicoid": "SW5E.LanguagesColicoid", - "dashadi": "SW5E.LanguagesDashadi", - "defel": "SW5E.LanguagesDefel", - "devaronese": "SW5E.LanguagesDevaronese", - "dosh": "SW5E.LanguagesDosh", - "draethos": "SW5E.LanguagesDraethos", - "durese": "SW5E.LanguagesDurese", - "dug": "SW5E.LanguagesDug", - "ewokese": "SW5E.LanguagesEwokese", - "falleen": "SW5E.LanguagesFalleen", - "felucianese": "SW5E.LanguagesFelucianese", - "gamorrese": "SW5E.LanguagesGamorrese", - "gand": "SW5E.LanguagesGand", - "geonosian": "SW5E.LanguagesGeonosian", - "givin": "SW5E.LanguagesGivin", - "gran": "SW5E.LanguagesGran", - "gungan": "SW5E.LanguagesGungan", - "hapan": "SW5E.LanguagesHapan", - "harchese": "SW5E.LanguagesHarchese", - "herglese": "SW5E.LanguagesHerglese", - "honoghran": "SW5E.LanguagesHonoghran", - "huttese": "SW5E.LanguagesHuttese", - "iktotchese": "SW5E.LanguagesIktotchese", - "ithorese": "SW5E.LanguagesIthorese", - "jawaese": "SW5E.LanguagesJawaese", - "kaleesh": "SW5E.LanguagesKaleesh", - "kaminoan": "SW5E.LanguagesKaminoan", - "karkaran": "SW5E.LanguagesKarkaran", - "keldor": "SW5E.LanguagesKelDor", - "kharan": "SW5E.LanguagesKharan", - "killik": "SW5E.LanguagesKillik", - "klatooinian": "SW5E.LanguagesKlatooinian", - "kubazian": "SW5E.LanguagesKubazian", - "kushiban": "SW5E.LanguagesKushiban", - "kyuzo": "SW5E.LanguagesKyuzo", - "lannik": "SW5E.LanguagesLannik", - "lasat": "SW5E.LanguagesLasat", - "lowickese": "SW5E.LanguagesLowickese", - "mandoa": "SW5E.LanguagesMandoa", - "miralukese": "SW5E.LanguagesMiralukese", - "mirialan": "SW5E.LanguagesMirialan", - "moncal": "SW5E.LanguagesMonCal", - "mustafarian": "SW5E.LanguagesMustafarian", - "muun": "SW5E.LanguagesMuun", - "nautila": "SW5E.LanguagesNautila", - "ortolan": "SW5E.LanguagesOrtolan", - "pakpak": "SW5E.LanguagesPakPak", - "pyke": "SW5E.LanguagesPyke", - "quarrenese": "SW5E.LanguagesQuarrenese", - "rakata": "SW5E.LanguagesRakata", - "rattataki": "SW5E.LanguagesRattataki", - "rishii": "SW5E.LanguagesRishii", - "rodese": "SW5E.LanguagesRodese", - "selkatha": "SW5E.LanguagesSelkatha", - "semblan": "SW5E.LanguagesSemblan", - "shistavanen": "SW5E.LanguagesShistavanen", - "shyriiwook": "SW5E.LanguagesShyriiwook", - "sith": "SW5E.LanguagesSith", - "squibbian": "SW5E.LanguagesSquibbian", - "sriluurian": "SW5E.LanguagesSriluurian", - "ssi-ruuvi": "SW5E.LanguagesSsi-ruuvi", - "sullustese": "SW5E.LanguagesSullustese", - "talzzi": "SW5E.LanguagesTalzzi", - "thisspiasian": "SW5E.LanguagesThisspiasian", - "togorese": "SW5E.LanguagesTogorese", - "togruti": "SW5E.LanguagesTogruti", - "toydarian": "SW5E.LanguagesToydarian", - - "tusken": "SW5E.LanguagesTusken", - "twi'leki": "SW5E.LanguagesTwileki", - "ugnaught": "SW5E.LanguagesUgnaught", - "umbaran": "SW5E.LanguagesUmbaran", - "utapese": "SW5E.LanguagesUtapese", - "verpine": "SW5E.LanguagesVerpine", - "vong": "SW5E.LanguagesVong", - "voss": "SW5E.LanguagesVoss", - "yevethan": "SW5E.LanguagesYevethan", - "zabraki": "SW5E.LanguagesZabraki", - "zygerrian": "SW5E.LanguagesZygerrian" -}; - -// Character Level XP Requirements -SW5E.CHARACTER_EXP_LEVELS = [ - 0, 300, 900, 2700, 6500, 14000, 23000, 34000, 48000, 64000, 85000, 100000, - 120000, 140000, 165000, 195000, 225000, 265000, 305000, 355000] -; - -// Challenge Rating XP Levels -SW5E.CR_EXP_LEVELS = [ - 10, 200, 450, 700, 1100, 1800, 2300, 2900, 3900, 5000, 5900, 7200, 8400, 10000, 11500, 13000, 15000, 18000, - 20000, 22000, 25000, 33000, 41000, 50000, 62000, 75000, 90000, 105000, 120000, 135000, 155000 -]; - -// Configure Optional Character Flags -SW5E.characterFlags = { - "detailOriented": { - name: "Detail Oriented", - hint: "You have advantage on Intelligence (Investigation) checks within 5 feet.", - section: "Racial Traits", - type: Boolean - }, - "keenSenses": { - name: "Keen Hearing and Smell", - hint: "You have advantage on Wisdom (Perception) checks that involve hearing or smell.", - section: "Racial Traits", - type: Boolean - }, - "naturallyStealthy": { - name: "Naturally Stealthy", - hint: "You can attempt to hide even when you are obscured only by a creature that is your size or larger than you.", - section: "Racial Traits", - type: Boolean - }, - "nimbleEscape": { - name: "Nimble Escape", - hint: "You can take the Disengage or Hide action as a bonus action.", - section: "Racial Traits", - type: Boolean - }, - "powerfulBuild": { - name: "Powerful Build", - hint: "You count as one size larger when determining your carrying capacity and the weight you can push, drag, or lift.", - section: "Racial Traits", - type: Boolean - }, - "programmer": { - name: "Programmer", - hint: "Whenever you make an Intelligence (Technology) check related to computers, you are considered to have expertise in the Technology skill.", - section: "Racial Traits", - type: Boolean - }, - "techResistance": { - name: "Tech Resistance", - hint: "You have advantage on Dexterity and Intelligence saving throws against tech powers.", - section: "Racial Traits", - type: Boolean - }, - "unarmedCombatant": { - name: "Unarmed Combatant", - hint: "Your unarmed strikes deal 1d4 kinetic damage. You can use your choice of your Strength or Dexterity modifier for the attack and damage rolls. You must use the same modifier for both rolls.", - section: "Racial Traits", - type: Boolean - }, - "undersized": { - name: "Undersized", - hint: "You can’t use heavy shields, martial weapons with the two-handed property unless it also has the light property, and if a martial weapon has the versatile property, you can only wield it in two hands.", - section: "Racial Traits", - type: Boolean - }, - "initiativeAdv": { - name: "Advantage on Initiative", - hint: "Provided by feats or magical items.", - section: "Feats", - type: Boolean - }, - "initiativeAlert": { - name: "Alert Feat", - hint: "Provides +5 to Initiative.", - section: "Feats", - type: Boolean - }, - "jackOfAllTrades": { - name: "Jack of All Trades", - hint: "Half-Proficiency to Ability Checks in which you are not already Proficient.", - section: "Feats", - type: Boolean - }, - "observantFeat": { - name: "Observant Feat", - hint: "Provides a +5 to passive Perception and Investigation.", - skills: ['prc','inv'], - section: "Feats", - type: Boolean - }, - "remarkableAthlete": { - name: "Remarkable Athlete.", - hint: "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.", - abilities: ['str','dex','con'], - section: "Feats", - type: Boolean - }, - "weaponCriticalThreshold": { - name: "Critical Hit Threshold", - hint: "Allow for expanded critical range; for example Improved or Superior Critical", - section: "Feats", - type: Number, - placeholder: 20 - } -}; diff --git a/module/dice.js b/module/dice.js index 8f2d4e46..dd647155 100644 --- a/module/dice.js +++ b/module/dice.js @@ -1,125 +1,156 @@ -export class Dice5e { - /** * A standardized helper function for managing core 5e "d20 rolls" * * 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 * - * @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 {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|null} template The HTML template used to render the roll dialog - * @param {string|null} title The dice roll UI window title - * @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 {Boolean} fastForward Allow fast-forward advantage selection - * @param {Function} onClose Callback for actions to take when the dialog form is closed - * @param {Object} dialogOptions Modal dialog options - * @param {boolean} advantage Apply advantage 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} 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 {boolean} elvenAccuracy Allow Elven Accuracy to modify this roll? - * @param {boolean} halflingLucky Allow Halfling Luck to modify this roll? - * - * @return {Promise} A Promise which resolves once the roll workflow has completed + * @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 {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|null} template The HTML template used to render the roll dialog + * @param {string|null} title The dice roll UI window title + * @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 {Boolean} fastForward Allow fast-forward advantage selection + * @param {Function} onClose Callback for actions to take when the dialog form is closed + * @param {Object} dialogOptions Modal dialog options + * @param {boolean} advantage Apply advantage 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} 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 {boolean} elvenAccuracy Allow Elven Accuracy to modify this roll? + * @param {boolean} halflingLucky Allow Halfling Luck to modify this roll? + * @param {boolean} reliableTalent Allow Reliable Talent to modify this roll? + * @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, - flavor=null, fastForward=null, onClose, dialogOptions, - advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null, - elvenAccuracy=false, halflingLucky=false}={}) { - - // Handle input arguments - flavor = flavor || title; - speaker = speaker || ChatMessage.getSpeaker(); + export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null, + flavor=null, fastForward=null, dialogOptions, + advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null, + elvenAccuracy=false, halflingLucky=false, reliableTalent=false, + chatMessage=true, messageData={}}={}) { + + // Prepare Message Data + messageData.flavor = flavor || title; + messageData.speaker = speaker || ChatMessage.getSpeaker(); + const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")}; parts = parts.concat(["@bonus"]); - rollMode = rollMode || game.settings.get("core", "rollMode"); - let rolled = false; - - // Define inner roll function - const _roll = function(parts, adv, form=null) { - - // Determine the d20 roll and modifiers + + // Handle fast-forward events + let adv = 0; + fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); + if (fastForward) { + if ( advantage || event.altKey ) adv = 1; + 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 mods = halflingLucky ? "r=1" : ""; // Handle advantage - if ( adv === 1 ) { + if (adv === 1) { nd = elvenAccuracy ? 3 : 2; - flavor += ` (${game.i18n.localize("SW5E.Advantage")})`; - mods += "kh"; + messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`; + if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true; + mods += "kh"; } // Handle disadvantage - else if ( adv === -1 ) { + else if (adv === -1) { nd = 2; - flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`; - mods += "kl"; + messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`; + if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true; + mods += "kl"; } - // Include the d20 roll - parts.unshift(`${nd}d20${mods}`); + // Prepend the d20 roll + let formula = `${nd}d20${mods}`; + if (reliableTalent) formula = `{${nd}d20${mods},10}kh`; + parts.unshift(formula); // Optionally include a situational bonus - if ( form !== null ) data['bonus'] = form.bonus.value; - if ( !data["bonus"] ) parts.pop(); - + if ( form ) { + 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) const ability = form ? form.ability : null; - if ( ability && ability.value ) { + if (ability && ability.value) { data.ability = ability.value; const abl = data.abilities[data.ability]; - if ( abl ) { - data.mod = abl.mod; - flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`; - } + if (abl) { + data.mod = abl.mod; + messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`; + } } - // Execute the roll and flag critical thresholds on the d20 - let roll = new Roll(parts.join(" + "), data).roll(); - const d20 = roll.parts[0]; - d20.options.critical = critical; - d20.options.fumble = fumble; - if ( targetValue ) d20.options.target = targetValue; - - // Convert the roll to a chat message and return the roll - 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); + // Execute the roll + let roll = new Roll(parts.join(" + "), data); + try { + roll.roll(); + } catch (err) { + console.error(err); + ui.notifications.error(`Dice roll evaluation failed: ${err.message}`); + return null; } - // Optionally allow fast-forwarding to specify advantage or disadvantage - if ( fastForward ) { - if ( advantage || event.altKey ) return _roll(parts, 1); - else if ( disadvantage || event.ctrlKey || event.metaKey ) return _roll(parts, -1); - else return _roll(parts, 0); + // Flag d20 options for any 20-sided dice in the roll + for (let d of roll.dice) { + if (d.faces === 20) { + d.options.critical = critical; + 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} + * @private + */ +async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) { + // Render modal dialog template = template || "systems/sw5e/templates/chat/roll-dialog.html"; let dialogData = { formula: parts.join(" + "), data: data, rollMode: rollMode, - rollModes: CONFIG.rollModes, + rollModes: CONFIG.Dice.rollModes, config: CONFIG.SW5E }; const html = await renderTemplate(template, dialogData); // Create the Dialog window - let roll; return new Promise(resolve => { new Dialog({ title: title, @@ -127,26 +158,24 @@ export class Dice5e { buttons: { 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: { 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: { 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", - close: html => { - if (onClose) onClose(html, parts, data); - resolve(rolled ? roll : false) - } + close: () => resolve(null) }, dialogOptions).render(true); - }) + }); } + /* -------------------------------------------- */ /** @@ -169,83 +198,103 @@ export class Dice5e { * @param {Boolean} fastForward Allow fast-forward advantage selection * @param {Function} onClose Callback for actions to take when the dialog form is closed * @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 */ - static async damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor, - allowCritical=true, critical=false, fastForward=null, onClose, dialogOptions}) { - - // Handle input arguments - flavor = flavor || title; - speaker = speaker || ChatMessage.getSpeaker(); - rollMode = game.settings.get("core", "rollMode"); - let rolled = false; + export async function damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor, + allowCritical=true, critical=false, fastForward=null, dialogOptions, chatMessage=true, messageData={}}={}) { + + // Prepare Message Data + messageData.flavor = flavor || title; + messageData.speaker = speaker || ChatMessage.getSpeaker(); + const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")}; + parts = parts.concat(["@bonus"]); + fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)); // Define inner roll function 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); - // Modify the damage formula for critical hits - if ( crit === true ) { - let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0; - let mult = 2; - roll.alter(add, mult); - flavor = `${flavor} (${game.i18n.localize("SW5E.Critical")})`; - } - - // Convert the roll to a chat message - 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 damage formula for critical hits + if ( crit === true ) { + let add = (actor && actor.getFlag("sw5e", "savageAttacks")) ? 1 : 0; + let mult = 2; + // TODO Backwards compatibility - REMOVE LATER + if (isNewerVersion(game.data.version, "0.6.9")) roll.alter(mult, add); + else roll.alter(add, mult); + messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`; + if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true; } - // Modify the roll and handle fast-forwarding - if ( fastForward ) return _roll(parts, critical || event.altKey); - else parts = parts.concat(["@bonus"]); + // Execute the roll + try { + return roll.roll(); + } catch(err) { + console.error(err); + ui.notifications.error(`Dice roll evaluation failed: ${err.message}`); + return null; + } + }; - // Render modal dialog - template = template || "systems/sw5e/templates/chat/roll-dialog.html"; - let dialogData = { - formula: parts.join(" + "), - data: data, - rollMode: rollMode, - rollModes: CONFIG.rollModes - }; - const html = await renderTemplate(template, dialogData); + // Create the Roll instance + const roll = fastForward ? _roll(parts, critical || event.altKey) : await _damageRollDialog({ + template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll + }); + + // Create a Chat Message + if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions); + return roll; - // Create the Dialog window - let roll; - return new Promise(resolve => { - new Dialog({ - title: title, - content: html, - buttons: { - critical: { - condition: allowCritical, - label: game.i18n.localize("SW5E.CriticalHit"), - callback: html => roll = _roll(parts, true, html[0].children[0]) - }, - normal: { - label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"), - callback: html => roll = _roll(parts, false, html[0].children[0]) - }, - }, - default: "normal", - close: html => { - if (onClose) onClose(html, parts, data); - resolve(rolled ? roll : false); - } - }, dialogOptions).render(true); - }); - } +} + +/* -------------------------------------------- */ + +/** + * Present a Dialog form which creates a damage roll once submitted + * @return {Promise} + * @private + */ +async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll}={}) { + + // Render modal dialog + template = template || "systems/sw5e/templates/chat/roll-dialog.html"; + let dialogData = { + formula: parts.join(" + "), + data: data, + rollMode: rollMode, + rollModes: CONFIG.Dice.rollModes + }; + const html = await renderTemplate(template, dialogData); + + // Create the Dialog window + 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); + }); } diff --git a/module/macros.js b/module/macros.js new file mode 100644 index 00000000..8b9c91b2 --- /dev/null +++ b/module/macros.js @@ -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(); +} diff --git a/module/migration.js b/module/migration.js index 6258747c..6009ac4b 100644 --- a/module/migration.js +++ b/module/migration.js @@ -136,6 +136,34 @@ export const migrateActorData = function(actor) { return updateData; }; +/* -------------------------------------------- */ + + +/** + * Scrub an Actor's system data, removing all keys which are not explicitly defined in the system template + * @param {Object} actorData The data object for an Actor + * @return {Object} The scrubbed Actor data + */ +function cleanActorData(actorData) { + + // Scrub system data + const model = game.system.model.Actor[actorData.type]; + actorData.data = filterObject(actorData.data, model); + + // Scrub system flags + const allowedFlags = CONFIG.SW5E.allowedActorFlags.reduce((obj, f) => { + obj[f] = null; + return obj; + }, {}); + if ( actorData.flags.sw5e ) { + actorData.flags.sw5e = filterObject(actorData.flags.sw5e, allowedFlags); + } + + // Return the scrubbed data + return actorData; +} + + /* -------------------------------------------- */ /** @@ -222,3 +250,33 @@ const _migrateRemoveDeprecated = function(ent, updateData) { updateData[`data.${parts.join(".")}`] = null; } }; + + +/* -------------------------------------------- */ + + +/** + * A general tool to purge flags from all entities in a Compendium pack. + * @param {Compendium} pack The compendium pack to clean + * @private + */ +export async function purgeFlags(pack) { + const cleanFlags = (flags) => { + const flags5e = flags.sw5e || null; + return flags5e ? {sw5e: flags5e} : {}; + }; + await pack.configure({locked: false}); + const content = await pack.getContent(); + for ( let entity of content ) { + const update = {_id: entity.id, flags: cleanFlags(entity.data.flags)}; + if ( pack.entity === "Actor" ) { + update.items = entity.data.items.map(i => { + i.flags = cleanFlags(i.flags); + return i; + }) + } + await pack.updateEntity(update, {recursive: false}); + console.log(`Purged flags from ${entity.name}`); + } + await pack.configure({locked: true}); +} diff --git a/module/pixi/ability-template.js b/module/pixi/ability-template.js index 13ca13d2..e07795e6 100644 --- a/module/pixi/ability-template.js +++ b/module/pixi/ability-template.js @@ -4,7 +4,7 @@ import { SW5E } from "../config.js"; * A helper class for building MeasuredTemplates for 5e powers and abilities * @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 @@ -37,8 +37,8 @@ export class AbilityTemplate extends MeasuredTemplate { templateData.width = target.value; templateData.direction = 45; break; - case "ray": // 5e rays are most commonly 5ft wide - templateData.width = 5; + case "ray": // 5e rays are most commonly 1 square (5 ft) in width + templateData.width = target.width ?? canvas.dimensions.distance; break; default: break; @@ -52,9 +52,8 @@ export class AbilityTemplate extends MeasuredTemplate { /** * Creates a preview of the power template - * @param {Event} event The initiating click event */ - drawPreview(event) { + drawPreview() { const initialLayer = canvas.activeLayer; this.draw(); this.layer.activate(); diff --git a/module/settings.js b/module/settings.js index 38402552..5c40216b 100644 --- a/module/settings.js +++ b/module/settings.js @@ -11,6 +11,23 @@ export const registerSystemSettings = function() { 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 */ @@ -23,7 +40,8 @@ export const registerSystemSettings = function() { type: String, choices: { "555": "SETTINGS.5eDiagPHB", - "5105": "SETTINGS.5eDiagDMG" + "5105": "SETTINGS.5eDiagDMG", + "EUCL": "SETTINGS.5eDiagEuclidean", }, onChange: rule => canvas.grid.diagonalRule = rule }); @@ -31,21 +49,14 @@ export const registerSystemSettings = function() { /** * 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", { name: "SETTINGS.5eInitTBN", hint: "SETTINGS.5eInitTBL", scope: "world", config: true, default: false, - type: Boolean, - onChange: enable => _set5eInitiative(enable) + type: Boolean }); - _set5eInitiative(game.settings.get("sw5e", "initiativeDexTiebreaker")); /** * Require Currency Carrying Weight diff --git a/module/templates.js b/module/templates.js index 0b6aec28..e14f1cd8 100644 --- a/module/templates.js +++ b/module/templates.js @@ -13,11 +13,13 @@ export const preloadHandlebarsTemplates = async function() { "systems/sw5e/templates/actors/parts/actor-inventory.html", "systems/sw5e/templates/actors/parts/actor-features.html", "systems/sw5e/templates/actors/parts/actor-powerbook.html", + "systems/sw5e/templates/actors/parts/actor-effects.html", // Item Sheet Partials "systems/sw5e/templates/items/parts/item-action.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 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..9122d71a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3101 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "accord": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", + "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", + "requires": { + "convert-source-map": "^1.5.0", + "glob": "^7.0.5", + "indx": "^0.2.3", + "lodash.clone": "^4.3.2", + "lodash.defaults": "^4.0.1", + "lodash.flatten": "^4.2.0", + "lodash.merge": "^4.4.0", + "lodash.partialright": "^4.1.4", + "lodash.pick": "^4.2.1", + "lodash.uniq": "^4.3.0", + "resolve": "^1.5.0", + "semver": "^5.3.0", + "uglify-js": "^2.8.22", + "when": "^3.7.8" + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==" + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "requires": { + "async-done": "^1.2.2" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=" + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "requires": { + "for-in": "^1.0.1" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "requires": { + "sparkles": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + } + } + } + }, + "gulp-less": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", + "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", + "requires": { + "accord": "^0.29.0", + "less": "2.6.x || ^3.7.1", + "object-assign": "^4.0.1", + "plugin-error": "^0.1.2", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "requires": { + "glogg": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "optional": true + }, + "indx": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/indx/-/indx-0.2.3.tgz", + "integrity": "sha1-Fdz1bunPZcAjTFE8J/vVgOcPvFA=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-core-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", + "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "less": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/less/-/less-3.12.2.tgz", + "integrity": "sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q==", + "requires": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.partialright": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz", + "integrity": "sha1-ATDYDoM2MmTUAHTzKbij56ihzEs=" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true + } + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==" + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "native-request": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.0.7.tgz", + "integrity": "sha512-9nRjinI9bmz+S7dgNtf4A70+/vPhnd+2krGpy4SUlADuOuSa24IDkNaZ+R/QT1wQ6S8jBdi6wE7fLekFZNfUpQ==", + "optional": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "requires": { + "once": "^1.3.2" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "requires": { + "readable-stream": "^2.0.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=" + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "optional": true + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==" + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "resolve": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", + "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", + "requires": { + "is-core-module": "^2.0.0", + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "^0.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "requires": { + "sver-compat": "^1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "requires": { + "through2": "^2.0.3" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" + }, + "undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=" + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "requires": { + "source-map": "^0.5.1" + } + }, + "when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + } + } +} diff --git a/packs/Icons/Portraits/Ezrukh Khim'bar.webp b/packs/Icons/Portraits/Ezrukh Khim'bar.webp new file mode 100644 index 0000000000000000000000000000000000000000..8188a88c83dd44478bfb27e35cc754867eeaa060 GIT binary patch literal 38964 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Ɲ%9W;i{oN&y=-6vz7=U7S0H^#~Zjq|kgKDeV5(XD4y#d}TwIRHFJ zjNyR@6A%v7D0FKvyJ z<0GS=jUT2_%g7R}B~%Or9MKCR7HBS+(MAf+FhbTQKoFFNRa#o*sYI%F6deU&a+sCH zN-(h$YQ((j#6UGNDxevOLMpH znZ>4u5(HzQjGAGoNJ7EnK!7|})QyAAv!qR;Qb}u}VV*nE_&qUxq_`zL1H7B_ z$Rpl)8^|GfzpUuCw!wA|8Czd7!Hc&FVfWiO75@!JY3NBLX&R)*{e^eTTiI4yj-4Y}}fMso$ zRxYCYu8oAIk{z^1a^1qZ_mweezpEEyPzy|lCP$j!yXCh;Np|HrO_+LD5{Ub1S?1rl zik$c&XB_;ooE9_A4exP0xLdPFTf8D3 zEw1T)VL~0ak004$7R4Nxhf$Hhx=sT;dA{iq*jxm+0^W_@7Y|p9QW$QA9&>ss{6@Og zwrAv|6Pz;iTC}YV+A#f(>oHJbffq1 zU`M~xd8;zh_Lyq``0IJ_z5N-2b5}?yD5pp%wr@4@%`6#qbQ;?lG3@Zo*}Rv1X(8zL zT-PL@mId3R2QG5K2ik}Sil*(BI^#21Y;L|HuGh~m7=tof!A3ipD`PyREcIR z$1|(Y{`8I7UI+W#{lfwjPu~<;!0dF)(`0N@mtO}c;bq84(y_}ClA397S2cD)-GD#C zw!P(*CP9BC>fq>#t6on=)bwb1^aQe8cm9Md4@?X{Xsen6q&%Gj{v zT8w*1?OZyU&A-(KI^xXC4A9<9{GocvvKbPKOkjeYcl`yF8`4%@DIAV%M-D2_R4`5E zawx7E0TDP2Z3~@s60u}mnMHkuLiah03S0vfpp5#IZ`loCvc#mW`>D{|VSLTMH8slh z7>sys@c2g>J+X;usS$dL$IgHb?@-qcQw*^MgDx;Q%EgnBQ{ZyiEyM9u1*Rlz@?yA^ zF3WZQ6uK{GunI}?Bu;Z*+n}zUnvd*xDJ!G~3q%D%amTI{m_}y-*E#|HnLUuz?GRTDuA?ZM{$UY&oIu(^TWvT#a|J(h4yDNDJAj;4r$NV#yXXS-iIXMKGBZNCr}2RC}n-qLr2^9V3Y; z*1$-O`9kZU>MdY}Ci9_M%ZTVPoN)e`4m`&hwMLP4_et0!7j2^+I0MaW-RU7<%AIA8 zo%J#NBp?<()CGh1EUi=CNPHTEVO@gy=0>YX_lWVF2+5l4TIrD^#I!Mpb5-l7@n`iI z>GJetNSjQ?&TfE-yYj$`gGj4q+%^#>q$lNT&B>*5$O2l#uS!fTQuv)^%T39LrLR)N z%~ldOG9iFWlB?>`3@(IfryZhY9ag)mX`VpiA*QkH zagyHs-1N6k2^M{K_zzP!CzJqnj9~6#EGBu{BRsgx6L2s%F=O0uOukLYvbQ$Ftzi%W}%24GP8rsG<+5#q!>VQyXnY(1BI(iEh;U`}H7 z1Hc?z>+s-&j5JLd+wq4ye*js^_0$4&lYc^RB%Uu7CEHabz#Ya%tA$yFMT7tf=!Tz0 zcoilhl-zqRyyaDR>!j-35@+1X;8;`Em{c4TOLJABZGSgpytlb(C?U*EsIkJwpHP90 zt`!q!m=)tt6PyC=1vyb)fD&ek16!xKvXq`O*g+{}*#8YDVwmK>8KVwavDq}k1biTh zg;&Lt9=LDewDH^}Y|(^7guwpI$1AMH4SL|`j1Q6v^3u%kaQgAZb8lOlHNk=gdyMY? zT@@_9MDIw7+p(~ob+Q}RM%&_L)=p4!W&K^S)4SgZ(=O~`ior`NIO21ev3On6#adC{ z*_`6e36yV5VDwp8+i=g9Sx%qG$uQwLN_`04Owpv3X`)dC0x)Q^UWyvI`vF81wk_$x zNwI6_L7|Ls#R_Y6Bqw*x`{i#@7d|V_VD!*n`@!|ycdZ)FC?MBA>5ZvY(=BXHX=G?z zZ$6okEmnVMp z?1Zg-PCF``x!UokhB&&R#UVpsoyQB{esU$h)Y^Dv^1k?(+kG~Z_>!R3S zJefj6vNjqhd*;2&{99;dR-#21LMEOe!0vWtHUD|Z4{dI|)gcyNBcWSmHPEG6yZtxC zgWe=MVN14!a61(OP2q1`&L}hXb)xInelWFM{zO0%z6Y){?)7?dKLpluIS>R~z$oVk zqmI?s3;| zU?Bym0%A?qr^99%Lt+wzVKL@2I9}DE;K}Sfw_C}}JZpTVL1N;)S3|$qZn+BoI02hN zx*=6#K3fx#cLZm*eBqXwoH#x;aXRcqn| zc3`#bR&I~V88Wp50fC8A{mW6$d7aAiam`qwOnqa>F@_*#;&N>&WMIizQrst2*OLj= zXc6gzBA>&J3ccUj9X8?Oypq>)lA8vimFchG9;IrsrKV7)-4bF4f?({iX|Cn6-808_ zwBRE+IdIgQV{A8ac_A-VOFGkZRcZk*VhRMA4H-!3`NqaaxQVy&=!@{K57o)9sBZ@G zai3Wks5+s}YP47AA|xQIE1~*lQySo-(-EFpV_8W5vc7S6ZC2(M+?ncYBj<^O#-X{=T3^OI9eJ#8(lYTt)g~5N^%{ z1jt_KV8W~H`9<4pYmSy29Do8d@VSgCs^l#_bw;b<&>*)VH5@tf*hAEkxA-okve_hq z39b`T6gyHqeyr3N6;51xCGpqW)X+&|rihw668i~%%jzr^=M3EZ(@v?bLABxEvNpqC zy5{%E`T>|4LW50wAV_7)FbfG7Do8oZZtHLJOBu4#X;i`5ON?@09n_oDMYysfJrWVn z^Fmf@N&0SNYD#^6{2u|d7E9?)4JGxsw7J;w8gv8`yeP_$=eO(WIT7qKSu4c_f+uuRP1G z>N3hnduxB)#=E{!K;+}UHL43}*5xcl>y?WtNyyxP{X(hemUb~bd!6>CL`m6*5N+M>uvMoR7|qc*EY7!%=e492;$5 za##(#Qk8d)XgE3Mu3zaFT5ac-Fx)z;4u-0%Glwn5&H7hlUpl(gGZ`*dW;!e+V3rvi z4EoZBlO|Uq80Qq@ZtSZVTqy5K*77z081?k6EXcYMotE5ynJwOlq&`O0IDYCnnwAJ8 zL;$ZN^`vWQn{#dXn1PQ$Lr%iFxYbaB5M{#ikAHe(Ms&I|xK$1BR*kfWfWLKk_cV)b zapndwgMspl^fWlV13I{{RzZ0?k>lnfKpeR-?4 z_H!|l%w4;W1a-w}1nse$n(i&mp(%_sJ5ER(`c+HaLU@-ks-Ie&vI3bE2|sZF991iZ z%Ybv!8*z-B^rBLgw+N-!YhK*$Kz0^kk_T#4x|HqRv|}ZzoX;#X1ymV)4oyp)tatOY zWw!Jcnw!4noT|gV*)hoDC9t&VZY5MFlps8V(v zmB%gIj(s@pDFw^B9)VY_B$oG4I$M36%edSZ9R4G)`quW1;Yn`cGT!DCZ{SnOu6g3& z1$QH8(3G8$H+1a=J8Nz4vP+`9Pa#jWaeBs`tj5K!B4rFGP&G#lATX9-LO~hM!}3rdUI6itD>{q>M6=jPg60 z^dTiUsUwb2<#uOB2@+dHC_IJEeMTx&@btF23>uc^Fkdq}{{YiAEs<3;=vpysBx10n zdrG%bc^z+nTj?XL)<6Dx@#E zOh|}7h;;syPR;zN(RfxHK^%fbR4M?;U@+PJDwI;(U%b)062|U-dRc}T`VtQ{B+sbY zGX=OR?4DTjF!*0=&<5{^Vw+deF0ZG9WOj&rtc-3W<|p;X;ZkXrl3(h#7>J}0#uXU< z023VN`c-Rh5o#asjZdT`4JE{gV@c2|fDZ(oojIq*vRIodea2Z7nI@Azad@B#z9QD5 zvheM{n8WVmgWm(E>s|>Z^zvW5?((xUm)tuO{{ZW+LinR~9QUzV+JLBvWMv+wx9MJg zrdUa-Y7tt;!E?#<1CvR#mDtj4=xImdIcAJSeHfe$jokfz#l?!$2>*c!WUd3AM>9lUU-@cE6&A3>2^YZoXx($O7W!AV-C=bjsv zP_4mJ=}Om9CORHE0bLFM0E1>!&7^`{9PE4ppKjHJ+HRnX@JnSPkU7B5<&M9FdbIJB z=d_L);pZLDLvtgbKQ2!}NY|4nU~#!mJa(v>IaU*Z7iUq>{#27G^1Q+bbKDx}=5{f% zxp3XcI%Ac}{b>^RYaDV#seFHNeEF#DUAdaH6gr#lF8T*5J2(l`mCfQr_&QUFD5t zb=m_BzNVp`La`MG86P)%nzGwh37n*4kUEpzwmdDOi`iRHx*xe#{n{Sq*ZEg2BC?|k zvwAe$Nm&fiJUexL3~wN7Xfd8g%+=Le>6UtIdoAGeCGO41C)D;8d`O`;AC)_M)k!Yc zoJYX*uMVyztyinlvFcN!3VRhSVS->~;ij5b61Q=*<8QYB3e%oXA|MxncomPQ#P;|1 z5bpil?dHeFN5?g36y#ej)Ac<609v7Idp(5SbPRLdbHh|V zV@&Te{rOnA{AjOIoOvRmdGnVvn>v`hTV}}-l0aDevL@0o?_0s5y0lV~?uEd{4^dn; zk#TKpce3snUGshKQO1P3#mN^C*HVgU>@vS6e6C%0b&;z28;($gro;f(`eQLel_kkUQJI2GO ze45yb!Zy8Vn4P^;deqm@L158HQ-o}?=kE*}0B(t-tk;HeT)FHRfTJBTQ+Sf<(@nFH zzS9aM3z?H6dwLIFTE89Lo}FWHsD}}!lfRR|q5U&aQasRTs5?nB zxV~?+>S8m>TYyOa0BH04h^GQU$l|LZFQ{Bjt(2Q6z z-W49A3fqa04>mVlr|NKjLTWo^^20VV#|op=)Hk;hJ=}K4-z{ zuJnsNN-`&h%9qtn4t;$Ma+Bwfjd2OpPua+g(kkv9;2Z&6!5CedQk=S%37zRJfA zigC2|#bd_G!yw>D2H<%7Yum$CRbzB{m8rN&%yo?BX-RboTcJP2SDobC>JttZ8^^zD zh9xN~pj@C~$?MvbIk%94OS`slo}=2jmD1Gbdc6svl%=J-4gRxe$j@4(s1GUQW5O}{ zvFTSfB)2ILW>yMHH%;9t87-#NwFtE6qi#-JvJMAxQ&ws)ZS);D^GzkxZI+!TuLSKH z2<_ogLt~a;G61gfDB`oSirE+UXrqr`YOeN?X_`b<_R0WRV_)})an_}|8(c1XWc_RB zE7z50cd_i!q^YYRl1kFHSkow`ODe+?NaqTpCb2aUq$1)7(7VOYZwLh=QGf*xM_^j&R3`wM#n)soXfdvE z*K=jh`#g|8Kr3#z&lBD;nJfX!Q60nTbI0pjP>(dxm1k&``%zVGXWlk3`G-$>wO*OR z6}_PAcW^pb`H_gQe7(!gdgC>NM{q&G2Oy5dfISjDPVE@^wvpre(aK3bfdY%YVG=(v z?JgK7IUo)P=UG}Vq%WVV41Z$VWBY^oR9+yyHr8SkBug>C$6?(608hq%F*QA6?^C#QD{Y7! zs~Vh)3ZX~_00NeP3eCBf%##D|js;t~m&=a;{LNX^0o&*oM&m?TINxg99D1i*{uw`& zZUd2;fl7uOC7Wxa0DyR`xV~$J3G8Hp-kDjyV=s z)zsx!WK*aDNu!SUPoB-j6_DT#!njNAK3y3gmuQmO;R9rjgpA_5sFbteP6_EvHc)Ds zFWF{JGEqV5rAZ&swxM0ocUu`%q^c`3gs?t+!)(sM&>GYe9kReUc3+fdzpZDa4W(U4 zXE1L!&A?unTnrFu<3_Pu?JmtLC~Rl$j(M-5qctd99yM6<%FKP^Dn@^KADV|hDI6d3 zn%nS}o%VYdynmWmi6M{obI0e`@veSrHA1%W;mnQ^$m{-k*LwsjZ*2{_50w-z4_yry7YJCc(oaue!2JbKnfJ=g zSKd$bc45Z^R%JpM*IX1l#|=j_@{%sQ~zKDFny--M2i zMA1Gxauo1q=r)y?j1Xd>^Y4}DFF-t9Sto2O>1=}mC8>UIaBiw z?+)U+d+lb{9W>0c#?0xIO!80Cxhl1%+}qk4hMg4dkK|_TeLori*znG$1e$VNyoi#< zF|?c#DeK;FX*}5rcI{EoMF3c^xHh-YOdA1Q7W!wJ^rbtL5!_dn*P>|GA8FE{a&p^neznJo zj?y}C?{sL!#T+o~YW>F38puH)5y0*bAJ((0N(S9*ry1>BEbB2X<2JV!7Kb}g_eY=Z zo`0Dm@T)ZKK>}(J$UluX5%kHX)i-@aw9uB@{d&@AP3GH9ha+vwxIgyMsrI8Yk6}@1 zB&?G!@R{Yu{q|{eVbsg=lWMoZNvqRPUdDA^dym7So!l-b~F0-1ak%h(4`W(43!87Cu2-@;dYSaPEWTyM@a`o8@&b~n zX+`a|%c8{jQm|3z8yU~!Dw4Wnw`PsWf8))u>VKV3tu|26ym;(!S%-s)lu^$3x^1>G zL;d&9<^KTdt~$D37)T8E(fJq;}*FjXvi3IK-R%md43B_k(j_dRNQWJ*>so~M)br_cde zw=#K?A3?yYw-4pQ$G<^YlmJmh6aZIg;W>9{uOD@D`qvj-PlKQSKGXfv{Ob%#y*6z@ z+GkZ)?FL{V^vzJMC`QQRKst<{YV#y_B95hozqDNQhj09J)OAwv-d_;II+7wl(2w@G z$v@0hx~8m=DctDvp}i`TI1|F!~oK`qq`AX5GKq;;5`*0I`FgFAJZgB=1)zT9(MR;LMzU%ZcI=WVjX=)7Q`(z{7{hBg@Cl;_(cHOu&y$+p(vxw%&` zJR1()gn~~$opt5%<*dBUY-DdqmpnAEMWx(bLc1ZA&d_>*4l8hvCde=MP%l4~W$8A? zKMY&MMj7K)$JYk5uUioT>T8y)xu)Nly*0}ejPk*GV~%lJt~WqOs@FNHOd^IHoNk^m z{{WAS*Gf4-^r@23GLzQEO+jlIxVyf$=)^0j^dlIl>Mc8set-6$AIw!Pn4d#J+ThIw z{{Z=G5h)_$o`c(Oi z9(&@p&a9)bjDIs&RkPxD-K;mV0s5N8uUe15k3Ofy!ip%Z2nw3oUoKIT@}3PtMF4D6 za{R?p)bsSFTFd4|gVb?PUz%2K!-@csiYW=eC(?i^UcOu)?bfQ(q;2vudYWwj6j4P0 z70`G|@u^6inmqnr{<`PuZ^FkK$NjTE%vM#uhRrE_Xzt=&&&j|XlT+PJV1pz5bi^cU z>7V|$KZO>t?j)RTUcTn5#Qy+o)cn)_t};?T!>Ro%%R9=Karqjhlmnu~&V9)zCalzV zs$X6W{m4h^O-W7k66$HdF#`Q7InHEn_l;<$%WOTCx$P?8+}bpZa!jxP0HGR~ts5J9 z*xir!mMo*L(kmOpsTO^R5rqV=;V9)g>1alcoYJ3K1u^vVAK8g0qU?NQXUbN(UA zKA#vrdgW`fU%0-=+Ezkag{LN;yRxR#uULkjVcI(VgtRDml z%&jjF9oqfj^^^KmZF!6@5)> z4wL{?H&3zy>UgQDjqBx=JvpEXv{6L>6j4P06|dozajn7;L1y_zdJlTYt#1uB-w@gR zz70iBft3%59*L+;_G@Ifi2n9pm3ib2=luH9bokaLNYr6ba0OLo`&In69aTSfAN40G~yZsoP{95Y1NXo9l-hPs_T=HFPf zOPCk~(bu>&q~O#eF62gO$;8wn5Rv}Pao!pvMR^%Nc6JrnNI;2?v92e=@8%`sgQHAM zDt`pNL!ZWup3%0>2dxm3EcaG!n`2cQPxpb&YB)|NPxpmBYl9xKkKjl*ztboD4M9w7 zvHh1;vXdVxM$nV*!5{hwrMMWKzO^;K05j&R>As)3|rCW%ixL%T=Z>JP)t{qNm z#p+Fr3_D5vD`wlomauqw8_DETCf%hJ@>|mtN5rx3(PlWwQV9ITaGQ?MxjRQV&2`hH zs?$mIXEjN>b8U4l>bkAYt#oFcxF2-~MHRQ;U-=5n{{UR;maebBgF2nT^o)Nx-m+1o z(lVsmsGBT1;h6_-12nUB3`NZTW3^c9mp1r$+01r$+01x;x*w)b((2l~`GTnzJ2)|Q12pR7PT zDcpXQGj@w?aaU;_<+LK++BmbFss>MbwQO;4l@&f8^~?3IA?kWalewNbQsP49^9XYl0LcJhHCU7=4^ zW7v!n=Gs>$Y2=LYUH6BgF=)2n^&q$Ab68cTsZ(inX4K~D zPVb?w7c(dO!ib_bSGt6s!c|~jd}RLs?9|4XTSX+~XJV2Esq8;GxoH@&)AvYlKrZLr Kxi&{fQ~%i{Qe4dd literal 0 HcmV?d00001 diff --git a/packs/Icons/Portraits/Ki-Adi Codux.webp b/packs/Icons/Portraits/Ki-Adi Codux.webp new file mode 100644 index 0000000000000000000000000000000000000000..30014660fdd3861a89300de92bb3ac41b2808e43 GIT binary patch literal 37276 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 Stashed changes "itemDescription": { "description": { "value": "", @@ -398,8 +451,6 @@ }, "source": "" }, -<<<<<<< Updated upstream -======= "classDescription": { "className": "", "description": { @@ -409,7 +460,6 @@ "value": "" } }, ->>>>>>> Stashed changes "speciesDescription": { "data": "$characteristics-table", "description": { @@ -440,6 +490,7 @@ }, "target": { "value": null, + "width": null, "units": "", "type": "" }, @@ -452,6 +503,11 @@ "value": 0, "max": 0, "per": null + }, + "consume": { + "type": "", + "target": null, + "amount": null } }, "action": { @@ -470,6 +526,17 @@ "dc": null, "scaling": "power" } + }, + "mountable": { + "armor": { + "value": 10 + }, + "hp": { + "value": 0, + "max": 0, + "dt": null, + "conditions": "" + } } }, "archetype": { @@ -505,31 +572,28 @@ }, "powercasting": "none" }, -<<<<<<< Updated upstream -======= "classfeature": { "templates": ["itemDescription", "activatedEffect", "action"], "className": "" }, ->>>>>>> Stashed changes "consumable": { "templates": ["itemDescription", "physicalItem", "activatedEffect", "action"], "consumableType": "potion", "uses": { - "value": 0, - "max": 0, - "per": null, - "autoUse": true, - "autoDestroy": true + "autoDestroy": false } }, "equipment": { - "templates": ["itemDescription", "physicalItem", "activatedEffect", "action"], + "templates": ["itemDescription", "physicalItem", "activatedEffect", "action", "mountable"], "armor": { "type": "light", "value": 10, "dex": null }, + "speed": { + "value": null, + "conditions": "" + }, "strength": 0, "stealth": false, "proficient": true @@ -547,10 +611,6 @@ }, "species": { "templates": ["speciesDescription"] -<<<<<<< Updated upstream - -======= ->>>>>>> Stashed changes }, "tool": { "templates": ["itemDescription", "physicalItem"], @@ -581,13 +641,13 @@ "prepared": false }, "scaling": { - "mode": null, + "mode": "none", "formula": null } }, "weapon": { - "templates": ["itemDescription", "physicalItem" ,"activatedEffect", "action"], - "weaponType": "simpleM", + "templates": ["itemDescription", "physicalItem" ,"activatedEffect", "action", "mountable"], + "weaponType": "simpleVW", "properties": {}, "proficient": true } diff --git a/template.json.bak b/template.json.bak deleted file mode 100644 index a8b4f38d..00000000 --- a/template.json.bak +++ /dev/null @@ -1,488 +0,0 @@ -{ - "Actor": { - "types": ["character", "npc"], - "templates": { - "common": { - "abilities": { - "str": { - "value": 10, - "proficient": 0 - }, - "dex": { - "value": 10, - "proficient": 0 - }, - "con": { - "value": 10, - "proficient": 0 - }, - "int": { - "value": 10, - "proficient": 0 - }, - "wis": { - "value": 10, - "proficient": 0 - }, - "cha": { - "value": 10, - "proficient": 0 - } - }, - "attributes": { - "ac": { - "min": 0 - }, - "hp": { - "value": 10, - "min": 0, - "max": 10, - "temp": 0, - "tempmax": 0 - }, - "init": { - "value": 0, - "bonus": 0 - }, - "speed": { - "value": "30 ft", - "special": "" - }, - "powercasting": "int" - }, - "details": { - "alignment": "", - "biography": { - "value": "", - "public": "" - }, - "race": "" - }, - "skills": { - "acr": { - "value": 0, - "ability": "dex" - }, - "ani": { - "value": 0, - "ability": "wis" - }, - "ath": { - "value": 0, - "ability": "str" - }, - "dec": { - "value": 0, - "ability": "cha" - }, - "ins": { - "value": 0, - "ability": "wis" - }, - "itm": { - "value": 0, - "ability": "cha" - }, - "inv": { - "value": 0, - "ability": "int" - }, - "lor": { - "value": 0, - "ability": "int" - }, - "med": { - "value": 0, - "ability": "wis" - }, - "nat": { - "value": 0, - "ability": "int" - }, - "pil": { - "value": 0, - "ability": "int" - }, - "prc": { - "value": 0, - "ability": "wis" - }, - "prf": { - "value": 0, - "ability": "cha" - }, - "per": { - "value": 0, - "ability": "cha" - }, - "slt": { - "value": 0, - "ability": "dex" - }, - "ste": { - "value": 0, - "ability": "dex" - }, - "sur": { - "value": 0, - "ability": "wis" - }, - "tec": { - "value": 0, - "ability": "int" - } - }, - "traits": { - "size": "med", - "senses": "", - "languages": { - "value": [], - "custom": "" - }, - "di": { - "value": [], - "custom": "" - }, - "dr": { - "value": [], - "custom": "" - }, - "dv": { - "value": [], - "custom": "" - }, - "ci": { - "value": [], - "custom": "" - } - }, - "currency": { - "gc": 0 - }, - "powers": { - "power1": { - "value": 0, - "max": 0, - "override": null - }, - "power2": { - "value": 0, - "max": 0, - "override": null - }, - "power3": { - "value": 0, - "max": 0, - "override": null - }, - "power4": { - "value": 0, - "max": 0, - "override": null - }, - "power5": { - "value": 0, - "max": 0, - "override": null - }, - "power6": { - "value": 0, - "max": 0, - "override": null - }, - "power7": { - "value": 0, - "max": 0, - "override": null - }, - "power8": { - "value": 0, - "max": 0, - "override": null - }, - "power9": { - "value": 0, - "max": 0, - "override": null - }, - "pact": { - "value": 0, - "max": 0, - "override": null - } - }, - "bonuses": { - "mwak": { - "attack": "", - "damage": "" - }, - "rwak": { - "attack": "", - "damage": "" - }, - "mpak": { - "attack": "", - "damage": "" - }, - "rpak": { - "attack": "", - "damage": "" - }, - "abilities": { - "check": "", - "save": "", - "skill": "" - }, - "power": { - "dc": "" - } - } - } - }, - "character": { - "templates": ["common"], - "attributes": { - "death": { - "success": 0, - "failure": 0 - }, - "exhaustion": 0, - "inspiration": 0 - }, - "details": { - "background": "", - "xp": { - "value": 0, - "min": 0, - "max": 300 - }, - "trait": "", - "ideal": "", - "bond": "", - "flaw": "" - }, - "resources": { - "primary": { - "value": 0, - "max": 0, - "sr": 0, - "lr": 0 - }, - "secondary": { - "value": 0, - "max": 0, - "sr": 0, - "lr": 0 - }, - "tertiary": { - "value": 0, - "max": 0, - "sr": 0, - "lr": 0 - } - }, - "traits": { - "weaponProf": { - "value": [], - "custom": "" - }, - "armorProf": { - "value": [], - "custom": "" - }, - "toolProf": { - "value": [], - "custom": "" - } - } - }, - "npc": { - "templates": ["common"], - "details": { - "type": "", - "environment": "", - "cr": 1, - "powerLevel": 0, - "xp": { - "value": 10 - }, - "source": "" - }, - "resources": { - "legact": { - "value": 0, - "max": 0 - }, - "legres": { - "value": 0, - "max": 0 - }, - "lair": { - "value": 0, - "initiative": 0 - } - } - } - }, - "Item": { - "types": ["weapon", "equipment", "consumable", "tool", "loot", "class", "power", "feat", "backpack"], - "templates": { - "itemDescription": { - "description": { - "value": "", - "chat": "", - "unidentified": "" - }, - "source": "" - }, - "physicalItem": { - "quantity": 1, - "weight": 0, - "price": 0, - "attuned": false, - "equipped": false, - "rarity": "", - "identified": true - }, - "activatedEffect": { - "activation": { - "type": "", - "cost": 0, - "condition": "" - }, - "duration": { - "value": null, - "units": "" - }, - "target": { - "value": null, - "units": "", - "type": "" - }, - "range": { - "value": null, - "long": null, - "units": "" - }, - "uses": { - "value": 0, - "max": 0, - "per": null - } - }, - "action": { - "ability": null, - "actionType": null, - "attackBonus": 0, - "chatFlavor": "", - "critical": null, - "damage": { - "parts": [], - "versatile": "" - }, - "formula": "", - "save": { - "ability": "", - "dc": null, - "scaling": "power" - } - } - }, - "backpack": { - "templates": ["itemDescription", "physicalItem"], - "capacity": { - "type": "weight", - "value": 0, - "weightless": false - }, - "currency": { - "gc": 0 - } - }, - "class": { - "templates": ["itemDescription"], - "levels": 1, - "subclass": "", - "hitDice": "d6", - "hitDiceUsed": 0, - "skills": { - "number": 2, - "choices": [], - "value": [] - }, - "powercasting": "none" - }, - "consumable": { - "templates": ["itemDescription", "physicalItem", "activatedEffect", "action"], - "consumableType": "potion", - "uses": { - "value": 0, - "max": 0, - "per": null, - "autoUse": true, - "autoDestroy": true - } - }, - "equipment": { - "templates": ["itemDescription", "physicalItem", "activatedEffect", "action"], - "armor": { - "type": "light", - "value": 10, - "dex": null - }, - "strength": 0, - "stealth": false, - "proficient": true - }, - "feat": { - "templates": ["itemDescription", "activatedEffect", "action"], - "requirements": "", - "recharge": { - "value": null, - "charged": false - } - }, - "loot": { - "templates": ["itemDescription", "physicalItem"] - }, - "tool": { - "templates": ["itemDescription", "physicalItem"], - "ability": "int", - "chatFlavor": "", - "proficient": 0 - }, - "power": { - "templates": ["itemDescription", "activatedEffect", "action"], - "level": 1, - "school": "", - "components": { - "value": "", - "vocal": false, - "somatic": false, - "material": false, - "ritual": false, - "concentration": false - }, - "materials": { - "value": "", - "consumed": false, - "cost": 0, - "supply": 0 - }, - "preparation": { - "mode": null, - "prepared": false - }, - "scaling": { - "mode": null, - "formula": null - } - }, - "weapon": { - "templates": ["itemDescription", "physicalItem" ,"activatedEffect", "action"], - "weaponType": "simpleM", - "properties": {}, - "proficient": true - } - } -} diff --git a/templates/actors/character-sheet.html b/templates/actors/character-sheet.html index 528b0c9e..cc9003be 100644 --- a/templates/actors/character-sheet.html +++ b/templates/actors/character-sheet.html @@ -4,31 +4,33 @@
-
{{!-- NPC Sheet Navigation --}} @@ -101,6 +114,7 @@ {{ localize "SW5E.Inventory" }} {{ localize "SW5E.Features" }} {{ localize "SW5E.Powerbook" }} + {{ localize "SW5E.Effects" }} {{ localize "SW5E.Biography" }} @@ -114,11 +128,11 @@ {{#each data.abilities as |ability id|}}
  • {{ability.label}}

    - +
    {{numberFormat ability.mod decimals=0 sign=true}} - {{{ability.icon}}} + {{{ability.icon}}} {{numberFormat ability.save decimals=0 sign=true}}
  • @@ -133,13 +147,13 @@ {{{skill.icon}}}

    {{skill.label}}

    {{skill.ability}} - {{numberFormat skill.mod decimals=0 sign=true}} + {{numberFormat skill.total decimals=0 sign=true}} ({{skill.passive}}) {{/each}} -
    +
    {{!-- Body Attributes --}}
      @@ -149,58 +163,44 @@ -
      - - / - -
      -
      -
      + {{/each}} - -
    • -

      {{ localize "SW5E.Initiative" }}

      -
      - {{numberFormat data.attributes.init.total decimals=0 sign=true}} -
      -
      - {{ localize "SW5E.Modifier" }} - -
      -
    {{!-- Counters --}} -
    +

    {{ localize "SW5E.DeathSave" }}

    - -
    -
    +

    {{ localize "SW5E.Exhaustion" }}

    -
    -
    +

    {{ localize "SW5E.Inspiration" }}

    {{> "systems/sw5e/templates/actors/parts/actor-powerbook.html"}}
    - + + {{!-- Effects Tab --}} +
    + {{> "systems/sw5e/templates/actors/parts/actor-effects.html"}} +
    + {{!-- Biography Tab --}} -
    - {{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}} +
    diff --git a/templates/actors/limited-sheet.html b/templates/actors/limited-sheet.html index a924f8ef..3ece51c7 100644 --- a/templates/actors/limited-sheet.html +++ b/templates/actors/limited-sheet.html @@ -1,21 +1,20 @@ -
    + - -
    -

    - -

    + {{!-- Sheet Header --}} +
    + + +
    +

    + +

    +
    - + {{!-- Sheet Body --}}
    {{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
    - - -
    - -
    diff --git a/templates/actors/npc-sheet.html b/templates/actors/npc-sheet.html index 01a377f7..6644268e 100644 --- a/templates/actors/npc-sheet.html +++ b/templates/actors/npc-sheet.html @@ -4,33 +4,76 @@
    -
    +

    -
    -
    +
    + -
      + {{!-- Character Summary --}} +
      • - + {{lookup config.actorSizes data.traits.size}}
      • +
      • + +
      -
    + + {{!-- Header Attributes --}} +
      +
    • +

      {{ localize "SW5E.Health" }}

      +
      + + / + +
      +
      + +
      +
    • + +
    • +

      {{ localize "SW5E.ArmorClass" }}

      +
      + +
      +
      + {{ localize "SW5E.Proficiency" }} + {{numberFormat data.attributes.prof decimals=0 sign=true}} +
      +
    • + +
    • +

      {{ localize "SW5E.Speed" }}

      +
      + +
      +
      + +
      +
    • +
    +
    {{!-- NPC Sheet Navigation --}} @@ -50,11 +93,11 @@ {{#each data.abilities as |ability id|}}
  • {{ability.label}}

    - +
    {{numberFormat ability.mod decimals=0 sign=true}} - {{{ability.icon}}} + {{{ability.icon}}} {{numberFormat ability.save decimals=0 sign=true}}
  • @@ -69,76 +112,30 @@ {{{skill.icon}}}

    {{skill.label}}

    {{skill.ability}} - {{numberFormat skill.mod decimals=0 sign=true}} + {{numberFormat skill.total decimals=0 sign=true}} ({{skill.passive}}) {{/each}} -
    - - {{!-- Attributes --}} -
      -
    • -

      {{ localize "SW5E.Health" }}

      -
      - - / - -
      -
      - -
      -
    • - -
    • -

      {{ localize "SW5E.ArmorClass" }}

      -
      - -
      -
      - {{ localize "SW5E.Proficiency" }} - {{numberFormat data.attributes.prof decimals=0 sign=true}} -
      -
    • - -
    • -

      {{ localize "SW5E.Speed" }}

      -
      - -
      -
      - -
      -
    • -
    +
    {{!-- Legendary Actions --}} -
    +

    {{ localize "SW5E.LegAct" }}

    - + / - +

    {{ localize "SW5E.LegRes" }}

    - + / - +
    @@ -146,9 +143,8 @@
    - -
    + +
    diff --git a/templates/actors/parts/actor-inventory.html b/templates/actors/parts/actor-inventory.html index f9b81c02..64bd4dde 100644 --- a/templates/actors/parts/actor-inventory.html +++ b/templates/actors/parts/actor-inventory.html @@ -1,6 +1,6 @@ -
    +
    - {{#if isCharacter}} + {{#unless isNPC}}

      {{localize "SW5E.Currency"}} @@ -11,15 +11,16 @@ {{/each}}

    - {{/if}} + {{/unless}} + {{#unless isVehicle}}
      -
    • {{localize "SW5E.Filter"}}
    • {{localize "SW5E.Action"}}
    • {{localize "SW5E.BonusAction"}}
    • {{localize "SW5E.Reaction"}}
    • {{localize "SW5E.Equipped"}}
    + {{/unless}}
      @@ -27,16 +28,23 @@
    1. {{localize section.label}}

      + {{#if section.columns}} + {{#each section.columns}} +
      {{label}}
      + {{/each}} + {{else}} {{#if ../isCharacter}}
      {{localize "SW5E.Weight"}}
      {{/if}}
      {{localize "SW5E.Charges"}}
      {{localize "SW5E.Usage"}}
      + {{/if}} {{#if ../owner}} @@ -45,16 +53,35 @@
        {{#each section.items as |item iid|}} -
      1. -
        -
        +
      2. +
        + {{#if section.editableName}} + + {{else}} +

        {{item.name~}} {{~#if item.isStack}} ({{item.data.quantity}}){{/if}} {{~#if item.data.attuned}} {{/if}}

        + {{/if}}
        + {{#if section.columns}} + {{#each section.columns}} +
        + {{#with (getProperty item property)}} + {{#if ../editable}} + + {{else}} + {{this}} + {{/if}} + {{/with}} +
        + {{/each}} + {{else}} {{#if ../../isCharacter}}
        {{#if item.totalWeight}} @@ -77,11 +104,16 @@ {{item.labels.activation}} {{/if}}
        + {{/if}} {{#if ../../owner}}
        + {{#unless @root.isVehicle}} + {{/unless}} + {{#unless section.editableName}} + {{/unless}}
        {{/if}} @@ -92,7 +124,7 @@
      -{{#if isCharacter }} +{{#unless isNPC}} {{#with data.attributes.encumbrance}}
      @@ -103,4 +135,4 @@
      {{/with}} -{{/if}} +{{/unless}} diff --git a/templates/actors/parts/actor-powerbook.html b/templates/actors/parts/actor-powerbook.html index dbd05381..1ed728bb 100644 --- a/templates/actors/parts/actor-powerbook.html +++ b/templates/actors/parts/actor-powerbook.html @@ -1,6 +1,12 @@ -
      +
      -

      {{localize "SW5E.PowerAbility"}}

      + {{#unless isNPC}} + + {{else}} + + + {{/unless}} - {{#if isNPC}} -

      {{localize "SW5E.PowercasterLevel"}}

      - - {{/if}} -

      {{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}

        -
      • {{localize "SW5E.Filter"}}
      • {{localize "SW5E.Action"}}
      • {{localize "SW5E.BonusAction"}}
      • {{localize "SW5E.Reaction"}}
      • @@ -48,7 +47,7 @@ {{/if}} {{ else }} - {{{section.uses}}} + {{{section.uses}}} / {{{section.slots}}} {{/if}} @@ -71,9 +70,9 @@ {{#each section.powers as |item i|}}
      • -
        +

        {{item.name}}

        - {{#if item.data.uses.value }} + {{#if item.data.uses.per }}
        Uses {{item.data.uses.value}} / {{item.data.uses.max}}
        {{/if}}
        diff --git a/templates/actors/parts/actor-traits.html b/templates/actors/parts/actor-traits.html index 9b3a337d..7bf70446 100644 --- a/templates/actors/parts/actor-traits.html +++ b/templates/actors/parts/actor-traits.html @@ -10,95 +10,115 @@
      + {{#unless isVehicle}}
      - + + + +
        {{#each data.traits.languages.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      + {{/unless}}
      - + + + +
        {{#each data.traits.di.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      - + + + +
        {{#each data.traits.dr.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      - + + + +
        {{#each data.traits.dv.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      - + + + +
        {{#each data.traits.ci.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      {{#if isCharacter}}
      - + + + +
        {{#each data.traits.weaponProf.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      - + + + +
        {{#each data.traits.armorProf.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      - + + + +
        {{#each data.traits.toolProf.selected as |v k|}}
      • {{v}}
      • {{/each}}
      -
      {{/if}} + {{#unless isVehicle}}
      -
      \ No newline at end of file + {{/unless}} +
    diff --git a/templates/actors/vehicle-sheet.html b/templates/actors/vehicle-sheet.html new file mode 100644 index 00000000..52e5d2dd --- /dev/null +++ b/templates/actors/vehicle-sheet.html @@ -0,0 +1,159 @@ +
    +
    + {{actor.name}} +
    +

    + +

    +
      +
    • + {{lookup config.actorSizes data.traits.size}} +
    • +
    • + {{localize 'SW5E.Vehicle'}} +
    • +
    • + +
    • +
    • + +
    • +
    +
      +
    • +

      {{localize 'SW5E.Health'}}

      +
      + + / + +
      +
      + + +
      +
    • +
    • +

      {{localize 'SW5E.ArmorClass'}}

      +
      + +
      +
      + +
      +
    • +
    • +

      {{localize 'SW5E.Speed'}}

      +
      + +
      +
    • +
    +
    +
    + + + +
    +
    +
      + {{#each data.abilities as |ability id|}} +
    • +

      {{ability.label}}

      + +
      + + {{numberFormat ability.mod decimals=0 sign=true}} + +
      +
    • + {{/each}} +
    +
    +
    +
    +

    {{localize 'SW5E.VehicleCreatureCapacity'}}

    +
    + +
    +
    +
    +

    {{localize 'SW5E.VehicleCargoCapacity'}}

    +
    + +
    +
    +
    +

    {{localize 'SW5E.VehicleActionStations'}}

    +
    + +
    +
    +
    +

    {{localize 'SW5E.ActionPl'}}

    +
    + +
    +
    +
    +

    {{localize 'SW5E.VehicleActionThresholds'}}

    +
    + < + + < + + < + +
    +
    +
    + {{> 'systems/sw5e/templates/actors/parts/actor-traits.html'}} +
    +
    + +
    + {{> 'systems/sw5e/templates/actors/parts/actor-features.html' sections=features}} +
    + +
    + {{> 'systems/sw5e/templates/actors/parts/actor-inventory.html' sections=cargo}} +
    + +
    + {{editor content=data.details.biography.value target='data.details.biography.value' + button=true owner=owner editable=editable}} +
    +
    +
    diff --git a/templates/apps/ability-use.html b/templates/apps/ability-use.html index 2c41ce6a..870bec73 100644 --- a/templates/apps/ability-use.html +++ b/templates/apps/ability-use.html @@ -1,26 +1,41 @@
    -

    {{ localize "SW5E.AbilityUseHint" }} {{item.name}} {{ localize "SW5E.Ability" }}.

    +

    {{ title }}

    +

    {{note}}

    + {{#each errors}} +

    {{localize this}}

    + {{/each}} -

    - {{#if recharges}} - {{ localize "SW5E.AbilityUseRechargeHint" }} {{#if isCharged}}{{ localize "SW5E.AbilityUseCharged" }}{{else}}{{ localize "SW5E.AbilityUseDepleted" }}{{/if}}.

    - {{else}} - {{ localize "SW5E.AbilityUseWarnStart" }} {{uses.value}} {{ localize "SW5E.of" }} {{uses.max}} {{ localize "SW5E.AbilityUseWarnEnd" }} {{perLabel}}. - {{/if}} - {{#unless canUse}} - {{ localize "SW5E.AbilityUseCantUse" }} - {{/unless}} -

    + {{#if canUpcast}} +
    + +
    + +
    +
    - - - {{#if hasPlaceableTemplate}} -
    - -
    - {{/if}} +
    + {{/if}} + + {{#if hasLimitedUses}} +
    + +
    + {{/if}} + + {{#if hasPlaceableTemplate}} +
    + +
    + {{/if}}
    diff --git a/templates/apps/actor-flags.html b/templates/apps/actor-flags.html index 49c01345..2692bf90 100644 --- a/templates/apps/actor-flags.html +++ b/templates/apps/actor-flags.html @@ -24,7 +24,7 @@ {{/if}} -

    {{flag.hint}}

    +

    {{localize flag.hint}}

    {{/each}} {{/each}} diff --git a/templates/apps/long-rest.html b/templates/apps/long-rest.html new file mode 100644 index 00000000..464204a6 --- /dev/null +++ b/templates/apps/long-rest.html @@ -0,0 +1,20 @@ +
    +

    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 power points.

    + + {{#if promptNewDay}} +
    + + +

    Recover limited use abilities which recharge "per day"?

    +
    + {{/if}} + +
    + {{#each buttons as |button id|}} + + {{/each}} +
    +
    diff --git a/templates/apps/short-rest.html b/templates/apps/short-rest.html index 40122fb0..f9f91c5f 100644 --- a/templates/apps/short-rest.html +++ b/templates/apps/short-rest.html @@ -18,6 +18,15 @@

    {{ localize "SW5E.ShortRestNoHD" }}

    {{/unless}} + + {{#if promptNewDay}} +
    + + +

    Recover limited use abilities which recharge "per day"?

    +
    + {{/if}} +
    {{#each buttons as |button id|}} diff --git a/templates/chat/item-card.html b/templates/chat/item-card.html index 2f0db698..faafcceb 100644 --- a/templates/chat/item-card.html +++ b/templates/chat/item-card.html @@ -28,7 +28,7 @@ {{#if hasSave}} {{/if}} diff --git a/templates/items/backpack.html b/templates/items/backpack.html index aaec44e6..dbe7b66d 100644 --- a/templates/items/backpack.html +++ b/templates/items/backpack.html @@ -14,7 +14,7 @@ {{itemStatus}}
    -
      +
      • diff --git a/templates/items/classfeature.html b/templates/items/classfeature.html new file mode 100644 index 00000000..02bec146 --- /dev/null +++ b/templates/items/classfeature.html @@ -0,0 +1,73 @@ +
        + + {{!-- Item Sheet Header --}} +
        + + +
        +

        + +

        + +
        +

        {{itemType}}

        + {{itemStatus}} +
        + +
          +
        • + {{labels.featType}} +
        • +
        • + +
        • +
        • + +
        • +
        +
        +
        + + {{!-- Item Sheet Navigation --}} + + + {{!-- Item Sheet Body --}} +
        + + {{!-- Description Tab --}} + {{> "systems/sw5e/templates/items/parts/item-description.html"}} + + {{!-- Details Tab --}} +
        + +

        {{ localize "SW5E.FeatureUsage" }}

        + + {{!-- Item Activation Template --}} + {{> "systems/sw5e/templates/items/parts/item-activation.html"}} + + {{!-- Recharge Requirement --}} + {{#if data.activation.type}} +
        + +
        + {{ localize "SW5E.FeatureRechargeOn" }} + + +
        +
        + {{/if}} + +

        {{ localize "SW5E.FeatureAttack" }}

        + + {{!-- Item Action Template --}} + {{> "systems/sw5e/templates/items/parts/item-action.html"}} +
        +
        +
        diff --git a/templates/items/equipment.html b/templates/items/equipment.html index e4d7d980..42415b85 100644 --- a/templates/items/equipment.html +++ b/templates/items/equipment.html @@ -14,7 +14,7 @@ {{itemStatus}} -
          +
          • {{lookup config.equipmentTypes data.armor.type }}
          • @@ -57,6 +57,7 @@ + {{#unless isMountable}} {{!-- Equipment Status --}}
            @@ -73,33 +74,17 @@ {{ localize "SW5E.Attuned" }}
            - -{{#unless isWeapon }} -{{!-- ArmorProperties Formula --}} -
            -

            - {{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}} - -

            -
              - {{#each data.armorproperties.parts as |part i| }} -
            1. - - - - -
            2. - {{/each}} -
            -
            -{{/unless}} + {{/unless}} + + {{!-- Armor Properties --}} +
            + + {{#each config.armorPropertiesTypes as |name prop|}} + + {{/each}} +
            {{!-- Armor Class --}}
            @@ -109,6 +94,7 @@
            + {{#unless isMountable}} {{!-- Dexterity Modifier --}}
            @@ -130,6 +116,21 @@
            + {{/unless}} + + {{#if isMountable}} + {{> 'systems/sw5e/templates/items/parts/item-mountable.html'}} +
            + +
            + + {{localize 'SW5E.FeetAbbr'}} + +
            +
            + {{/if}}

            {{ localize "SW5E.ItemEquipmentUsage" }}

            diff --git a/templates/items/feat.html b/templates/items/feat.html index 02bec146..b5eb2ce7 100644 --- a/templates/items/feat.html +++ b/templates/items/feat.html @@ -14,7 +14,7 @@ {{itemStatus}} -
              +
              • {{labels.featType}}
              • diff --git a/templates/items/loot.html b/templates/items/loot.html index 57b978a1..04604e15 100644 --- a/templates/items/loot.html +++ b/templates/items/loot.html @@ -14,7 +14,7 @@ {{itemStatus}} -
                  +
                  • diff --git a/templates/items/parts/item-activation.html b/templates/items/parts/item-activation.html index 9663d17d..0afc2d24 100644 --- a/templates/items/parts/item-activation.html +++ b/templates/items/parts/item-activation.html @@ -23,6 +23,21 @@ +{{#if isCrewed}} +
                    + +
                    + +
                    +
                    +{{/if}} {{!-- Ability Target --}}
                    @@ -48,6 +63,16 @@
                    +{{!-- Ability Target Width --}} +{{#if isLine}} +
                    + +
                    + +
                    +
                    +{{/if}} + {{!-- Ability Range --}}
                    @@ -99,4 +124,28 @@
                    -{{/if}} \ No newline at end of file + +{{!-- Consumption --}} +
                    + +
                    + + + +
                    +
                    +{{/if}} diff --git a/templates/items/parts/item-mountable.html b/templates/items/parts/item-mountable.html new file mode 100644 index 00000000..38fe444f --- /dev/null +++ b/templates/items/parts/item-mountable.html @@ -0,0 +1,19 @@ +
                    + +
                    + + / + + +
                    +
                    + +
                    + +
                    + +
                    +
                    diff --git a/templates/items/power.html b/templates/items/power.html index 6b38d74f..1c788303 100644 --- a/templates/items/power.html +++ b/templates/items/power.html @@ -14,7 +14,7 @@ {{itemStatus}} -
                      +
                      • {{labels.level}}
                      • @@ -68,12 +68,29 @@ + {{!-- Preparation Mode --}} +
                        + +
                        + + +
                        +
                        {{!-- Concentration Mode --}}
                        -
                        diff --git a/templates/items/tool.html b/templates/items/tool.html index 25d107c7..6a2c36f2 100644 --- a/templates/items/tool.html +++ b/templates/items/tool.html @@ -14,7 +14,7 @@ {{itemStatus}} -
                          +
                          • diff --git a/templates/items/weapon.html b/templates/items/weapon.html index c17664bd..57c9f6ca 100644 --- a/templates/items/weapon.html +++ b/templates/items/weapon.html @@ -14,7 +14,7 @@ {{itemStatus}} -
                              +
                              • {{lookup config.weaponTypes data.weaponType }}
                              • @@ -56,24 +56,27 @@ + {{#unless isMountable}} {{!-- Weapon Status --}}
                                - - - - +
                                + + + + +
                                + {{/unless}} - {{#if sss}} {{!-- Weapon Properties --}}
                                @@ -81,37 +84,21 @@ + {{/each}}
                                - {{/if}} -{{#if isWeapon }} -
                                -{{!-- weaponproperties Formula --}} -

                                - {{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}} - -

                                -
                                  - {{#each data.weaponproperties.parts as |part i| }} -
                                1. - - - - -
                                2. - {{/each}} -
                                -{{/if}} + {{#if isMountable}} +
                                + +
                                + +
                                +
                                -
                                + {{> 'systems/sw5e/templates/items/parts/item-mountable.html'}} + {{/if}}

                                {{ localize "SW5E.ItemWeaponUsage" }}

                                diff --git a/ui/parchment.jpg b/ui/parchment.jpg deleted file mode 100644 index 432c794acb3fe94dedea920ff230c07c1e2381fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37349 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