forked from GitHub-Mirrors/foundry-sw5e
Super VJ Update
This commit is contained in:
parent
442212bdea
commit
1983b74bde
59 changed files with 4640 additions and 2462 deletions
193
lang/en.json
193
lang/en.json
|
@ -5,25 +5,41 @@
|
|||
"SW5E.AbbreviationConc": "Conc.",
|
||||
"SW5E.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 Spell",
|
||||
"SW5E.AbilityUseUse": "Use Ability",
|
||||
|
||||
"SW5E.Action": "Action",
|
||||
"SW5E.ActionPl": "Actions",
|
||||
"SW5E.ActionAbil": "Ability Check",
|
||||
"SW5E.ActionHeal": "Healing",
|
||||
"SW5E.ActionMPAK": "Melee Power Attack",
|
||||
|
@ -33,6 +49,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,9 +62,10 @@
|
|||
"SW5E.AlignmentLN": "Lawful Neutral",
|
||||
"SW5E.AlignmentND": "Neutral Dark",
|
||||
"SW5E.AlignmentNL": "Neutral Light",
|
||||
"SW5E.AlignmentBN": "Balenced Neutral",
|
||||
"SW5E.AlignmentBN": "Balanced Neutral",
|
||||
"SW5E.Archetypes": "Archetypes",
|
||||
"SW5E.ArmorClass": "Armor Class",
|
||||
"SW5E.AC": "AC",
|
||||
"SW5E.ArmorProperties": "Armor Properties",
|
||||
"SW5E.ArmorProperAbsorptive": "Absorptive",
|
||||
"SW5E.ArmorProperAgile": "Agile",
|
||||
|
@ -76,6 +95,8 @@
|
|||
"SW5E.ArmorProperSteadfast": "Steadfast",
|
||||
"SW5E.ArmorProperVersatile": "Versatile",
|
||||
"SW5E.Attack": "Attack",
|
||||
"SW5E.AttackPl": "Attacks",
|
||||
"SW5E.AttackRoll": "Attack Roll",
|
||||
"SW5E.Attributes": "Attributes",
|
||||
"SW5E.Attuned": "Attuned",
|
||||
"SW5E.Background": "Background",
|
||||
|
@ -84,12 +105,12 @@
|
|||
"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 +137,7 @@
|
|||
"SW5E.ConBlinded": "Blinded",
|
||||
"SW5E.ConCharmed": "Charmed",
|
||||
"SW5E.ConDeafened": "Deafened",
|
||||
"SW5E.ConDiseased": "Diseased",
|
||||
"SW5E.ConExhaustion": "Exhaustion",
|
||||
"SW5E.ConFrightened": "Frightened",
|
||||
"SW5E.ConGrappled": "Grappled",
|
||||
|
@ -128,9 +150,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",
|
||||
|
@ -139,8 +174,17 @@
|
|||
"SW5E.ConsumableTrinket": "Trinket",
|
||||
"SW5E.ConsumableTechnology": "Technology",
|
||||
"SW5E.ConsumableAmmunition": "Ammunition",
|
||||
"SW5E.ConsumableUseWarnStart": "This consumable has",
|
||||
"SW5E.ConsumableUseWarnEnd": "of the current unit",
|
||||
"SW5E.ConsumableUnitWarn": "units remaining",
|
||||
"SW5E.ConsumableLastChargeWarn": "This is the last charge of this unit and consuming it will also reduce the item's quantity by 1",
|
||||
"SW5E.ConsumableWithoutCharges": "available units to use",
|
||||
"SW5E.Consumed": "Consumed",
|
||||
"SW5E.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",
|
||||
|
@ -160,15 +204,22 @@
|
|||
"SW5E.DamageKinetic": "Kinetic",
|
||||
"SW5E.DamageLightning": "Lightning",
|
||||
"SW5E.DamageNecrotic": "Necrotic",
|
||||
"SW5E.DamagePhysical": "Physical",
|
||||
"SW5E.DamagePoison": "Poison",
|
||||
"SW5E.DamagePsychic": "Psychic",
|
||||
"SW5E.DamageSonic": "Sonic",
|
||||
"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",
|
||||
|
@ -185,6 +236,7 @@
|
|||
"SW5E.EquipmentShield": "Shield",
|
||||
"SW5E.EquipmentShieldProficiency": "Shields",
|
||||
"SW5E.EquipmentTrinket": "Trinket",
|
||||
"SW5E.EquipmentVehicle": "Vehicle Equipment",
|
||||
"SW5E.Equipped": "Equipped",
|
||||
"SW5E.Exhaustion": "Exhaustion",
|
||||
"SW5E.Expertise": "Expertise",
|
||||
|
@ -208,6 +260,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",
|
||||
|
@ -217,12 +270,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",
|
||||
|
@ -230,9 +308,12 @@
|
|||
"SW5E.Healing": "Healing",
|
||||
"SW5E.HealingTemp": "Healing (Temporary)",
|
||||
"SW5E.Health": "Health",
|
||||
"SW5E.HealthConditions": "Health Conditions",
|
||||
"SW5E.HealthFormula": "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.Identified": "Identified",
|
||||
"SW5E.Initiative": "Initiative",
|
||||
"SW5E.Inspiration": "Inspiration",
|
||||
|
@ -242,12 +323,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",
|
||||
|
@ -270,6 +349,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",
|
||||
|
||||
|
@ -281,7 +364,8 @@
|
|||
"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",
|
||||
"SW5E.LanguagesAleena": "Aleena",
|
||||
"SW5E.LanguagesAntarian": "Antarian",
|
||||
|
@ -383,12 +467,18 @@
|
|||
"SW5E.LanguagesYevethan": "Yevethan",
|
||||
"SW5E.LanguagesZabraki": "Zabraki",
|
||||
"SW5E.LanguagesZygerrian": "Zygerrian",
|
||||
"SW5E.LegAct": "Legd. Actions",
|
||||
"SW5E.LegRes": "Legd. Resistance",
|
||||
"SW5E.LegAct": "Legendary Actions",
|
||||
"SW5E.LegendaryActionLabel": "Legendary Action",
|
||||
"SW5E.LegRes": "Legendary Resistance",
|
||||
"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.Max": "Max",
|
||||
"SW5E.Modifier": "Modifier",
|
||||
"SW5E.Name": "Character Name",
|
||||
|
@ -397,6 +487,8 @@
|
|||
"SW5E.Normal": "Normal",
|
||||
"SW5E.NotProficient": "Not Proficient",
|
||||
"SW5E.OtherFormula": "Other Formula",
|
||||
"SW5E.PactMagic": "Pact Magic",
|
||||
"SW5E.Passive": "Passive",
|
||||
"SW5E.PlaceTemplate": "Place Measured Template",
|
||||
"SW5E.Polymorph": "Polymorph",
|
||||
"SW5E.PolymorphAcceptSettings": "Custom Settings",
|
||||
|
@ -414,10 +506,12 @@
|
|||
"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.Concentrated": "Concentrate",
|
||||
"SW5E.Concentrate": "Concentrate",
|
||||
"SW5E.Price": "Price",
|
||||
"SW5E.Proficiency": "Proficiency",
|
||||
"SW5E.Proficient": "Proficient",
|
||||
|
@ -426,6 +520,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",
|
||||
|
@ -438,7 +534,8 @@
|
|||
"SW5E.RollExample": "e.g. +1d4",
|
||||
"SW5E.RollMode": "Roll Mode",
|
||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||
"SW5E.Save": "Save",
|
||||
"SW5E.SavingThrow": "Saving Throw",
|
||||
"SW5E.SavePromptTitle": "{ability} Saving Throw",
|
||||
"SW5E.ScalingFormula": "Scaling Formula",
|
||||
"SW5E.SchoolLgt": "Light",
|
||||
"SW5E.SchoolUni": "Universal",
|
||||
|
@ -451,8 +548,13 @@
|
|||
"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.ShortRestSelect": "Select Dice to Roll",
|
||||
"SW5E.Size": "Size",
|
||||
"SW5E.SizeGargantuan": "Gargantuan",
|
||||
|
@ -479,6 +581,7 @@
|
|||
"SW5E.SkillSte": "Stealth",
|
||||
"SW5E.SkillSur": "Survival",
|
||||
"SW5E.SkillTec": "Technology",
|
||||
"SW5E.SkillPromptTitle": "{skill} Skill Check",
|
||||
"SW5E.Slots": "Slots",
|
||||
"SW5E.Source": "Source",
|
||||
"SW5E.Special": "Special",
|
||||
|
@ -511,7 +614,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",
|
||||
|
@ -532,6 +636,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",
|
||||
|
@ -552,6 +657,7 @@
|
|||
"SW5E.TargetWall": "Wall",
|
||||
"SW5E.TargetWeapon": "Weapon",
|
||||
"SW5E.Temp": "Temp",
|
||||
"SW5E.Threshold": "Threshold",
|
||||
"SW5E.TimeDay": "Days",
|
||||
"SW5E.TimeHour": "Hours",
|
||||
"SW5E.TimeInst": "Instantaneous",
|
||||
|
@ -567,6 +673,7 @@
|
|||
"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",
|
||||
|
@ -593,6 +700,7 @@
|
|||
"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",
|
||||
|
@ -602,6 +710,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.",
|
||||
|
@ -648,7 +770,7 @@
|
|||
"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.",
|
||||
|
@ -659,12 +781,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)"
|
||||
}
|
||||
|
|
349
less/actors.less
349
less/actors.less
|
@ -1,58 +1,73 @@
|
|||
@import "./variables.less";
|
||||
|
||||
@actorNameHeight: 60px;
|
||||
|
||||
.sw5e.sheet.actor {
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Sheet Header */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.sheet-header {
|
||||
.charlevel {
|
||||
|
||||
// Portrait Artwork
|
||||
img.profile {
|
||||
flex: 0 0 160px;
|
||||
height: 60px;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
text-align: right;
|
||||
|
||||
.level {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
|
||||
label {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline;
|
||||
width: 36px;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// If the Experience Bar is disabled
|
||||
&.noxp {
|
||||
margin-top: 10px;
|
||||
> label {
|
||||
font-size: 32px;
|
||||
}
|
||||
> input {
|
||||
font-size: 32px;
|
||||
flex: 0 0 40px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
max-width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
// Character Name
|
||||
h1.charname {
|
||||
flex: 1;
|
||||
height: @actorNameHeight;
|
||||
padding: 0;
|
||||
input {
|
||||
height: @actorNameHeight - 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.experience {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
padding-right: 5px;
|
||||
font-size: 16px;
|
||||
color: @colorOlive;
|
||||
// Character Level
|
||||
.header-exp {
|
||||
flex: 0 0 150px;
|
||||
margin-right: 3px;
|
||||
height: @actorNameHeight;
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// Character Summary
|
||||
.summary {
|
||||
height: 30px;
|
||||
border-bottom: @borderGroove;
|
||||
font-size: 18px;
|
||||
input, span {
|
||||
display: block;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// Primary Attributes
|
||||
.attributes {
|
||||
flex: 0 0 100%;
|
||||
margin: 0;
|
||||
|
||||
.attribute {
|
||||
height: 70px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-right: @borderGroove;
|
||||
border-radius: 0;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.attribute-value {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,55 +84,55 @@
|
|||
|
||||
// Box Headers
|
||||
h4.box-title {
|
||||
height: 18px;
|
||||
line-height: 16px;
|
||||
margin: 4px 8px 2px;
|
||||
.modesto();
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid @colorBeige;
|
||||
color: @colorOlive;
|
||||
border-bottom: 1px solid @colorFaint;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Attributes */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.tab.attributes {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul.attributes {
|
||||
flex: 0 0 60px;
|
||||
list-style: none;
|
||||
margin: 5px 0 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li.attribute {
|
||||
height: 70px;
|
||||
margin: 0 5px;
|
||||
height: 60px;
|
||||
margin: 0 5px 0 0;
|
||||
border: @borderGroove;
|
||||
border-radius: 4px;
|
||||
.nodesto();
|
||||
text-align: center;
|
||||
|
||||
.attribute-name {
|
||||
flex: 0 0 18px;
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.attribute-value {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
.modesto();
|
||||
|
||||
input {
|
||||
display: inline;
|
||||
max-width: 80%;
|
||||
height: 28px;
|
||||
margin: 0;
|
||||
> * {
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
span.sep {
|
||||
display: inline;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
font-size: 28px;
|
||||
color: @colorTan;
|
||||
}
|
||||
|
||||
&.multiple {
|
||||
input { max-width: 33%; }
|
||||
&.multiple input {
|
||||
flex: 0 0 33%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,8 +140,9 @@
|
|||
flex: 0 0 18px;
|
||||
margin-top: -1px;
|
||||
line-height: 18px;
|
||||
font-family: "Signika", "Palatino Linotype", serif;
|
||||
font-family: "Signika", sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,22 +152,28 @@
|
|||
/* ----------------------------------------- */
|
||||
|
||||
.ability-scores {
|
||||
flex: 0 0 100%;
|
||||
flex: 0 0 100px;
|
||||
height: 440px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.nodesto();
|
||||
.modesto();
|
||||
border: @borderGroove;
|
||||
border-radius: 3px;
|
||||
|
||||
.ability {
|
||||
height: 70px;
|
||||
margin: 0 5px;
|
||||
text-align: center;
|
||||
border: @borderGroove;
|
||||
border-radius: 3px;
|
||||
border-bottom: @borderGroove;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
input.ability-score {
|
||||
height: 30px;
|
||||
width: 50px;
|
||||
width: 36px;
|
||||
margin: 0 auto;
|
||||
line-height: 32px;
|
||||
font-size: 24px;
|
||||
|
@ -159,12 +181,14 @@
|
|||
|
||||
.ability-modifiers {
|
||||
height: 24px;
|
||||
margin: -10px 0 0;
|
||||
margin: -8px 0 0;
|
||||
|
||||
span.ability-mod,
|
||||
span.ability-save {
|
||||
flex: 0 0 24px;
|
||||
height: 24px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 16px;
|
||||
border-top: @borderGroove;
|
||||
}
|
||||
|
||||
|
@ -180,12 +204,6 @@
|
|||
border-left: @borderGroove;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide modifier box on hover */
|
||||
|
||||
input.ability-score:hover + .ability-modifiers {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,16 +227,21 @@
|
|||
/* ----------------------------------------- */
|
||||
|
||||
ul.skills-list {
|
||||
flex: 0 0 192px;
|
||||
flex: 0 0 180px;
|
||||
height: 440px;
|
||||
list-style: none;
|
||||
margin: 5px 5px 0;
|
||||
padding: 2px 2px 0;
|
||||
margin: 0 5px 0;
|
||||
padding: 3px 0 2px;
|
||||
border: @borderGroove;
|
||||
border-radius: 3px;
|
||||
|
||||
li.skill {
|
||||
height: 22px;
|
||||
padding: 3px 0;
|
||||
height: 24px;
|
||||
padding: 3px 2px;
|
||||
|
||||
&:nth-child(even) {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
h4 {
|
||||
flex: 1px;
|
||||
|
@ -234,6 +257,7 @@
|
|||
|
||||
.skill-ability {
|
||||
flex: 0 0 26px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.skill-mod {
|
||||
|
@ -253,23 +277,25 @@
|
|||
/* ----------------------------------------- */
|
||||
|
||||
.counters {
|
||||
flex: 0 0 100%;
|
||||
flex: none;
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
border-bottom: @borderGroove;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.counter {
|
||||
padding: 0 3px;
|
||||
line-height: 32px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
h4 {
|
||||
flex: auto;
|
||||
margin: 0;
|
||||
.nodesto();
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: @colorOlive;
|
||||
}
|
||||
|
||||
.counter-value {
|
||||
flex: 0 0 50px;
|
||||
flex: none;
|
||||
text-align: right;
|
||||
> * {
|
||||
display: inline;
|
||||
|
@ -286,12 +312,13 @@
|
|||
|
||||
input[type="checkbox"] {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
top: 6px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
span.sep {
|
||||
margin: 0 -2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
@ -301,28 +328,31 @@
|
|||
/* Traits */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.traits {
|
||||
margin: 0 5px;
|
||||
.center-pane {
|
||||
height: 100%;
|
||||
padding: 0 5px 0 3px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.traits {
|
||||
.form-group, .form-group-stacked {
|
||||
margin: 0 0 4px 0;
|
||||
margin: 0 0 3px 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.configure-flags {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.actor-size {
|
||||
flex: 0 0 150px;
|
||||
}
|
||||
|
||||
label {
|
||||
flex: 0 0 150px;
|
||||
flex: none;
|
||||
line-height: 20px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
select {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
input {
|
||||
|
@ -360,18 +390,10 @@
|
|||
.inventory-filters {
|
||||
margin: 0 8px;
|
||||
flex: 0 0 20px;
|
||||
h3, .filter-title {
|
||||
.nodesto();
|
||||
color: @colorOlive;
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.powerbook-filters {
|
||||
flex: 0 0 40px;
|
||||
}
|
||||
justify-content: flex-end;
|
||||
|
||||
.currency {
|
||||
flex: 0 0 100%;
|
||||
list-style: none;
|
||||
margin: 4px 0 8px;
|
||||
padding: 0;
|
||||
|
@ -398,6 +420,7 @@
|
|||
margin: 0;
|
||||
padding: 0 5px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
|
||||
// Inventory Item
|
||||
.item {
|
||||
|
@ -424,12 +447,12 @@
|
|||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&.rollable .item-image:hover {
|
||||
background-image: url("/icons/svg/d20-black.svg") !important;
|
||||
}
|
||||
&.rollable:hover .item-image {
|
||||
background-image: url("/icons/svg/d20-grey.svg") !important;
|
||||
}
|
||||
&.rollable .item-image:hover {
|
||||
background-image: url("/icons/svg/d20-black.svg") !important;
|
||||
}
|
||||
|
||||
i.attuned {
|
||||
color: @colorTan;
|
||||
|
@ -453,6 +476,7 @@
|
|||
text-align: right;
|
||||
font-size: 11px;
|
||||
color: @colorTan;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,8 +492,8 @@
|
|||
h3 {
|
||||
margin: 0 -5px 0 0;
|
||||
padding-left: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
.modesto();
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.item-controls a.item-create {
|
||||
|
@ -484,6 +508,10 @@
|
|||
color: @colorTan;
|
||||
text-align: center;
|
||||
border-right: 1px solid @colorFaint;
|
||||
word-break: break-word;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child { border-right: none; }
|
||||
&.item-action {flex: 0 0 100px}
|
||||
}
|
||||
|
@ -524,15 +552,77 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Encumbrance Bar */
|
||||
.encumbrance {
|
||||
flex: 0 0 12px;
|
||||
background: @colorTan;
|
||||
margin: 1px 15px 0 1px;
|
||||
border: 1px solid @colorDark;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
|
||||
.encumbrance-bar {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
background: #6c8aa5;
|
||||
height: 8px;
|
||||
border: 1px solid #cde4ff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.encumbrance-label {
|
||||
height: 10px;
|
||||
padding: 0 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 13px;
|
||||
line-height: 12px;
|
||||
text-align: right;
|
||||
color: #EEE;
|
||||
text-shadow: 0 0 5px #000;
|
||||
}
|
||||
|
||||
.encumbrance-breakpoint {
|
||||
display: block;
|
||||
position: absolute;
|
||||
&.encumbrance-33 { left: 33% }
|
||||
&.encumbrance-66 { left: 66% }
|
||||
}
|
||||
|
||||
.arrow-up {
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 4px solid #666;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid #666;
|
||||
}
|
||||
|
||||
&.encumbered {
|
||||
.arrow-up { border-bottom: 4px solid #000; }
|
||||
.arrow-down { border-top: 4px solid #000; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Powerbook */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.powercasting-ability {
|
||||
h3 {
|
||||
flex: none;
|
||||
margin-right: 5px;
|
||||
}
|
||||
flex: 0 0 240px;
|
||||
margin: 0;
|
||||
|
||||
input, span {
|
||||
flex: 0 0 32px;
|
||||
text-align: center;
|
||||
|
@ -549,7 +639,7 @@
|
|||
|
||||
.power-slots,
|
||||
.power-comps {
|
||||
flex: 0 0 72px;
|
||||
flex: 0 0 75px;
|
||||
padding-right: 5px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
|
@ -558,10 +648,13 @@
|
|||
}
|
||||
|
||||
.power-slots {
|
||||
input, span.sep {
|
||||
input {
|
||||
display: inline;
|
||||
max-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.sep {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
@detailsHeight: 40px;
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* All SW5e Apps */
|
||||
/* All DnD5e Apps */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.sw5e {
|
||||
.window-content {
|
||||
background: @sheetBackground;
|
||||
font-size: 13px;
|
||||
color: @colorDark
|
||||
color: @colorDark;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
|
@ -56,20 +56,27 @@
|
|||
}
|
||||
|
||||
// Checkbox Labels
|
||||
// TODO: THIS CAN BE MOSTLY REMOVED NOW THAT IT IS IN CORE, see core forms.less
|
||||
label.checkbox {
|
||||
flex: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 20px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 11px;
|
||||
text-align: right;
|
||||
input[type="checkbox"] {
|
||||
height: auto;
|
||||
margin: 0 5px 0;
|
||||
> input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 2px 0 0;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
top: 4px;
|
||||
}
|
||||
&.right > input[type="checkbox"] {
|
||||
margin: 0 0 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Form Groups */
|
||||
.form-group {
|
||||
label {
|
||||
|
@ -101,11 +108,10 @@
|
|||
// Form Headers
|
||||
.form-header {
|
||||
margin: 0 0 0.25em 0;
|
||||
padding: 0 2px;
|
||||
.nodesto();
|
||||
font-size: 24px;
|
||||
padding: 2px 0;
|
||||
border-top: @borderGroove;
|
||||
border-bottom: @borderGroove;
|
||||
.modesto();
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
|
@ -175,6 +181,16 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Separators
|
||||
span.sep {
|
||||
flex: none;
|
||||
margin: 0 1px;
|
||||
display: inline;
|
||||
position: relative;
|
||||
color: @colorTan;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* TinyMCE */
|
||||
/* ----------------------------------------- */
|
||||
|
@ -190,10 +206,14 @@
|
|||
/* ----------------------------------------- */
|
||||
/* Sheet Header */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.sheet-header {
|
||||
flex: 0 0 @headerHeight;
|
||||
border-bottom: @borderGroove;
|
||||
.nodesto();
|
||||
|
||||
.header-details {
|
||||
.modesto();
|
||||
}
|
||||
|
||||
/* Character Name */
|
||||
h1 {
|
||||
|
@ -204,8 +224,10 @@
|
|||
padding: 5px;
|
||||
|
||||
input {
|
||||
display: block;
|
||||
height: 50px;
|
||||
font-size: 36px;
|
||||
font-size: 32px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +236,7 @@
|
|||
flex: 0 0 @headerHeight;
|
||||
max-width: @headerHeight;
|
||||
height: @headerHeight;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
border: none;
|
||||
border-right: @borderGroove;
|
||||
}
|
||||
|
@ -230,11 +252,10 @@
|
|||
border-bottom: none;
|
||||
|
||||
li {
|
||||
width: 33.33%;
|
||||
height: calc(100% - 6px);
|
||||
float: left;
|
||||
height: @detailsHeight - 6px;
|
||||
margin: 2px 0;
|
||||
padding: 0 3px;
|
||||
padding: 0;
|
||||
border-right: @borderGroove;
|
||||
line-height: 34px;
|
||||
color: @colorOlive;
|
||||
|
@ -253,7 +274,7 @@
|
|||
.sheet-navigation {
|
||||
flex: 0 0 @navHeight;
|
||||
margin-bottom: 5px;
|
||||
.nodesto();
|
||||
.modesto();
|
||||
|
||||
.item {
|
||||
height: 30px;
|
||||
|
@ -285,18 +306,25 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 16px;
|
||||
max-width: 70%;
|
||||
|
||||
.filter-title {
|
||||
flex: 3;
|
||||
.filter-icon {
|
||||
flex: none;
|
||||
font-size: 14px;
|
||||
color: @colorTan;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
margin: 0 6px;
|
||||
margin: 0 6px 0 0;
|
||||
border-bottom: 3px solid @colorBeige;
|
||||
white-space: nowrap;
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-shadow: 0 0 4px red;
|
||||
border-bottom: 3px solid @colorTan;
|
||||
|
@ -312,19 +340,23 @@
|
|||
/* Trait Lists */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.trait-selector {
|
||||
flex: 0 0 16px;
|
||||
padding: 2px 0;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
.traits {
|
||||
margin: 5px 0 0;
|
||||
|
||||
.traits-list {
|
||||
line-height: 20px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
.trait-selector {
|
||||
flex: 0 0 16px;
|
||||
padding: 2px 0;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.traits-list {
|
||||
flex: 0 0 100%;
|
||||
line-height: 20px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
/* Basic Structure */
|
||||
/* ----------------------------------------- */
|
||||
.sw5e.sheet.actor.character {
|
||||
min-width: 680px;
|
||||
min-height: 736px;
|
||||
min-width: 720px;
|
||||
min-height: 680px;
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Sheet Header */
|
||||
|
@ -13,93 +13,73 @@
|
|||
|
||||
.sheet-header {
|
||||
|
||||
// Character Profile image (larger than usual)
|
||||
// Character Profile image (larger)
|
||||
img.profile {
|
||||
flex: 0 0 180px;
|
||||
max-width: 180px;
|
||||
height: 180px;
|
||||
flex: 0 0 160px;
|
||||
max-width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
// Character level and experience bar
|
||||
// Character Level
|
||||
.charlevel {
|
||||
flex: 0 0 180px;
|
||||
padding: 0 5px 2px;
|
||||
flex: 0 0 20px;
|
||||
height: 20px;
|
||||
font-size: 18px;
|
||||
color: @colorTan;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.level {
|
||||
height: 28px;
|
||||
// Experience Tracking
|
||||
.experience {
|
||||
flex: 0 0 32px;
|
||||
margin-bottom: -5px;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
span.max {
|
||||
color: @colorTan;
|
||||
flex: none;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.experience {
|
||||
input[type="text"] {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.xpbar {
|
||||
width: 100%;
|
||||
flex: 0 0 8px;
|
||||
background: #666;
|
||||
}
|
||||
.xpbar {
|
||||
flex: 0 0 8px;
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
background: @colorTan;
|
||||
border: 1px solid #000;
|
||||
border-radius: 3px;
|
||||
.bar {
|
||||
height: 4px;
|
||||
margin: 1px;
|
||||
display: block;
|
||||
background: #afebff;
|
||||
border: 1px solid #000;
|
||||
border-radius: 3px;
|
||||
|
||||
.bar {
|
||||
height: 4px;
|
||||
margin: 1px;
|
||||
display: block;
|
||||
background: #afebff;
|
||||
border: 1px solid #000;
|
||||
border-radius: 2px;
|
||||
}
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Character Summary
|
||||
.summary {
|
||||
border-bottom: @borderGroove;
|
||||
}
|
||||
|
||||
// Primary Attributes
|
||||
// Header Attributes
|
||||
.attributes {
|
||||
height: 80px;
|
||||
margin: 0;
|
||||
|
||||
.attribute {
|
||||
height: 80px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-right: @borderGroove;
|
||||
border-radius: 0;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.attribute-value {
|
||||
margin: 5px 0 0;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.attribute-name {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.attribute-footer {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
a.rest {
|
||||
border: 1px solid @colorBeige;
|
||||
border-radius: 2px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 1px 3px;
|
||||
margin: 0 6px;
|
||||
padding: 0 3px;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.hit-dice {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.initiative .attribute-footer input {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.summary .proficiency {
|
||||
text-align: right;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,26 +87,24 @@
|
|||
/* Sheet Body */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.attributes {
|
||||
.resource {
|
||||
.attribute-name {
|
||||
margin: 0 8px;
|
||||
input[type="text"] {
|
||||
height: 20px;
|
||||
margin: 2px 0 -2px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
label.checkbox {
|
||||
margin: 0 3px;
|
||||
input[type="checkbox"] {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
// Custom Resources
|
||||
.resource .attribute-value {
|
||||
input {
|
||||
flex: 0 0 25%;
|
||||
}
|
||||
|
||||
.initiative .attribute-footer input {
|
||||
width: 32px;
|
||||
label.recharge {
|
||||
height: 32px;
|
||||
position: relative;
|
||||
font-family: "Signika", sans-serif;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
color: @colorOlive;
|
||||
input[type="checkbox"] {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
margin: 0;
|
||||
top: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,88 +121,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.counters {
|
||||
.death-saves {
|
||||
flex: 2;
|
||||
.counter-value {
|
||||
flex: 0 0 90px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-detail.player-class {
|
||||
flex: 0 0 180px;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Inventory */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
/* Encumbrance Bar */
|
||||
.encumbrance {
|
||||
flex: 0 0 12px;
|
||||
background: @colorTan;
|
||||
margin: 1px 15px 0 1px;
|
||||
border: 1px solid @colorDark;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
|
||||
.encumbrance-bar {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
background: #6c8aa5;
|
||||
height: 8px;
|
||||
border: 1px solid #cde4ff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.encumbrance-label {
|
||||
height: 10px;
|
||||
padding: 0 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 13px;
|
||||
line-height: 12px;
|
||||
text-align: right;
|
||||
color: #EEE;
|
||||
text-shadow: 0 0 5px #000;
|
||||
}
|
||||
|
||||
.encumbrance-breakpoint {
|
||||
display: block;
|
||||
position: absolute;
|
||||
&.encumbrance-33 { left: 33% }
|
||||
&.encumbrance-66 { left: 66% }
|
||||
}
|
||||
|
||||
.arrow-up {
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 4px solid #666;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid #666;
|
||||
}
|
||||
|
||||
&.encumbered {
|
||||
.arrow-up { border-bottom: 4px solid #000; }
|
||||
.arrow-down { border-top: 4px solid #000; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Item Controls */
|
||||
/* ----------------------------------------- */
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
flex: 1;
|
||||
margin: 0;
|
||||
line-height: 36px;
|
||||
.nodesto();
|
||||
.bungeeInline();
|
||||
color: @colorOlive;
|
||||
&:hover {
|
||||
color: #111;
|
||||
|
|
|
@ -192,7 +192,26 @@
|
|||
text-align: right;
|
||||
color: @colorTan;
|
||||
}
|
||||
|
||||
|
||||
.recharge.form-group {
|
||||
span {
|
||||
text-align: right;
|
||||
padding-right: 3px;
|
||||
}
|
||||
input[type="text"] {
|
||||
flex: 0 0 32px;
|
||||
text-align: center;
|
||||
}
|
||||
label.checkbox {
|
||||
flex: none;
|
||||
input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h4.armorproperties-header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -263,16 +282,6 @@
|
|||
color: @colorTan;
|
||||
}
|
||||
|
||||
.recharge {
|
||||
span {
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
label.checkbox {
|
||||
flex: 0 0 80px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Item Actions */
|
||||
/* ----------------------------------------- */
|
||||
|
|
|
@ -5,5 +5,37 @@
|
|||
/* ----------------------------------------- */
|
||||
.sw5e.sheet.actor.npc {
|
||||
min-width: 600px;
|
||||
min-height: 658px;
|
||||
min-height: 680px;
|
||||
|
||||
.header-exp {
|
||||
flex: 0 0 80px;
|
||||
justify-content: center;
|
||||
.cr {
|
||||
flex: 0 0 32px;
|
||||
line-height: 28px;
|
||||
margin-bottom: -5px;
|
||||
font-size: 24px;
|
||||
input {
|
||||
width: 32px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.experience {
|
||||
flex: 0 0 18px;
|
||||
color: @colorTan;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.powercasting-ability {
|
||||
label {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,3 +5,4 @@
|
|||
@import "chat.less";
|
||||
@import "character.less";
|
||||
@import "npc.less";
|
||||
@import "vehicle.less";
|
||||
|
|
|
@ -7,13 +7,41 @@
|
|||
/* Fonts */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
/* russo-one-regular - latin */
|
||||
@font-face {
|
||||
font-family: "Nodesto";
|
||||
src: url("fonts/NodestoCapsCondensed.otf");
|
||||
font-family: 'Russo One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/RussoOne.ttf');
|
||||
}
|
||||
.nodesto {
|
||||
font-family: "Nodesto", "Signika", "Palatino Linotype", serif;
|
||||
.russoOne {
|
||||
font-family: 'Russo One';
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
/* bungee-inline-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Bungee Inline';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/BungeeInline.ttf');
|
||||
}
|
||||
.bungeeInline {
|
||||
font-family: 'Bungee Inline';
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
/* open-sans-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/OpenSans-Regular.ttf');
|
||||
}
|
||||
.openSans {
|
||||
font-family: 'Open Sans';
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
|
|
32
less/vehicle.less
Normal file
32
less/vehicle.less
Normal file
|
@ -0,0 +1,32 @@
|
|||
.sw5e.sheet.actor.vehicle {
|
||||
.features {
|
||||
.item-controls {
|
||||
flex: 0 0 68px;
|
||||
.item-toggle {
|
||||
color: #b5b3a4;
|
||||
&.active {
|
||||
color: #4b4a44;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.counters {
|
||||
.counter.creature-cap {
|
||||
.counter-value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: none;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.counter.cargo-cap {
|
||||
input {
|
||||
max-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,14 @@
|
|||
import {TraitSelector} from "../../apps/trait-selector.js";
|
||||
import {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 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);
|
||||
|
||||
|
@ -37,6 +37,13 @@ export class ActorSheet5e extends ActorSheet {
|
|||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/limited-sheet.html";
|
||||
return `systems/sw5e/templates/actors/${this.actor.data.type}-sheet.html`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -53,6 +60,7 @@ export class ActorSheet5e extends ActorSheet {
|
|||
cssClass: isOwner ? "editable" : "locked",
|
||||
isCharacter: this.entity.data.type === "character",
|
||||
isNPC: this.entity.data.type === "npc",
|
||||
isVehicle: this.entity.data.type === 'vehicle',
|
||||
config: CONFIG.SW5E,
|
||||
};
|
||||
|
||||
|
@ -75,11 +83,13 @@ export class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// 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];
|
||||
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
|
||||
|
@ -96,9 +106,9 @@ export class ActorSheet5e extends ActorSheet {
|
|||
|
||||
_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,
|
||||
|
@ -200,7 +210,7 @@ export class ActorSheet5e extends ActorSheet {
|
|||
if ( mode in sections ) {
|
||||
s = sections[mode];
|
||||
if ( !powerbook[s] ){
|
||||
registerSection(sl, s, CONFIG.SW5E.powerPreparationModes[mode], levels[mode]);
|
||||
registerSection(mode, s, CONFIG.SW5E.powerPreparationModes[mode], levels[mode]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +262,7 @@ export class ActorSheet5e extends ActorSheet {
|
|||
|
||||
// Equipment-specific filters
|
||||
if ( filters.has("equipped") ) {
|
||||
if (data.equipped && data.equipped !== true) return false;
|
||||
if ( data.equipped !== true ) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@ -295,8 +305,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));
|
||||
|
@ -328,14 +340,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 +428,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 +497,30 @@ 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;
|
||||
}
|
||||
|
||||
// Upgrade the number of class levels a character has
|
||||
if ( (itemData.type === "class") && ( this.actor.itemTypes.class.find(c => c.name === itemData.name)) ) {
|
||||
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||
const lvl = cls.data.data.levels;
|
||||
return cls.update({"data.levels": Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level)})
|
||||
}
|
||||
|
||||
// Create the owned item as normal
|
||||
// TODO remove conditional logic in 0.7.x
|
||||
if (isNewerVersion(game.data.version, "0.6.9")) return super._onDropItemCreate(itemData);
|
||||
else return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -634,7 +634,7 @@ export class ActorSheet5e extends ActorSheet {
|
|||
const header = event.currentTarget;
|
||||
const 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)
|
||||
};
|
||||
|
@ -736,11 +736,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 +757,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ActorSheet5e } from "./base.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for player character type actors in the SW5E system.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @type {ActorSheet5e}
|
||||
*/
|
||||
export class ActorSheet5eCharacter extends ActorSheet5e {
|
||||
export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||
|
||||
/**
|
||||
* Define default rendering options for the NPC sheet
|
||||
|
@ -14,24 +14,11 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "character"],
|
||||
width: 672,
|
||||
width: 720,
|
||||
height: 736
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Rendering */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the correct HTML template path to use for rendering this particular sheet
|
||||
* @type {String}
|
||||
*/
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/limited-sheet.html";
|
||||
return "systems/sw5e/templates/actors/character-sheet.html";
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -57,6 +44,7 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
|
||||
// Experience Tracking
|
||||
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
||||
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
|
||||
|
||||
// Return data for rendering
|
||||
return sheetData;
|
||||
|
@ -80,13 +68,12 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
||||
};
|
||||
|
||||
|
||||
// Partition items by category
|
||||
let [items, powers, feats, classes, species] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.isStack = item.data.quantity ? item.data.quantity > 1 : false;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
|
||||
// Item usage
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
|
@ -101,7 +88,7 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
if ( item.type === "power" ) arr[1].push(item);
|
||||
else if ( item.type === "feat" ) arr[2].push(item);
|
||||
else if ( item.type === "class" ) arr[3].push(item);
|
||||
else if ( item.type === "species" ) arr[4].push(item);
|
||||
else if ( item.type === "species" ) arr[4].push(item);
|
||||
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
||||
return arr;
|
||||
}, [[], [], [], [], []]);
|
||||
|
@ -111,27 +98,24 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
powers = this._filterItems(powers, this._filters.powerbook);
|
||||
feats = this._filterItems(feats, this._filters.features);
|
||||
|
||||
// Organize items
|
||||
for ( let i of items ) {
|
||||
i.data.quantity = i.data.quantity || 0;
|
||||
i.data.weight = i.data.weight || 0;
|
||||
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
|
||||
inventory[i.type].items.push(i);
|
||||
}
|
||||
|
||||
// Organize Powerbook and count the number of prepared powers (excluding always, at will, etc...)
|
||||
const powerbook = this._preparePowerbook(data, powers);
|
||||
const nPrepared = powers.filter(s => {
|
||||
return (s.data.level > 0) && (s.data.preparation.mode === "prepared") && s.data.preparation.prepared;
|
||||
}).length;
|
||||
|
||||
// Organize Inventory
|
||||
let totalWeight = 0;
|
||||
for ( let i of items ) {
|
||||
i.data.quantity = i.data.quantity || 0;
|
||||
i.data.weight = i.data.weight || 0;
|
||||
i.totalWeight = Math.round(i.data.quantity * i.data.weight * 10) / 10;
|
||||
inventory[i.type].items.push(i);
|
||||
totalWeight += i.totalWeight;
|
||||
}
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
|
||||
// Organize Features
|
||||
const features = {
|
||||
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
||||
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true},
|
||||
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true},
|
||||
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
||||
};
|
||||
|
@ -141,7 +125,7 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
features.classes.items = classes;
|
||||
features.species.items = species;
|
||||
features.species.items = species;
|
||||
|
||||
// Assign and return
|
||||
data.inventory = Object.values(inventory);
|
||||
|
@ -174,51 +158,6 @@ export class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Compute the level and percentage of encumbrance for an Actor.
|
||||
*
|
||||
* Optionally include the weight of carried currency across all denominations by applying the standard rule
|
||||
* from the PHB pg. 143
|
||||
*
|
||||
* @param {Number} totalWeight The cumulative item weight from inventory items
|
||||
* @param {Object} actorData The data object for the Actor being rendered
|
||||
* @return {Object} An object describing the character's encumbrance level
|
||||
* @private
|
||||
*/
|
||||
_computeEncumbrance(totalWeight, actorData) {
|
||||
|
||||
// Encumbrance classes
|
||||
let mod = {
|
||||
tiny: 0.5,
|
||||
sm: 1,
|
||||
med: 1,
|
||||
lg: 2,
|
||||
huge: 4,
|
||||
grg: 8
|
||||
}[actorData.data.traits.size] || 1;
|
||||
|
||||
// Apply Powerful Build feat
|
||||
if ( this.actor.getFlag("sw5e", "powerfulBuild") ) mod = Math.min(mod * 2, 8);
|
||||
|
||||
// Add Currency Weight
|
||||
if ( game.settings.get("sw5e", "currencyWeight") ) {
|
||||
const currency = actorData.data.currency;
|
||||
const numCoins = Object.values(currency).reduce((val, denom) => val += denom, 0);
|
||||
totalWeight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||
}
|
||||
|
||||
// Compute Encumbrance percentage
|
||||
const enc = {
|
||||
max: actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod,
|
||||
value: Math.round(totalWeight * 10) / 10,
|
||||
};
|
||||
enc.pct = Math.min(enc.value * 100 / enc.max, 99);
|
||||
enc.encumbered = enc.pct > (2/3);
|
||||
return enc;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -1,37 +1,21 @@
|
|||
import { ActorSheet5e } from "../sheets/base.js";
|
||||
import ActorSheet5e from "../sheets/base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for NPC type characters in the SW5E system.
|
||||
* 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
381
module/actor/sheets/vehicle.js
Normal file
381
module/actor/sheets/vehicle.js
Normal file
|
@ -0,0 +1,381 @@
|
|||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for Vehicle type actors.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @type {ActorSheet5e}
|
||||
*/
|
||||
export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||
/**
|
||||
* Define default rendering options for the Vehicle sheet.
|
||||
* @returns {Object}
|
||||
*/
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||
width: 605,
|
||||
height: 680
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Creates a new cargo entry for a vehicle Actor.
|
||||
*/
|
||||
static get newCargo() {
|
||||
return {
|
||||
name: '',
|
||||
quantity: 1
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Compute the total weight of the vehicle's cargo.
|
||||
* @param {Number} totalWeight The cumulative item weight from inventory items
|
||||
* @param {Object} actorData The data object for the Actor being rendered
|
||||
* @returns {{max: number, value: number, pct: number}}
|
||||
* @private
|
||||
*/
|
||||
_computeEncumbrance(totalWeight, actorData) {
|
||||
|
||||
// Compute currency weight
|
||||
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
||||
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||
|
||||
// Vehicle weights are an order of magnitude greater.
|
||||
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
||||
|
||||
// Compute overall encumbrance
|
||||
const enc = {
|
||||
max: actorData.data.attributes.capacity.cargo,
|
||||
value: Math.round(totalWeight * 10) / 10
|
||||
};
|
||||
enc.pct = Math.min(enc.value * 100 / enc.max, 99);
|
||||
return enc;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare items that are mounted to a vehicle and require one or more crew
|
||||
* to operate.
|
||||
* @private
|
||||
*/
|
||||
_prepareCrewedItem(item) {
|
||||
|
||||
// Determine crewed status
|
||||
const isCrewed = item.data.crewed;
|
||||
item.toggleClass = isCrewed ? 'active' : '';
|
||||
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`);
|
||||
|
||||
// Handle crew actions
|
||||
if (item.type === 'feat' && item.data.activation.type === 'crew') {
|
||||
item.crew = item.data.activation.cost;
|
||||
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`);
|
||||
if (item.data.cover === .5) item.cover = '½';
|
||||
else if (item.data.cover === .75) item.cover = '¾';
|
||||
else if (item.data.cover === null) item.cover = '—';
|
||||
if (item.crew < 1 || item.crew === null) item.crew = '—';
|
||||
}
|
||||
|
||||
// Prepare vehicle weapons
|
||||
if (item.type === 'equipment' || item.type === 'weapon') {
|
||||
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—';
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the Vehicle sheet.
|
||||
* @private
|
||||
*/
|
||||
_prepareItems(data) {
|
||||
const cargoColumns = [{
|
||||
label: game.i18n.localize('SW5E.Quantity'),
|
||||
css: 'item-qty',
|
||||
property: 'quantity',
|
||||
editable: 'Number'
|
||||
}];
|
||||
|
||||
const equipmentColumns = [{
|
||||
label: game.i18n.localize('SW5E.Quantity'),
|
||||
css: 'item-qty',
|
||||
property: 'data.quantity'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.AC'),
|
||||
css: 'item-ac',
|
||||
property: 'data.armor.value'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.HP'),
|
||||
css: 'item-hp',
|
||||
property: 'data.hp.value',
|
||||
editable: 'Number'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Threshold'),
|
||||
css: 'item-threshold',
|
||||
property: 'threshold'
|
||||
}];
|
||||
|
||||
const features = {
|
||||
actions: {
|
||||
label: game.i18n.localize('SW5E.ActionPl'),
|
||||
items: [],
|
||||
crewable: true,
|
||||
dataset: {type: 'feat', 'activation.type': 'crew'},
|
||||
columns: [{
|
||||
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||
css: 'item-crew',
|
||||
property: 'crew'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Cover'),
|
||||
css: 'item-cover',
|
||||
property: 'cover'
|
||||
}]
|
||||
},
|
||||
equipment: {
|
||||
label: game.i18n.localize('SW5E.ItemTypeEquipment'),
|
||||
items: [],
|
||||
crewable: true,
|
||||
dataset: {type: 'equipment', 'armor.type': 'vehicle'},
|
||||
columns: equipmentColumns
|
||||
},
|
||||
passive: {
|
||||
label: game.i18n.localize('SW5E.Features'),
|
||||
items: [],
|
||||
dataset: {type: 'feat'}
|
||||
},
|
||||
reactions: {
|
||||
label: game.i18n.localize('SW5E.ReactionPl'),
|
||||
items: [],
|
||||
dataset: {type: 'feat', 'activation.type': 'reaction'}
|
||||
},
|
||||
weapons: {
|
||||
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'),
|
||||
items: [],
|
||||
crewable: true,
|
||||
dataset: {type: 'weapon', 'weapon-type': 'siege'},
|
||||
columns: equipmentColumns
|
||||
}
|
||||
};
|
||||
|
||||
const cargo = {
|
||||
crew: {
|
||||
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||
items: data.data.cargo.crew,
|
||||
css: 'cargo-row crew',
|
||||
editableName: true,
|
||||
dataset: {type: 'crew'},
|
||||
columns: cargoColumns
|
||||
},
|
||||
passengers: {
|
||||
label: game.i18n.localize('SW5E.VehiclePassengers'),
|
||||
items: data.data.cargo.passengers,
|
||||
css: 'cargo-row passengers',
|
||||
editableName: true,
|
||||
dataset: {type: 'passengers'},
|
||||
columns: cargoColumns
|
||||
},
|
||||
cargo: {
|
||||
label: game.i18n.localize('SW5E.VehicleCargo'),
|
||||
items: [],
|
||||
dataset: {type: 'loot'},
|
||||
columns: [{
|
||||
label: game.i18n.localize('SW5E.Quantity'),
|
||||
css: 'item-qty',
|
||||
property: 'data.quantity',
|
||||
editable: 'Number'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Price'),
|
||||
css: 'item-price',
|
||||
property: 'data.price',
|
||||
editable: 'Number'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Weight'),
|
||||
css: 'item-weight',
|
||||
property: 'data.weight',
|
||||
editable: 'Number'
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
let totalWeight = 0;
|
||||
for (const item of data.items) {
|
||||
this._prepareCrewedItem(item);
|
||||
if (item.type === 'weapon') features.weapons.items.push(item);
|
||||
else if (item.type === 'equipment') features.equipment.items.push(item);
|
||||
else if (item.type === 'loot') {
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
}
|
||||
else if (item.type === 'feat') {
|
||||
if (!item.data.activation.type || item.data.activation.type === 'none') {
|
||||
features.passive.items.push(item);
|
||||
}
|
||||
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
|
||||
else features.actions.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
data.features = Object.values(features);
|
||||
data.cargo = Object.values(cargo);
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if (!this.options.editable) return;
|
||||
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
html.find('.item-hp input')
|
||||
.click(evt => evt.target.select())
|
||||
.change(this._onHPChange.bind(this));
|
||||
|
||||
html.find('.item:not(.cargo-row) input[data-property]')
|
||||
.click(evt => evt.target.select())
|
||||
.change(this._onEditInSheet.bind(this));
|
||||
|
||||
html.find('.cargo-row input')
|
||||
.click(evt => evt.target.select())
|
||||
.change(this._onCargoRowChange.bind(this));
|
||||
|
||||
if (this.actor.data.data.attributes.actions.stations) {
|
||||
html.find('.counter.actions, .counter.action-thresholds').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle saving a cargo row (i.e. crew or passenger) in-sheet.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Actor>|null}
|
||||
* @private
|
||||
*/
|
||||
_onCargoRowChange(event) {
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget;
|
||||
const row = target.closest('.item');
|
||||
const idx = Number(row.dataset.itemId);
|
||||
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
|
||||
// Get the cargo entry
|
||||
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||
const entry = cargo[idx];
|
||||
if (!entry) return null;
|
||||
|
||||
// Update the cargo value
|
||||
const key = target.dataset.property || 'name';
|
||||
const type = target.dataset.dtype;
|
||||
let value = target.value;
|
||||
if (type === 'Number') value = Number(value);
|
||||
entry[key] = value;
|
||||
|
||||
// Perform the Actor update
|
||||
return this.actor.update({[`data.cargo.${property}`]: cargo});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle editing certain values like quantity, price, and weight in-sheet.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
_onEditInSheet(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const property = event.currentTarget.dataset.property;
|
||||
const type = event.currentTarget.dataset.dtype;
|
||||
let value = event.currentTarget.value;
|
||||
switch (type) {
|
||||
case 'Number': value = parseInt(value); break;
|
||||
case 'Boolean': value = value === 'true'; break;
|
||||
}
|
||||
return item.update({[`${property}`]: value});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle creating a new crew or passenger row.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Actor|Item>}
|
||||
* @private
|
||||
*/
|
||||
_onItemCreate(event) {
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget;
|
||||
const type = target.dataset.type;
|
||||
if (type === 'crew' || type === 'passengers') {
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||
cargo.push(this.constructor.newCargo);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
return super._onItemCreate(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle deleting a crew or passenger row.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Actor|Item>}
|
||||
* @private
|
||||
*/
|
||||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const row = event.currentTarget.closest('.item');
|
||||
if (row.classList.contains('cargo-row')) {
|
||||
const idx = Number(row.dataset.itemId);
|
||||
const type = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
||||
return super._onItemDelete(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Special handling for editing HP to clamp it within appropriate range.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
_onHPChange(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
|
||||
event.currentTarget.value = hp;
|
||||
return item.update({'data.hp.value': hp});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle toggling an item's crewed status.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const crewed = !!item.data.data.crewed;
|
||||
return item.update({'data.crewed': !crewed});
|
||||
}
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
* A specialized Dialog subclass for ability usage
|
||||
* @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,150 @@ 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.hasPowerSlots ? "fa-magic" : "fa-fist-raised";
|
||||
const label = game.i18n.localize("SW5E.AbilityUse" + (data.hasPowerSlots ? "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: '<i class="fas fa-fist-raised"></i>',
|
||||
label: "Use Ability",
|
||||
callback: html => formData = new FormData(html[0].querySelector("#ability-use-form"))
|
||||
icon: `<i class="fas ${icon}"></i>`,
|
||||
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);
|
||||
|
||||
// Determine the levels which are feasible
|
||||
let lmax = 0;
|
||||
const powerLevels = Array.fromRange(10).reduce((arr, i) => {
|
||||
if ( i < lvl ) return arr;
|
||||
const label = CONFIG.SW5E.powerLevels[i];
|
||||
const l = actorData.powers["power"+i] || {max: 0, override: null};
|
||||
let max = parseInt(l.override || l.max || 0);
|
||||
let slots = Math.clamped(parseInt(l.value || 0), 0, max);
|
||||
if ( max > 0 ) lmax = i;
|
||||
arr.push({
|
||||
level: i,
|
||||
label: i > 0 ? game.i18n.format('SW5E.PowerLevelSlot', {level: label, n: slots}) : label,
|
||||
canCast: canUpcast && (max > 0),
|
||||
hasSlots: slots > 0
|
||||
});
|
||||
return arr;
|
||||
}, []).filter(sl => sl.level <= lmax);
|
||||
|
||||
// If this character has pact slots, present them as an option for casting the power.
|
||||
const pact = actorData.powers.pact;
|
||||
if (pact.level >= lvl) {
|
||||
powerLevels.push({
|
||||
level: 'pact',
|
||||
label: `${game.i18n.format('SW5E.PowerLevelPact', {level: pact.level, n: pact.value})}`,
|
||||
canCast: canUpcast,
|
||||
hasSlots: pact.value > 0
|
||||
});
|
||||
}
|
||||
const canCast = powerLevels.some(l => l.hasSlots);
|
||||
|
||||
// Return merged data
|
||||
data = mergeObject(data, { hasPowerSlots: true, canUpcast, powerLevels });
|
||||
if ( !canCast ) data.errors.push("SW5E.PowerCastNoSlots");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the ability usage note that is displayed
|
||||
* @private
|
||||
*/
|
||||
static _getAbilityUseNote(item, uses, recharge) {
|
||||
|
||||
// Zero quantity
|
||||
const quantity = item.data.quantity;
|
||||
if ( quantity <= 0 ) return game.i18n.localize("SW5E.AbilityUseUnavailableHint");
|
||||
|
||||
// Abilities which use Recharge
|
||||
if ( !!recharge.value ) {
|
||||
return game.i18n.format(recharge.charged ? "SW5E.AbilityUseChargedHint" : "SW5E.AbilityUseRechargeHint", {
|
||||
type: item.type,
|
||||
})
|
||||
}
|
||||
|
||||
// Does not use any resource
|
||||
if ( !uses.per || !uses.max ) return "";
|
||||
|
||||
// Consumables
|
||||
if ( item.type === "consumable" ) {
|
||||
let str = "SW5E.AbilityUseNormalHint";
|
||||
if ( uses.value > 1 ) str = "SW5E.AbilityUseConsumableChargeHint";
|
||||
else if ( item.data.quantity === 1 && uses.autoDestroy ) str = "SW5E.AbilityUseConsumableDestroyHint";
|
||||
else if ( item.data.quantity > 1 ) str = "SW5E.AbilityUseConsumableQuantityHint";
|
||||
return game.i18n.format(str, {
|
||||
type: item.data.consumableType,
|
||||
value: uses.value,
|
||||
quantity: item.data.quantity,
|
||||
});
|
||||
}
|
||||
|
||||
// Other Items
|
||||
else {
|
||||
return game.i18n.format("SW5E.AbilityUseNormalHint", {
|
||||
type: item.type,
|
||||
value: uses.value,
|
||||
max: uses.max,
|
||||
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _handleSubmit(formData, item) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
export class ActorSheetFlags extends BaseEntitySheet {
|
||||
static get defaultOptions() {
|
||||
/**
|
||||
* An application class which provides advanced configuration for special character flags which modify an Actor
|
||||
* @extends {BaseEntitySheet}
|
||||
*/
|
||||
export default class ActorSheetFlags extends BaseEntitySheet {
|
||||
static get defaultOptions() {
|
||||
const options = super.defaultOptions;
|
||||
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.mpak.attack", label: "SW5E.BonusMSAttack"},
|
||||
{name: "data.bonuses.mpak.damage", label: "SW5E.BonusMSDamage"},
|
||||
{name: "data.bonuses.rpak.attack", label: "SW5E.BonusRSAttack"},
|
||||
{name: "data.bonuses.rpak.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.data = diffObject(this.object.overrides, updateData.data);
|
||||
}
|
||||
await actor.update(updateData, {diff: false});
|
||||
}
|
||||
}
|
||||
|
|
69
module/apps/long-rest.js
Normal file
69
module/apps/long-rest.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* A helper Dialog subclass for completing a long rest
|
||||
* @extends {Dialog}
|
||||
*/
|
||||
export default class LongRestDialog extends Dialog {
|
||||
constructor(actor, dialogData = {}, options = {}) {
|
||||
super(dialogData, options);
|
||||
this.actor = actor;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
template: "systems/sw5e/templates/apps/long-rest.html",
|
||||
classes: ["sw5e", "dialog"]
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
const variant = game.settings.get("sw5e", "restVariant");
|
||||
data.promptNewDay = variant !== "gritty"; // It's always a new day when resting 1 week
|
||||
data.newDay = variant === "normal"; // It's probably a new day when resting normally (8 hours)
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A helper constructor function which displays the Long Rest confirmation dialog and returns a Promise once it's
|
||||
* workflow has been resolved.
|
||||
* @param {Actor5e} actor
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async longRestDialog({ actor } = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, {
|
||||
title: "Long Rest",
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: "Rest",
|
||||
callback: html => {
|
||||
let newDay = false;
|
||||
if (game.settings.get("sw5e", "restVariant") === "normal")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
else if(game.settings.get("sw5e", "restVariant") === "gritty")
|
||||
newDay = true;
|
||||
resolve(newDay);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: "Cancel",
|
||||
callback: reject
|
||||
}
|
||||
},
|
||||
default: 'rest',
|
||||
close: reject
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
import LongRestDialog from "./long-rest.js";
|
||||
|
||||
/**
|
||||
* A helper Dialog subclass for rolling Hit Dice on short rest
|
||||
* @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: '<i class="fas fa-bed"></i>',
|
||||
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: '<i class="fas fa-times"></i>',
|
||||
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 = `<p>Take a long rest?</p><p>On a long rest you will recover hit points, half your maximum hit dice,
|
||||
class resources, limited use item charges, and power slots.</p>`;
|
||||
return new Promise((resolve, reject) => {
|
||||
new Dialog({
|
||||
title: "Long Rest",
|
||||
content: content,
|
||||
buttons: {
|
||||
rest: {
|
||||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: "Rest",
|
||||
callback: resolve
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: "Cancel",
|
||||
callback: reject
|
||||
},
|
||||
},
|
||||
default: 'rest',
|
||||
close: reject
|
||||
}, {classes: ["sw5e", "dialog"]}).render(true);
|
||||
});
|
||||
console.warn("WARNING! ShortRestDialog.longRestDialog has been deprecated, use LongRestDialog.longRestDialog instead.");
|
||||
return LongRestDialog.longRestDialog(...arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
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
|
||||
// Ensure it is an un-modified d20 roll
|
||||
const isD20 = (d.faces === 20) && ( d.results.length === 1 );
|
||||
if ( !isD20 ) return;
|
||||
const isModifiedRoll = ("success" in d.rolls[0]) || d.options.marginSuccess || d.options.marginFailure;
|
||||
if ( isModifiedRoll ) return;
|
||||
|
||||
|
@ -60,32 +60,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: '<i class="fas fa-user-minus"></i>',
|
||||
condition: canApply,
|
||||
callback: li => Actor5e.applyDamage(li, 1)
|
||||
callback: li => applyChatCardDamage(li, 1)
|
||||
},
|
||||
{
|
||||
name: game.i18n.localize("SW5E.ChatContextHealing"),
|
||||
icon: '<i class="fas fa-user-plus"></i>',
|
||||
condition: canApply,
|
||||
callback: li => Actor5e.applyDamage(li, -1)
|
||||
callback: li => applyChatCardDamage(li, -1)
|
||||
},
|
||||
{
|
||||
name: game.i18n.localize("SW5E.ChatContextDoubleDamage"),
|
||||
icon: '<i class="fas fa-user-injured"></i>',
|
||||
condition: canApply,
|
||||
callback: li => Actor5e.applyDamage(li, 2)
|
||||
callback: li => applyChatCardDamage(li, 2)
|
||||
},
|
||||
{
|
||||
name: game.i18n.localize("SW5E.ChatContextHalfDamage"),
|
||||
icon: '<i class="fas fa-user-shield"></i>',
|
||||
condition: canApply,
|
||||
callback: li => Actor5e.applyDamage(li, 0.5)
|
||||
callback: li => applyChatCardDamage(li, 0.5)
|
||||
}
|
||||
);
|
||||
return options;
|
||||
};
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Apply rolled dice damage to the token or tokens which are currently controlled.
|
||||
* This allows for damage to be scaled by a multiplier to account for healing, critical hits, or resistance
|
||||
*
|
||||
* @param {HTMLElement} roll The chat entry which contains the roll data
|
||||
* @param {Number} multiplier A damage multiplier to apply to the rolled damage.
|
||||
* @return {Promise}
|
||||
*/
|
||||
function applyChatCardDamage(roll, multiplier) {
|
||||
const amount = roll.find('.dice-total').text();
|
||||
return Promise.all(canvas.tokens.controlled.map(t => {
|
||||
const a = t.actor;
|
||||
return a.applyDamage(amount, multiplier);
|
||||
}));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -11,6 +11,48 @@ export const _getInitiativeFormula = function(combatant) {
|
|||
const init = actor.data.data.attributes.init;
|
||||
const 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);
|
||||
|
||||
// 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;
|
||||
};
|
|
@ -2,14 +2,14 @@
|
|||
export const SW5E = {};
|
||||
|
||||
// ASCII Artwork
|
||||
SW5E.ASCII = `_______________________________
|
||||
SW5E.ASCII = `__________________________________________
|
||||
_
|
||||
| |
|
||||
___| |_ __ _ _ ____ ____ _ _ __ ___
|
||||
/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __|
|
||||
\__ \ || (_) | | \ V V / (_) | | \__ \
|
||||
|___/\__\__/_|_| \_/\_/ \__/_|_| |___/
|
||||
_______________________________`;
|
||||
__________________________________________`;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,15 @@ SW5E.abilities = {
|
|||
"cha": "SW5E.AbilityCha"
|
||||
};
|
||||
|
||||
SW5E.abilityAbbreviations = {
|
||||
"str": "SW5E.AbilityStrAbbr",
|
||||
"dex": "SW5E.AbilityDexAbbr",
|
||||
"con": "SW5E.AbilityConAbbr",
|
||||
"int": "SW5E.AbilityIntAbbr",
|
||||
"wis": "SW5E.AbilityWisAbbr",
|
||||
"cha": "SW5E.AbilityChaAbbr"
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -46,7 +55,7 @@ SW5E.alignments = {
|
|||
|
||||
SW5E.weaponProficiencies = {
|
||||
"sim": "SW5E.WeaponSimpleProficiency",
|
||||
"mar": "SW5E.WeaponMartialProficiency",
|
||||
"mar": "SW5E.WeaponMartialProficiency"
|
||||
};
|
||||
|
||||
SW5E.toolProficiencies = {
|
||||
|
@ -119,9 +128,21 @@ SW5E.abilityActivationTypes = {
|
|||
"day": SW5E.timePeriods.day,
|
||||
"special": SW5E.timePeriods.spec,
|
||||
"legendary": "SW5E.LegAct",
|
||||
"lair": "SW5E.LairAct"
|
||||
"lair": "SW5E.LairAct",
|
||||
"crew": "SW5E.VehicleCrewAction"
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
SW5E.abilityConsumptionTypes = {
|
||||
"ammo": "SW5E.ConsumeAmmunition",
|
||||
"attribute": "SW5E.ConsumeAttribute",
|
||||
"material": "SW5E.ConsumeMaterial",
|
||||
"charges": "SW5E.ConsumeCharges"
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// Creature Sizes
|
||||
|
@ -196,7 +217,8 @@ SW5E.equipmentTypes = {
|
|||
"natural": "SW5E.EquipmentNatural",
|
||||
"shield": "SW5E.EquipmentShield",
|
||||
"clothing": "SW5E.EquipmentClothing",
|
||||
"trinket": "SW5E.EquipmentTrinket"
|
||||
"trinket": "SW5E.EquipmentTrinket",
|
||||
"vehicle": "SW5E.EquipmentVehicle"
|
||||
};
|
||||
|
||||
|
||||
|
@ -231,7 +253,6 @@ SW5E.consumableTypes = {
|
|||
"trinket": "SW5E.ConsumableTrinket"
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -261,10 +282,14 @@ SW5E.damageTypes = {
|
|||
"sonic": "SW5E.DamageSonic"
|
||||
};
|
||||
|
||||
// Damage Resistance Types
|
||||
SW5E.damageResistanceTypes = mergeObject(duplicate(SW5E.damageTypes), {
|
||||
"physical": "SW5E.DamagePhysical"
|
||||
});
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// armor Types
|
||||
SW5E.armorpropertiesTypes = {
|
||||
SW5E.armorPropertiesTypes = {
|
||||
"Absorptive": "SW5E.ArmorProperAbsorptive",
|
||||
"Agile": "SW5E.ArmorProperAgile",
|
||||
"Anchor": "SW5E.ArmorProperAnchor",
|
||||
|
@ -315,7 +340,8 @@ SW5E.distanceUnits = {
|
|||
*/
|
||||
SW5E.encumbrance = {
|
||||
currencyPerWeight: 50,
|
||||
strMultiplier: 15
|
||||
strMultiplier: 15,
|
||||
vehicleWeightMultiplier: 2000 // 2000 lbs in a ton
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -435,7 +461,7 @@ SW5E.powerPreparationModes = {
|
|||
"prepared": "SW5E.PowerPrepPrepared"
|
||||
};
|
||||
|
||||
SW5E.powerUpcastModes = ["always"];
|
||||
SW5E.powerUpcastModes = ["always", "pact", "prepared"];
|
||||
|
||||
|
||||
SW5E.powerProgression = {
|
||||
|
@ -604,12 +630,28 @@ SW5E.proficiencyLevels = {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The amount of cover provided by an object.
|
||||
* In cases where multiple pieces of cover are
|
||||
* in play, we take the highest value.
|
||||
*/
|
||||
SW5E.cover = {
|
||||
0: 'SW5E.None',
|
||||
.5: 'SW5E.CoverHalf',
|
||||
.75: 'SW5E.CoverThreeQuarters',
|
||||
1: 'SW5E.CoverTotal'
|
||||
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
// Condition Types
|
||||
SW5E.conditionTypes = {
|
||||
"blinded": "SW5E.ConBlinded",
|
||||
"charmed": "SW5E.ConCharmed",
|
||||
"deafened": "SW5E.ConDeafened",
|
||||
"diseased": "SW5E.ConDiseased",
|
||||
"exhaustion": "SW5E.ConExhaustion",
|
||||
"frightened": "SW5E.ConFrightened",
|
||||
"grappled": "SW5E.ConGrappled",
|
||||
|
@ -768,8 +810,8 @@ SW5E.characterFlags = {
|
|||
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.",
|
||||
name: "SW5E.FlagsPowerfulBuild",
|
||||
hint: "SW5E.FlagsPowerfulBuildHint",
|
||||
section: "Racial Traits",
|
||||
type: Boolean
|
||||
},
|
||||
|
@ -798,40 +840,40 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"initiativeAdv": {
|
||||
name: "Advantage on Initiative",
|
||||
hint: "Provided by feats or magical items.",
|
||||
name: "SW5E.FlagsInitiativeAdv",
|
||||
hint: "SW5E.FlagsInitiativeAdvHint",
|
||||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"initiativeAlert": {
|
||||
name: "Alert Feat",
|
||||
hint: "Provides +5 to Initiative.",
|
||||
name: "SW5E.FlagsAlert",
|
||||
hint: "SW5E.FlagsAlertHint",
|
||||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"jackOfAllTrades": {
|
||||
name: "Jack of All Trades",
|
||||
hint: "Half-Proficiency to Ability Checks in which you are not already Proficient.",
|
||||
name: "SW5E.FlagsJOAT",
|
||||
hint: "SW5E.FlagsJOATHint",
|
||||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"observantFeat": {
|
||||
name: "Observant Feat",
|
||||
hint: "Provides a +5 to passive Perception and Investigation.",
|
||||
name: "SW5E.FlagsObservant",
|
||||
hint: "SW5E.FlagsObservantHint",
|
||||
skills: ['prc','inv'],
|
||||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"remarkableAthlete": {
|
||||
name: "Remarkable Athlete.",
|
||||
hint: "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
|
||||
name: "SW5E.FlagsRemarkableAthlete",
|
||||
hint: "SW5E.FlagsRemarkableAthleteHint",
|
||||
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",
|
||||
name: "SW5E.FlagsCritThreshold",
|
||||
hint: "SW5E.FlagsCritThresholdHint",
|
||||
section: "Feats",
|
||||
type: Number,
|
||||
placeholder: 20
|
||||
|
|
357
module/dice.js
357
module/dice.js
|
@ -1,112 +1,144 @@
|
|||
export class Dice5e {
|
||||
|
||||
/**
|
||||
* A standardized helper function for managing core 5e "d20 rolls"
|
||||
*
|
||||
* 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<Roll>}
|
||||
* @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 = {
|
||||
|
@ -119,7 +151,6 @@ export class Dice5e {
|
|||
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.Dice.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<Roll>}
|
||||
* @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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Dice5e } from "../dice.js";
|
||||
import { AbilityUseDialog } from "../apps/ability-use-dialog.js";
|
||||
import { AbilityTemplate } from "../pixi/ability-template.js";
|
||||
import {d20Roll, damageRoll} from "../dice.js";
|
||||
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
||||
import AbilityTemplate from "../pixi/ability-template.js";
|
||||
|
||||
/**
|
||||
* Override and extend the basic :class:`Item` implementation
|
||||
*/
|
||||
export class Item5e extends Item {
|
||||
export default class Item5e extends Item {
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Item Properties */
|
||||
|
@ -20,14 +20,34 @@ export class Item5e extends Item {
|
|||
if (!("ability" in itemData)) return null;
|
||||
|
||||
// Case 1 - defined directly by the item
|
||||
if ( itemData.ability ) return itemData.ability;
|
||||
if (itemData.ability) return itemData.ability;
|
||||
|
||||
// Case 2 - inferred from a parent actor
|
||||
else if ( this.actor ) {
|
||||
else if (this.actor) {
|
||||
const actorData = this.actor.data.data;
|
||||
if ( this.data.type === "power" ) return actorData.attributes.powercasting || "int";
|
||||
else if ( this.data.type === "tool" ) return "int";
|
||||
else return "str";
|
||||
|
||||
// Powers - Use Actor powercasting modifier
|
||||
if (this.data.type === "power") return actorData.attributes.powercasting || "int";
|
||||
|
||||
// Tools - default to Intelligence
|
||||
else if (this.data.type === "tool") return "int";
|
||||
|
||||
// Weapons
|
||||
else if (this.data.type === "weapon") {
|
||||
const wt = itemData.weaponType;
|
||||
|
||||
// Melee weapons - Str or Dex if Finesse (PHB pg. 147)
|
||||
if ( ["simpleVW", "martialVW", "simpleLW", "martialLW"].includes(wt) ) {
|
||||
if (itemData.properties.fin === true) { // Finesse weapons
|
||||
return (actorData.abilities["dex"].mod >= actorData.abilities["str"].mod) ? "dex" : "str";
|
||||
}
|
||||
return "str";
|
||||
}
|
||||
|
||||
// Ranged weapons - Dex (PH p.194)
|
||||
else if ( ["simpleB", "martialB"].includes(wt) ) return "dex";
|
||||
}
|
||||
return "str";
|
||||
}
|
||||
|
||||
// Case 3 - unknown
|
||||
|
@ -149,15 +169,16 @@ export class Item5e extends Item {
|
|||
arr.push(c[0].titleCase().slice(0, 1));
|
||||
return arr;
|
||||
}, []);
|
||||
labels.materials = data?.materials?.value ?? null;
|
||||
}
|
||||
|
||||
// Feat Items
|
||||
else if ( itemData.type === "feat" ) {
|
||||
const act = data.activation;
|
||||
if ( act && (act.type === C.abilityActivationTypes.legendary) ) labels.featType = "Legendary Action";
|
||||
else if ( act && (act.type === C.abilityActivationTypes.lair) ) labels.featType = "Lair Action";
|
||||
else if ( act && act.type ) labels.featType = data.damage.length ? "Attack" : "Action";
|
||||
else labels.featType = "Passive";
|
||||
if ( act && (act.type === C.abilityActivationTypes.legendary) ) labels.featType = game.i18n.localize("SW5E.LegendaryActionLabel");
|
||||
else if ( act && (act.type === C.abilityActivationTypes.lair) ) labels.featType = game.i18n.localize("SW5E.LairActionLabel");
|
||||
else if ( act && act.type ) labels.featType = game.i18n.localize(data.damage.length ? "SW5E.Attack" : "SW5E.Action");
|
||||
else labels.featType = game.i18n.localize("SW5E.Passive");
|
||||
}
|
||||
|
||||
// Species Items
|
||||
|
@ -167,7 +188,7 @@ export class Item5e extends Item {
|
|||
|
||||
// Equipment Items
|
||||
else if ( itemData.type === "equipment" ) {
|
||||
labels.armor = data.armor.value ? `${data.armor.value} AC` : "";
|
||||
labels.armor = data.armor.value ? `${data.armor.value} ${game.i18n.localize("SW5E.AC")}` : "";
|
||||
}
|
||||
|
||||
// Activated Items
|
||||
|
@ -201,7 +222,7 @@ export class Item5e extends Item {
|
|||
|
||||
// Recharge Label
|
||||
let chg = data.recharge || {};
|
||||
labels.recharge = `Recharge [${chg.value}${parseInt(chg.value) < 6 ? "+" : ""}]`;
|
||||
labels.recharge = `${game.i18n.localize("SW5E.Recharge")} [${chg.value}${parseInt(chg.value) < 6 ? "+" : ""}]`;
|
||||
}
|
||||
|
||||
// Item Actions
|
||||
|
@ -216,7 +237,7 @@ export class Item5e extends Item {
|
|||
} else { // Un-owned items
|
||||
if ( save.scaling !== "flat" ) save.dc = null;
|
||||
}
|
||||
labels.save = save.ability ? `DC ${save.dc || ""} ${C.abilities[save.ability]}` : "";
|
||||
labels.save = save.ability ? `${game.i18n.localize("SW5E.AbbreviationDC")} ${save.dc || ""} ${C.abilities[save.ability]}` : "";
|
||||
|
||||
// Damage
|
||||
let dam = data.damage || {};
|
||||
|
@ -234,9 +255,13 @@ export class Item5e extends Item {
|
|||
|
||||
/**
|
||||
* Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options
|
||||
* @param {boolean} [configureDialog] Display a configuration dialog for the item roll, if applicable?
|
||||
* @param {string} [rollMode] The roll display mode with which to display (or not) the card
|
||||
* @param {boolean} [createMessage] Whether to automatically create a chat message (if true) or simply return
|
||||
* the prepared chat message data (if false).
|
||||
* @return {Promise}
|
||||
*/
|
||||
async roll({configureDialog=true}={}) {
|
||||
async roll({configureDialog=true, rollMode=null, createMessage=true}={}) {
|
||||
|
||||
// Basic template rendering data
|
||||
const token = this.actor.token;
|
||||
|
@ -259,10 +284,17 @@ export class Item5e extends Item {
|
|||
if (this.data.type === "feat") {
|
||||
let configured = await this._rollFeat(configureDialog);
|
||||
if ( configured === false ) return;
|
||||
} else if ( this.data.type === "consumable" ) {
|
||||
let configured = await this._rollConsumable(configureDialog);
|
||||
if ( configured === false ) return;
|
||||
}
|
||||
|
||||
// For items which consume a resource, handle that here
|
||||
const allowed = await this._handleResourceConsumption({isCard: true, isAttack: false});
|
||||
if ( allowed === false ) return;
|
||||
|
||||
// Render the chat card template
|
||||
const templateType = ["tool", "consumable"].includes(this.data.type) ? this.data.type : "item";
|
||||
const templateType = ["tool"].includes(this.data.type) ? this.data.type : "item";
|
||||
const template = `systems/sw5e/templates/chat/${templateType}-card.html`;
|
||||
const html = await renderTemplate(template, templateData);
|
||||
|
||||
|
@ -279,12 +311,90 @@ export class Item5e extends Item {
|
|||
};
|
||||
|
||||
// Toggle default roll mode
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
if ( ["gmroll", "blindroll"].includes(rollMode) ) chatData["whisper"] = ChatMessage.getWhisperIDs("GM");
|
||||
rollMode = rollMode || game.settings.get("core", "rollMode");
|
||||
if ( ["gmroll", "blindroll"].includes(rollMode) ) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM");
|
||||
if ( rollMode === "blindroll" ) chatData["blind"] = true;
|
||||
|
||||
// Create the chat message
|
||||
return ChatMessage.create(chatData);
|
||||
if ( createMessage ) return ChatMessage.create(chatData);
|
||||
else return chatData;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* For items which consume a resource, handle the consumption of that resource when the item is used.
|
||||
* There are four types of ability consumptions which are handled:
|
||||
* 1. Ammunition (on attack rolls)
|
||||
* 2. Attributes (on card usage)
|
||||
* 3. Materials (on card usage)
|
||||
* 4. Item Charges (on card usage)
|
||||
*
|
||||
* @param {boolean} isCard Is the item card being played?
|
||||
* @param {boolean} isAttack Is an attack roll being made?
|
||||
* @return {Promise<boolean>} Can the item card or attack roll be allowed to proceed?
|
||||
* @private
|
||||
*/
|
||||
async _handleResourceConsumption({isCard=false, isAttack=false}={}) {
|
||||
const itemData = this.data.data;
|
||||
const consume = itemData.consume || {};
|
||||
if ( !consume.type ) return true;
|
||||
const actor = this.actor;
|
||||
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
|
||||
const amount = parseInt(consume.amount || 1);
|
||||
|
||||
// Only handle certain types for certain actions
|
||||
if ( ((consume.type === "ammo") && !isAttack ) || ((consume.type !== "ammo") && !isCard) ) return true;
|
||||
|
||||
// No consumed target set
|
||||
if ( !consume.target ) {
|
||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Identify the consumed resource and it's quantity
|
||||
let consumed = null;
|
||||
let quantity = 0;
|
||||
switch ( consume.type ) {
|
||||
case "attribute":
|
||||
consumed = getProperty(actor.data.data, consume.target);
|
||||
quantity = consumed || 0;
|
||||
break;
|
||||
case "ammo":
|
||||
case "material":
|
||||
consumed = actor.items.get(consume.target);
|
||||
quantity = consumed ? consumed.data.data.quantity : 0;
|
||||
break;
|
||||
case "charges":
|
||||
consumed = actor.items.get(consume.target);
|
||||
quantity = consumed ? consumed.data.data.uses.value : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify that the consumed resource is available
|
||||
if ( [null, undefined].includes(consumed) ) {
|
||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoSource", {name: this.name, type: typeLabel}));
|
||||
return false;
|
||||
}
|
||||
let remaining = quantity - amount;
|
||||
if ( remaining < 0) {
|
||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoQuantity", {name: this.name, type: typeLabel}));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the consumed resource
|
||||
switch ( consume.type ) {
|
||||
case "attribute":
|
||||
await this.actor.update({[`data.${consume.target}`]: remaining});
|
||||
break;
|
||||
case "ammo":
|
||||
case "material":
|
||||
await consumed.update({"data.quantity": remaining});
|
||||
break;
|
||||
case "charges":
|
||||
await consumed.update({"data.uses.value": remaining});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -298,27 +408,35 @@ export class Item5e extends Item {
|
|||
if ( this.data.type !== "feat" ) throw new Error("Wrong Item type");
|
||||
|
||||
// Configure whether to consume a limited use or to place a template
|
||||
const usesRecharge = !!this.data.data.recharge.value;
|
||||
const charge = this.data.data.recharge;
|
||||
const uses = this.data.data.uses;
|
||||
let usesCharges = !!uses.per && (uses.max > 0);
|
||||
let placeTemplate = false;
|
||||
let consume = usesRecharge || usesCharges;
|
||||
let consume = charge.value || usesCharges;
|
||||
|
||||
// Determine whether the feat uses charges
|
||||
configureDialog = configureDialog && (consume || this.hasAreaTarget);
|
||||
if ( configureDialog ) {
|
||||
const usage = await AbilityUseDialog.create(this);
|
||||
if ( usage === null ) return false;
|
||||
consume = Boolean(usage.get("consume"));
|
||||
consume = Boolean(usage.get("consumeUse"));
|
||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
||||
}
|
||||
|
||||
// Update Item data
|
||||
const current = getProperty(this.data, "data.uses.value") || 0;
|
||||
if ( consume && usesRecharge ) {
|
||||
await this.update({"data.recharge.charged": false});
|
||||
if ( consume && charge.value ) {
|
||||
if ( !charge.charged ) {
|
||||
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||
return false;
|
||||
}
|
||||
else await this.update({"data.recharge.charged": false});
|
||||
}
|
||||
else if ( consume && usesCharges ) {
|
||||
if ( uses.value <= 0 ) {
|
||||
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||
return false;
|
||||
}
|
||||
await this.update({"data.uses.value": Math.max(current - 1, 0)});
|
||||
}
|
||||
|
||||
|
@ -340,14 +458,13 @@ export class Item5e extends Item {
|
|||
* @param {Object} htmlOptions Options used by the TextEditor.enrichHTML function
|
||||
* @return {Object} An object of chat data to render
|
||||
*/
|
||||
getChatData(htmlOptions) {
|
||||
getChatData(htmlOptions={}) {
|
||||
const data = duplicate(this.data.data);
|
||||
const labels = this.labels;
|
||||
|
||||
// Rich text description
|
||||
data.description.value = TextEditor.enrichHTML(data.description.value, htmlOptions);
|
||||
|
||||
|
||||
// Item type specific properties
|
||||
const props = [];
|
||||
const fn = this[`_${this.data.type}ChatData`];
|
||||
|
@ -356,16 +473,16 @@ export class Item5e extends Item {
|
|||
// General equipment properties
|
||||
if ( data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type) ) {
|
||||
props.push(
|
||||
data.equipped ? "Equipped" : "Not Equipped",
|
||||
data.proficient ? "Proficient": "Not Proficient",
|
||||
game.i18n.localize(data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped"),
|
||||
game.i18n.localize(data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient"),
|
||||
);
|
||||
}
|
||||
|
||||
// Ability activation properties
|
||||
if ( data.hasOwnProperty("activation") ) {
|
||||
props.push(
|
||||
labels.activation + (data.activation?.condition ? ` (${data.activation.condition})` : ""),
|
||||
labels.target,
|
||||
labels.activation,
|
||||
labels.range,
|
||||
labels.duration
|
||||
);
|
||||
|
@ -386,7 +503,7 @@ export class Item5e extends Item {
|
|||
props.push(
|
||||
CONFIG.SW5E.equipmentTypes[data.armor.type],
|
||||
labels.armor || null,
|
||||
data.stealth.value ? "Stealth Disadvantage" : null,
|
||||
data.stealth.value ? game.i18n.localize("SW5E.StealthDisadvantage") : null
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -411,7 +528,7 @@ export class Item5e extends Item {
|
|||
_consumableChatData(data, labels, props) {
|
||||
props.push(
|
||||
CONFIG.SW5E.consumableTypes[data.consumableType],
|
||||
data.uses.value + "/" + data.uses.max + " Charges"
|
||||
data.uses.value + "/" + data.uses.max + " " + game.i18n.localize("SW5E.Charges")
|
||||
);
|
||||
data.hasCharges = data.uses.value >= 0;
|
||||
}
|
||||
|
@ -437,8 +554,8 @@ export class Item5e extends Item {
|
|||
*/
|
||||
_lootChatData(data, labels, props) {
|
||||
props.push(
|
||||
"Loot",
|
||||
data.weight ? data.weight + " lbs." : null
|
||||
game.i18n.localize("SW5E.ItemTypeLoot"),
|
||||
data.weight ? data.weight + " " + game.i18n.localize("SW5E.AbbreviationLbs") : null
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -452,7 +569,7 @@ export class Item5e extends Item {
|
|||
_powerChatData(data, labels, props) {
|
||||
props.push(
|
||||
labels.level,
|
||||
labels.components,
|
||||
labels.components + (labels.materials ? ` (${labels.materials})` : "")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -472,17 +589,19 @@ export class Item5e extends Item {
|
|||
|
||||
/**
|
||||
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
||||
* Rely upon the d20Roll logic for the core implementation
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||
* @param {object} options Roll options which are configured and provided to the d20Roll function
|
||||
* @return {Promise<Roll|null>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
rollAttack(options={}) {
|
||||
async rollAttack(options={}) {
|
||||
const itemData = this.data.data;
|
||||
const actorData = this.actor.data.data;
|
||||
const flags = this.actor.data.flags.sw5e || {};
|
||||
if ( !this.hasAttack ) {
|
||||
throw new Error("You may not place an Attack Roll with this Item.");
|
||||
}
|
||||
let title = `${this.name} - ${game.i18n.localize("SW5E.AttackRoll")}`;
|
||||
const rollData = this.getRollData();
|
||||
|
||||
// Define Roll bonuses
|
||||
|
@ -492,26 +611,44 @@ export class Item5e extends Item {
|
|||
}
|
||||
|
||||
// Attack Bonus
|
||||
const actorBonus = actorData.bonuses[itemData.actionType] || {};
|
||||
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
|
||||
if ( itemData.attackBonus || actorBonus.attack ) {
|
||||
parts.push("@atk");
|
||||
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
|
||||
}
|
||||
|
||||
// Ammunition Bonus
|
||||
delete this._ammo;
|
||||
const consume = itemData.consume;
|
||||
if ( consume?.type === "ammo" ) {
|
||||
const ammo = this.actor.items.get(consume.target);
|
||||
const q = ammo.data.data.quantity;
|
||||
if ( q && (q - consume.amount >= 0) ) {
|
||||
let ammoBonus = ammo.data.data.attackBonus;
|
||||
if ( ammoBonus ) {
|
||||
parts.push("@ammo");
|
||||
rollData["ammo"] = ammoBonus;
|
||||
title += ` [${ammo.name}]`;
|
||||
this._ammo = ammo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compose roll options
|
||||
const rollConfig = {
|
||||
event: options.event,
|
||||
const rollConfig = mergeObject({
|
||||
parts: parts,
|
||||
actor: this.actor,
|
||||
data: rollData,
|
||||
title: `${this.name} - Attack Roll`,
|
||||
title: title,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
dialogOptions: {
|
||||
width: 400,
|
||||
top: options.event ? options.event.clientY - 80 : null,
|
||||
left: window.innerWidth - 710
|
||||
}
|
||||
};
|
||||
},
|
||||
messageData: {"flags.sw5e.roll": {type: "attack", itemId: this.id }}
|
||||
}, options);
|
||||
rollConfig.event = options.event;
|
||||
|
||||
// Expanded weapon critical threshold
|
||||
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
|
||||
|
@ -529,16 +666,22 @@ export class Item5e extends Item {
|
|||
if ( flags.halflingLucky ) rollConfig.halflingLucky = true;
|
||||
|
||||
// Invoke the d20 roll helper
|
||||
return Dice5e.d20Roll(rollConfig);
|
||||
const roll = await d20Roll(rollConfig);
|
||||
if ( roll === false ) return null;
|
||||
|
||||
// Handle resource consumption if the attack roll was made
|
||||
const allowed = await this._handleResourceConsumption({isCard: false, isAttack: true});
|
||||
if ( allowed === false ) return null;
|
||||
return roll;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Place a damage roll using an item (weapon, feat, power, or equipment)
|
||||
* Rely upon the Dice5e.damageRoll logic for the core implementation
|
||||
* Rely upon the damageRoll logic for the core implementation
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
rollDamage({event, powerLevel=null, versatile=false}={}) {
|
||||
const itemData = this.data.data;
|
||||
|
@ -546,32 +689,54 @@ export class Item5e extends Item {
|
|||
if ( !this.hasDamage ) {
|
||||
throw new Error("You may not make a Damage Roll with this Item.");
|
||||
}
|
||||
const messageData = {"flags.sw5e.roll": {type: "damage", itemId: this.id }};
|
||||
|
||||
// Get roll data
|
||||
const rollData = this.getRollData();
|
||||
if ( powerLevel ) rollData.item.level = powerLevel;
|
||||
|
||||
// Get message labels
|
||||
const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`;
|
||||
let flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title;
|
||||
|
||||
// Define Roll parts
|
||||
const parts = itemData.damage.parts.map(d => d[0]);
|
||||
if ( versatile && itemData.damage.versatile ) parts[0] = itemData.damage.versatile;
|
||||
|
||||
// Adjust damage from versatile usage
|
||||
if ( versatile && itemData.damage.versatile ) {
|
||||
parts[0] = itemData.damage.versatile;
|
||||
messageData["flags.sw5e.roll"].versatile = true;
|
||||
}
|
||||
|
||||
// Scale damage from up-casting powers
|
||||
if ( (this.data.type === "power") ) {
|
||||
if ( (itemData.scaling.mode === "atwill") ) {
|
||||
const lvl = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
|
||||
this._scaleAtWillDamage(parts, lvl, itemData.scaling.formula );
|
||||
} else if ( powerLevel && (itemData.scaling.mode === "level") && itemData.scaling.formula ) {
|
||||
this._scalePowerDamage(parts, itemData.level, powerLevel, itemData.scaling.formula );
|
||||
if ( (itemData.scaling.mode === "cantrip") ) {
|
||||
const level = this.actor.data.type === "character" ? actorData.details.level : actorData.details.powerLevel;
|
||||
this._scaleCantripDamage(parts, itemData.scaling.formula, level, rollData);
|
||||
}
|
||||
else if ( powerLevel && (itemData.scaling.mode === "level") && itemData.scaling.formula ) {
|
||||
const scaling = itemData.scaling.formula;
|
||||
this._scalePowerDamage(parts, itemData.level, powerLevel, scaling, rollData);
|
||||
}
|
||||
}
|
||||
|
||||
// Define Roll Data
|
||||
const actorBonus = actorData.bonuses[itemData.actionType] || {};
|
||||
const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
|
||||
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) {
|
||||
parts.push("@dmg");
|
||||
rollData["dmg"] = actorBonus.damage;
|
||||
}
|
||||
|
||||
// Ammunition Damage
|
||||
if ( this._ammo ) {
|
||||
parts.push("@ammo");
|
||||
rollData["ammo"] = this._ammo.data.data.damage.parts.map(p => p[0]).join("+");
|
||||
flavor += ` [${this._ammo.name}]`;
|
||||
delete this._ammo;
|
||||
}
|
||||
|
||||
// Call the roll helper utility
|
||||
const title = `${this.name} - Damage Roll`;
|
||||
const flavor = this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title;
|
||||
return Dice5e.damageRoll({
|
||||
return damageRoll({
|
||||
event: event,
|
||||
parts: parts,
|
||||
actor: this.actor,
|
||||
|
@ -583,7 +748,8 @@ export class Item5e extends Item {
|
|||
width: 400,
|
||||
top: event ? event.clientY - 80 : null,
|
||||
left: window.innerWidth - 710
|
||||
}
|
||||
},
|
||||
messageData
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -593,13 +759,24 @@ export class Item5e extends Item {
|
|||
* Adjust an at-will damage formula to scale it for higher level characters and monsters
|
||||
* @private
|
||||
*/
|
||||
_scaleAtWillDamage(parts, level, scale) {
|
||||
_scaleAtWillDamage(parts, scale, level, rollData) {
|
||||
const add = Math.floor((level + 1) / 6);
|
||||
if ( add === 0 ) return;
|
||||
if ( scale && (scale !== parts[0]) ) {
|
||||
parts[0] = parts[0] + " + " + scale.replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${add}d${d}`);
|
||||
} else {
|
||||
parts[0] = parts[0].replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${parseInt(nd)+add}d${d}`);
|
||||
|
||||
// FUTURE SOLUTION - 0.7.0 AND LATER
|
||||
if (isNewerVersion(game.data.version, "0.6.9")) {
|
||||
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData)
|
||||
|
||||
}
|
||||
|
||||
// LEGACY SOLUTION - 0.6.x AND OLDER
|
||||
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
|
||||
else {
|
||||
if ( scale && (scale !== parts[0]) ) {
|
||||
parts[0] = parts[0] + " + " + scale.replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${add}d${d}`);
|
||||
} else {
|
||||
parts[0] = parts[0].replace(new RegExp(Roll.diceRgx, "g"), (match, nd, d) => `${parseInt(nd)+add}d${d}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -611,13 +788,61 @@ export class Item5e extends Item {
|
|||
* @param {number} baseLevel The default power level
|
||||
* @param {number} powerLevel The casted power level
|
||||
* @param {string} formula The scaling formula
|
||||
* @param {object} rollData A data object that should be applied to the scaled damage roll
|
||||
* @return {string[]} The scaled roll parts
|
||||
* @private
|
||||
*/
|
||||
_scalePowerDamage(parts, baseLevel, powerLevel, formula) {
|
||||
_scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) {
|
||||
const upcastLevels = Math.max(powerLevel - baseLevel, 0);
|
||||
if ( upcastLevels === 0 ) return parts;
|
||||
const bonus = new Roll(formula).alter(0, upcastLevels);
|
||||
parts.push(bonus.formula);
|
||||
|
||||
// FUTURE SOLUTION - 0.7.0 AND LATER
|
||||
if (isNewerVersion(game.data.version, "0.6.9")) {
|
||||
this._scaleDamage(parts, formula, upcastLevels, rollData);
|
||||
}
|
||||
|
||||
// LEGACY SOLUTION - 0.6.x AND OLDER
|
||||
// TODO: Deprecate the legacy solution one FVTT 0.7.x is RELEASE
|
||||
else {
|
||||
const bonus = new Roll(formula);
|
||||
bonus.alter(0, upcastLevels);
|
||||
parts.push(bonus.formula);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Scale an array of damage parts according to a provided scaling formula and scaling multiplier
|
||||
* @param {string[]} parts Initial roll parts
|
||||
* @param {string} scaling A scaling formula
|
||||
* @param {number} times A number of times to apply the scaling formula
|
||||
* @param {object} rollData A data object that should be applied to the scaled damage roll
|
||||
* @return {string[]} The scaled roll parts
|
||||
* @private
|
||||
*/
|
||||
_scaleDamage(parts, scaling, times, rollData) {
|
||||
if ( times <= 0 ) return parts;
|
||||
const p0 = new Roll(parts[0], rollData);
|
||||
const s = new Roll(scaling, rollData).alter(times);
|
||||
|
||||
// Attempt to simplify by combining like dice terms
|
||||
let simplified = false;
|
||||
if ( (s.terms[0] instanceof Die) && (s.terms.length === 1) ) {
|
||||
const d0 = p0.terms[0];
|
||||
const s0 = s.terms[0];
|
||||
if ( (d0 instanceof Die) && (d0.faces === s0.faces) && d0.modifiers.equals(s0.modifiers) ) {
|
||||
d0.number += s0.number;
|
||||
parts[0] = p0.formula;
|
||||
simplified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise add to the first part
|
||||
if ( !simplified ) {
|
||||
parts[0] = `${parts[0]} + ${s.formula}`;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
@ -625,8 +850,8 @@ export class Item5e extends Item {
|
|||
|
||||
/**
|
||||
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
||||
*
|
||||
* Rely upon the d20Roll logic for the core implementation
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
async rollFormula(options={}) {
|
||||
|
@ -636,14 +861,16 @@ export class Item5e extends Item {
|
|||
|
||||
// Define Roll Data
|
||||
const rollData = this.getRollData();
|
||||
const title = `${this.name} - Other Formula`;
|
||||
if ( options.powerLevel ) rollData.item.level = options.powerLevel;
|
||||
const title = `${this.name} - ${game.i18n.localize("SW5E.OtherFormula")}`;
|
||||
|
||||
// Invoke the roll and submit it to chat
|
||||
const roll = new Roll(rollData.item.formula, rollData).roll();
|
||||
roll.toMessage({
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
flavor: this.data.data.chatFlavor || title,
|
||||
rollMode: game.settings.get("core", "rollMode")
|
||||
rollMode: game.settings.get("core", "rollMode"),
|
||||
messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }}
|
||||
});
|
||||
return roll;
|
||||
}
|
||||
|
@ -652,58 +879,77 @@ export class Item5e extends Item {
|
|||
|
||||
/**
|
||||
* Use a consumable item, deducting from the quantity or charges of the item.
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance or null
|
||||
* @param {boolean} configureDialog Whether to show a configuration dialog
|
||||
* @return {boolean} Whether further execution should be prevented
|
||||
* @private
|
||||
*/
|
||||
async rollConsumable(options={}) {
|
||||
async _rollConsumable(configureDialog) {
|
||||
if ( this.data.type !== "consumable" ) throw new Error("Wrong Item type");
|
||||
const itemData = this.data.data;
|
||||
|
||||
// Dispatch a damage roll
|
||||
let roll = null;
|
||||
if ( itemData.damage.parts.length ) {
|
||||
roll = await this.rollDamage(options);
|
||||
// Determine whether to deduct uses of the item
|
||||
const uses = itemData.uses || {};
|
||||
const autoDestroy = uses.autoDestroy;
|
||||
let usesCharges = !!uses.per && (uses.max > 0);
|
||||
const recharge = itemData.recharge || {};
|
||||
const usesRecharge = !!recharge.value;
|
||||
|
||||
// Display a configuration dialog to confirm the usage
|
||||
let placeTemplate = false;
|
||||
let consume = uses.autoUse || true;
|
||||
if ( configureDialog ) {
|
||||
const usage = await AbilityUseDialog.create(this);
|
||||
if ( usage === null ) return false;
|
||||
consume = Boolean(usage.get("consumeUse"));
|
||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
||||
}
|
||||
|
||||
// Dispatch an other formula
|
||||
if ( itemData.formula ) {
|
||||
roll = await this.rollFormula(options);
|
||||
}
|
||||
|
||||
// Deduct consumed charges from the item
|
||||
if ( itemData.uses.autoUse ) {
|
||||
let q = itemData.quantity;
|
||||
let c = itemData.uses.value;
|
||||
|
||||
// Deduct an item quantity
|
||||
if ( c <= 1 && q > 1 ) {
|
||||
await this.update({
|
||||
'data.quantity': Math.max(q - 1, 0),
|
||||
'data.uses.value': itemData.uses.max
|
||||
});
|
||||
}
|
||||
|
||||
// Optionally destroy the item
|
||||
else if ( c <= 1 && q <= 1 && itemData.uses.autoDestroy ) {
|
||||
await this.actor.deleteOwnedItem(this.id);
|
||||
}
|
||||
|
||||
// Deduct the remaining charges
|
||||
// Update Item data
|
||||
if ( consume ) {
|
||||
const current = uses.value || 0;
|
||||
const remaining = usesCharges ? Math.max(current - 1, 0) : current;
|
||||
if ( usesRecharge ) await this.update({"data.recharge.charged": false});
|
||||
else {
|
||||
await this.update({'data.uses.value': Math.max(c - 1, 0)});
|
||||
const q = itemData.quantity;
|
||||
// Case 1, reduce charges
|
||||
if ( remaining ) {
|
||||
await this.update({"data.uses.value": remaining});
|
||||
}
|
||||
// Case 2, reduce quantity
|
||||
else if ( q > 1 ) {
|
||||
await this.update({"data.quantity": q - 1, "data.uses.value": uses.max || 0});
|
||||
}
|
||||
// Case 3, destroy the item
|
||||
else if ( (q <= 1) && autoDestroy ) {
|
||||
await this.actor.deleteOwnedItem(this.id);
|
||||
}
|
||||
// Case 4, reduce item to 0 quantity and 0 charges
|
||||
else if ( (q === 1) ) {
|
||||
await this.update({"data.quantity": q - 1, "data.uses.value": 0});
|
||||
}
|
||||
// Case 5, item unusable, display warning and do nothing
|
||||
else {
|
||||
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return roll;
|
||||
}
|
||||
|
||||
// Maybe initiate template placement workflow
|
||||
if ( this.hasAreaTarget && placeTemplate ) {
|
||||
const template = AbilityTemplate.fromItem(this);
|
||||
if ( template ) template.drawPreview(event);
|
||||
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Perform an ability recharge test for an item which uses the d6 recharge mechanic
|
||||
* @prarm {Object} options
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
async rollRecharge(options={}) {
|
||||
async rollRecharge() {
|
||||
const data = this.data.data;
|
||||
if ( !data.recharge.value ) return;
|
||||
|
||||
|
@ -713,7 +959,7 @@ export class Item5e extends Item {
|
|||
|
||||
// Display a Chat Message
|
||||
const promises = [roll.toMessage({
|
||||
flavor: `${this.name} recharge check - ${success ? "success!" : "failure!"}`,
|
||||
flavor: `${game.i18n.format("SW5E.ItemRechargeCheck", {name: this.name})} - ${game.i18n.localize(success ? "SW5E.ItemRechargeSuccess" : "SW5E.ItemRechargeFailure")}`,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor, token: this.actor.token})
|
||||
})];
|
||||
|
||||
|
@ -725,10 +971,9 @@ export class Item5e extends Item {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Roll a Tool Check
|
||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||
* Roll a Tool Check. Rely upon the d20Roll logic for the core implementation
|
||||
* @prarm {Object} options Roll configuration options provided to the d20Roll function
|
||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
rollToolCheck(options={}) {
|
||||
if ( this.type !== "tool" ) throw "Wrong item type!";
|
||||
|
@ -736,24 +981,28 @@ export class Item5e extends Item {
|
|||
// Prepare roll data
|
||||
let rollData = this.getRollData();
|
||||
const parts = [`@mod`, "@prof"];
|
||||
const title = `${this.name} - Tool Check`;
|
||||
const title = `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`;
|
||||
|
||||
// Call the roll helper utility
|
||||
return Dice5e.d20Roll({
|
||||
event: options.event,
|
||||
// Compose the roll data
|
||||
const rollConfig = mergeObject({
|
||||
parts: parts,
|
||||
data: rollData,
|
||||
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
|
||||
title: title,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
flavor: `${this.name} - Tool Check`,
|
||||
flavor: `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`,
|
||||
dialogOptions: {
|
||||
width: 400,
|
||||
top: options.event ? options.event.clientY - 80 : null,
|
||||
left: window.innerWidth - 710,
|
||||
},
|
||||
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky" ) || false
|
||||
});
|
||||
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky" ) || false,
|
||||
messageData: {"flags.sw5e.roll": {type: "tool", itemId: this.id }}
|
||||
}, options);
|
||||
rollConfig.event = options.event;
|
||||
|
||||
// Call the roll helper utility
|
||||
return d20Roll(rollConfig);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -819,7 +1068,7 @@ export class Item5e extends Item {
|
|||
// Get the Item
|
||||
const item = actor.getOwnedItem(card.dataset.itemId);
|
||||
if ( !item ) {
|
||||
return ui.notifications.error(`The requested item ${card.dataset.itemId} no longer exists on Actor ${actor.name}`)
|
||||
return ui.notifications.error(game.i18n.format("SW5E.ActionWarningNoItem", {item: card.dataset.itemId, name: actor.name}))
|
||||
}
|
||||
const powerLevel = parseInt(card.dataset.powerLevel) || null;
|
||||
|
||||
|
@ -828,7 +1077,7 @@ export class Item5e extends Item {
|
|||
if ( isTargetted ) {
|
||||
targets = this._getChatCardTargets(card);
|
||||
if ( !targets.length ) {
|
||||
ui.notifications.warn(`You must have one or more controlled Tokens in order to use this option.`);
|
||||
ui.notifications.warn(game.i18n.localize("SW5E.ActionWarningNoToken"));
|
||||
return button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
@ -837,18 +1086,16 @@ export class Item5e extends Item {
|
|||
if ( action === "attack" ) await item.rollAttack({event});
|
||||
else if ( action === "damage" ) await item.rollDamage({event, powerLevel});
|
||||
else if ( action === "versatile" ) await item.rollDamage({event, powerLevel, versatile: true});
|
||||
else if ( action === "formula" ) await item.rollFormula({event});
|
||||
else if ( action === "formula" ) await item.rollFormula({event, powerLevel});
|
||||
|
||||
// Saving Throws for card targets
|
||||
else if ( action === "save" ) {
|
||||
for ( let t of targets ) {
|
||||
await t.rollAbilitySave(button.dataset.ability, {event});
|
||||
for ( let a of targets ) {
|
||||
const speaker = ChatMessage.getSpeaker({scene: canvas.scene, token: a.token});
|
||||
await a.rollAbilitySave(button.dataset.ability, { event, speaker });
|
||||
}
|
||||
}
|
||||
|
||||
// Consumable usage
|
||||
else if ( action === "consume" ) await item.rollConsumable({event});
|
||||
|
||||
// Tool usage
|
||||
else if ( action === "toolCheck" ) await item.rollToolCheck({event});
|
||||
|
||||
|
@ -919,4 +1166,56 @@ export class Item5e extends Item {
|
|||
if ( character && (controlled.length === 0) ) targets.push(character);
|
||||
return targets;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Factory Methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a consumable power scroll Item from a power Item.
|
||||
* @param {Item5e} power The power to be made into a scroll
|
||||
* @return {Item5e} The created scroll consumable item
|
||||
* @private
|
||||
*/
|
||||
static async createScrollFromPower(power) {
|
||||
|
||||
// Get power data
|
||||
const itemData = power instanceof Item5e ? power.data : power;
|
||||
const {actionType, description, source, activation, duration, target, range, damage, save, level} = itemData.data;
|
||||
|
||||
// Get scroll data
|
||||
const scrollUuid = CONFIG.SW5E.powerScrollIds[level];
|
||||
const scrollItem = await fromUuid(scrollUuid);
|
||||
const scrollData = scrollItem.data;
|
||||
delete scrollData._id;
|
||||
|
||||
// Split the scroll description into an intro paragraph and the remaining details
|
||||
const scrollDescription = scrollData.data.description.value;
|
||||
const pdel = '</p>';
|
||||
const scrollIntroEnd = scrollDescription.indexOf(pdel);
|
||||
const scrollIntro = scrollDescription.slice(0, scrollIntroEnd + pdel.length);
|
||||
const scrollDetails = scrollDescription.slice(scrollIntroEnd + pdel.length);
|
||||
|
||||
// Create a composite description from the scroll description and the power details
|
||||
const desc = `${scrollIntro}<hr/><h3>${itemData.name} (Level ${level})</h3><hr/>${description.value}<hr/><h3>Scroll Details</h3><hr/>${scrollDetails}`;
|
||||
|
||||
// Create the power scroll data
|
||||
const powerScrollData = mergeObject(scrollData, {
|
||||
name: `${game.i18n.localize("SW5E.PowerScroll")}: ${itemData.name}`,
|
||||
img: itemData.img,
|
||||
data: {
|
||||
"description.value": desc.trim(),
|
||||
source,
|
||||
actionType,
|
||||
activation,
|
||||
duration,
|
||||
target,
|
||||
range,
|
||||
damage,
|
||||
save,
|
||||
level
|
||||
}
|
||||
});
|
||||
return new this(powerScrollData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import { TraitSelector } from "../apps/trait-selector.js";
|
||||
|
||||
import TraitSelector from "../apps/trait-selector.js";
|
||||
|
||||
/**
|
||||
* Override and extend the core ItemSheet implementation to handle SW5E specific item types
|
||||
* @type {ItemSheet}
|
||||
* Override and extend the core ItemSheet implementation to handle specific item types
|
||||
* @extends {ItemSheet}
|
||||
*/
|
||||
export class ItemSheet5e extends ItemSheet {
|
||||
export default class ItemSheet5e extends ItemSheet {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
if ( this.object.data.type === "class" ) {
|
||||
this.options.resizable = true;
|
||||
this.options.width = 600;
|
||||
this.options.height = 640;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
|
@ -13,7 +22,7 @@ export class ItemSheet5e extends ItemSheet {
|
|||
width: 560,
|
||||
height: 420,
|
||||
classes: ["sw5e", "sheet", "item"],
|
||||
resizable: false,
|
||||
resizable: true,
|
||||
scrollY: [".tab.details"],
|
||||
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
|
||||
});
|
||||
|
@ -43,14 +52,79 @@ export class ItemSheet5e extends ItemSheet {
|
|||
data.itemProperties = this._getItemProperties(data.item);
|
||||
data.isPhysical = data.item.data.hasOwnProperty("quantity");
|
||||
|
||||
// Potential consumption targets
|
||||
data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item);
|
||||
|
||||
// Action Details
|
||||
data.hasAttackRoll = this.item.hasAttack;
|
||||
data.isHealing = data.item.data.actionType === "heal";
|
||||
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
|
||||
data.isWeapon = data.item.type === "weapon";
|
||||
|
||||
// Vehicles
|
||||
data.isCrewed = data.item.data.activation?.type === 'crew';
|
||||
data.isMountable = this._isItemMountable(data.item);
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the valid item consumption targets which exist on the actor
|
||||
* @param {Object} item Item data for the item being displayed
|
||||
* @return {{string: string}} An object of potential consumption targets
|
||||
* @private
|
||||
*/
|
||||
_getItemConsumptionTargets(item) {
|
||||
const consume = item.data.consume || {};
|
||||
if ( !consume.type ) return [];
|
||||
const actor = this.item.actor;
|
||||
if ( !actor ) return {};
|
||||
|
||||
// Ammunition
|
||||
if ( consume.type === "ammo" ) {
|
||||
return actor.itemTypes.consumable.reduce((ammo, i) => {
|
||||
if ( i.data.data.consumableType === "ammo" ) {
|
||||
ammo[i.id] = `${i.name} (${i.data.data.quantity})`;
|
||||
}
|
||||
return ammo;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Attributes
|
||||
else if ( consume.type === "attribute" ) {
|
||||
const attributes = Object.values(CombatTrackerConfig.prototype.getAttributeChoices())[0]; // Bit of a hack
|
||||
return attributes.reduce((obj, a) => {
|
||||
obj[a] = a;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Materials
|
||||
else if ( consume.type === "material" ) {
|
||||
return actor.items.reduce((obj, i) => {
|
||||
if ( ["consumable", "loot"].includes(i.data.type) && !i.data.data.activation ) {
|
||||
obj[i.id] = `${i.name} (${i.data.data.quantity})`;
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Charges
|
||||
else if ( consume.type === "charges" ) {
|
||||
return actor.items.reduce((obj, i) => {
|
||||
const uses = i.data.data.uses || {};
|
||||
if ( uses.per && uses.max ) {
|
||||
const label = uses.per === "charges" ?
|
||||
` (${game.i18n.format("SW5E.AbilityUseChargesLabel", {value: uses.value})})` :
|
||||
` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {max: uses.max, per: uses.per})})`;
|
||||
obj[i.id] = i.name + label;
|
||||
}
|
||||
return obj;
|
||||
}, {})
|
||||
}
|
||||
else return {};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -63,10 +137,10 @@ export class ItemSheet5e extends ItemSheet {
|
|||
return CONFIG.SW5E.powerPreparationModes[item.data.preparation];
|
||||
}
|
||||
else if ( ["weapon", "equipment"].includes(item.type) ) {
|
||||
return item.data.equipped ? "Equipped" : "Unequipped";
|
||||
return game.i18n.localize(item.data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped");
|
||||
}
|
||||
else if ( item.type === "tool" ) {
|
||||
return item.data.proficient ? "Proficient" : "Not Proficient";
|
||||
return game.i18n.localize(item.data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,8 +165,8 @@ export class ItemSheet5e extends ItemSheet {
|
|||
props.push(
|
||||
labels.components,
|
||||
labels.materials,
|
||||
item.data.components.concentration ? "Concentration" : null,
|
||||
item.data.components.ritual ? "Ritual" : null
|
||||
item.data.components.concentration ? game.i18n.localize("SW5E.Concentration") : null,
|
||||
item.data.components.ritual ? game.i18n.localize("SW5E.Ritual") : null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -128,6 +202,22 @@ export class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Is this item a separate large object like a siege engine or vehicle
|
||||
* component that is usually mounted on fixtures rather than equipped, and
|
||||
* has its own AC and HP.
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
_isItemMountable(item) {
|
||||
const data = item.data;
|
||||
return (item.type === 'weapon' && data.weaponType === 'siege')
|
||||
|| (item.type === 'equipment' && data.armor.type === 'vehicle');
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
setPosition(position={}) {
|
||||
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
|
||||
|
@ -141,33 +231,12 @@ export class ItemSheet5e extends ItemSheet {
|
|||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
|
||||
// TODO: This can be removed once 0.7.x is release channel
|
||||
if ( !formData.data ) formData = expandObject(formData);
|
||||
|
||||
// Handle Damage Array
|
||||
let damage = Object.entries(formData).filter(e => e[0].startsWith("data.damage.parts"));
|
||||
formData["data.damage.parts"] = damage.reduce((arr, entry) => {
|
||||
let [i, j] = entry[0].split(".").slice(3);
|
||||
if ( !arr[i] ) arr[i] = [];
|
||||
arr[i][j] = entry[1];
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
// Handle armorproperties Array
|
||||
let armorproperties = Object.entries(formData).filter(e => e[0].startsWith("data.armorproperties.parts"));
|
||||
formData["data.armorproperties.parts"] = armorproperties.reduce((arr, entry) => {
|
||||
let [i, j] = entry[0].split(".").slice(3);
|
||||
if ( !arr[i] ) arr[i] = [];
|
||||
arr[i][j] = entry[1];
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
// Handle weaponproperties Array
|
||||
let weaponproperties = Object.entries(formData).filter(e => e[0].startsWith("data.weaponproperties.parts"));
|
||||
formData["data.weaponproperties.parts"] = weaponproperties.reduce((arr, entry) => {
|
||||
let [i, j] = entry[0].split(".").slice(3);
|
||||
if ( !arr[i] ) arr[i] = [];
|
||||
arr[i][j] = entry[1];
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
const damage = formData.data?.damage;
|
||||
if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
|
||||
|
||||
// Update the Item
|
||||
super._updateObject(event, formData);
|
||||
|
@ -179,16 +248,14 @@ export class ItemSheet5e extends ItemSheet {
|
|||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.find(".damage-control").click(this._onDamageControl.bind(this));
|
||||
|
||||
// Activate any Trait Selectors
|
||||
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this));
|
||||
|
||||
// Armor properties
|
||||
html.find(".armorproperties-control").click(this._onarmorpropertiesControl.bind(this));
|
||||
|
||||
// Weapon properties
|
||||
html.find(".weaponproperties-control").click(this._onweaponpropertiesControl.bind(this));
|
||||
|
||||
|
||||
// Armor properties
|
||||
html.find(".armorproperties-control").click(this._onarmorpropertiesControl.bind(this));
|
||||
|
||||
// Weapon properties
|
||||
html.find(".weaponproperties-control").click(this._onweaponpropertiesControl.bind(this));
|
||||
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -222,64 +289,6 @@ export class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Add or remove a armorproperties part from the armorproperties formula
|
||||
* @param {Event} event The original click event
|
||||
* @return {Promise}
|
||||
* @private
|
||||
*/
|
||||
async _onarmorpropertiesControl(event) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
|
||||
// Add new armorproperties component
|
||||
if ( a.classList.contains("add-armorproperties") ) {
|
||||
await this._onSubmit(event); // Submit any unsaved changes
|
||||
const armorproperties = this.item.data.data.armorproperties;
|
||||
return this.item.update({"data.armorproperties.parts": armorproperties.parts.concat([["", ""]])});
|
||||
}
|
||||
|
||||
// Remove a armorproperties component
|
||||
if ( a.classList.contains("delete-armorproperties") ) {
|
||||
await this._onSubmit(event); // Submit any unsaved changes
|
||||
const li = a.closest(".armorproperties-part");
|
||||
const armorproperties = duplicate(this.item.data.data.armorproperties);
|
||||
armorproperties.parts.splice(Number(li.dataset.armorpropertiesPart), 1);
|
||||
return this.item.update({"data.armorproperties.parts": armorproperties.parts});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Add or remove a weaponproperties part from the weaponproperties formula
|
||||
* @param {Event} event The original click event
|
||||
* @return {Promise}
|
||||
* @private
|
||||
*/
|
||||
async _onweaponpropertiesControl(event) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
|
||||
// Add new weaponproperties component
|
||||
if ( a.classList.contains("add-weaponproperties") ) {
|
||||
await this._onSubmit(event); // Submit any unsaved changes
|
||||
const weaponproperties = this.item.data.data.weaponproperties;
|
||||
return this.item.update({"data.weaponproperties.parts": weaponproperties.parts.concat([["", ""]])});
|
||||
}
|
||||
|
||||
// Remove a weaponproperties component
|
||||
if ( a.classList.contains("delete-weaponproperties") ) {
|
||||
await this._onSubmit(event); // Submit any unsaved changes
|
||||
const li = a.closest(".weaponproperties-part");
|
||||
const weaponproperties = duplicate(this.item.data.data.weaponproperties);
|
||||
weaponproperties.parts.splice(Number(li.dataset.weaponpropertiesPart), 1);
|
||||
return this.item.update({"data.weaponproperties.parts": weaponproperties.parts});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
|
|
60
module/macros.js
Normal file
60
module/macros.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
|
||||
/* -------------------------------------------- */
|
||||
/* Hotbar Macros */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a Macro from an Item drop.
|
||||
* Get an existing item macro if one exists, otherwise create a new one.
|
||||
* @param {Object} data The dropped data
|
||||
* @param {number} slot The hotbar slot to use
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export async function create5eMacro(data, slot) {
|
||||
if ( data.type !== "Item" ) return;
|
||||
if (!( "data" in data ) ) return ui.notifications.warn("You can only create macro buttons for owned Items");
|
||||
const item = data.data;
|
||||
|
||||
// Create the macro command
|
||||
const command = `game.sw5e.rollItemMacro("${item.name}");`;
|
||||
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
|
||||
if ( !macro ) {
|
||||
macro = await Macro.create({
|
||||
name: item.name,
|
||||
type: "script",
|
||||
img: item.img,
|
||||
command: command,
|
||||
flags: {"sw5e.itemMacro": true}
|
||||
});
|
||||
}
|
||||
game.user.assignHotbarMacro(macro, slot);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a Macro from an Item drop.
|
||||
* Get an existing item macro if one exists, otherwise create a new one.
|
||||
* @param {string} itemName
|
||||
* @return {Promise}
|
||||
*/
|
||||
export function rollItemMacro(itemName) {
|
||||
const speaker = ChatMessage.getSpeaker();
|
||||
let actor;
|
||||
if ( speaker.token ) actor = game.actors.tokens[speaker.token];
|
||||
if ( !actor ) actor = game.actors.get(speaker.actor);
|
||||
|
||||
// Get matching items
|
||||
const items = actor ? actor.items.filter(i => i.name === itemName) : [];
|
||||
if ( items.length > 1 ) {
|
||||
ui.notifications.warn(`Your controlled Actor ${actor.name} has more than one Item with name ${itemName}. The first matched item will be chosen.`);
|
||||
} else if ( items.length === 0 ) {
|
||||
return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
|
||||
}
|
||||
const item = items[0];
|
||||
|
||||
// Trigger the item roll
|
||||
if ( item.data.type === "power" ) return actor.usePower(item);
|
||||
return item.roll();
|
||||
}
|
|
@ -4,7 +4,7 @@ import { SW5E } from "../config.js";
|
|||
* A helper class for building MeasuredTemplates for 5e powers and abilities
|
||||
* @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 = canvas.dimensions.distance;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,7 +17,8 @@ export const preloadHandlebarsTemplates = async function() {
|
|||
// 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
|
||||
|
|
File diff suppressed because one or more lines are too long
152
sw5e.js
152
sw5e.js
|
@ -2,7 +2,7 @@
|
|||
* The Star Wars 5th Edition game system for Foundry Virtual Tabletop
|
||||
* Author: Kakeman89
|
||||
* Software License: GNU GPLv3
|
||||
* Content License: https://media.wizards.com/2016/downloads/SW5ERD-OGL_V5.1.pdf
|
||||
* Content License: https://media.wizards.com/2016/downloads/SW5E/SRD-OGL_V5.1.pdf
|
||||
* Repository: https://gitlab.com/foundrynet/sw5e
|
||||
* Issue Tracker: https://gitlab.com/foundrynet/sw5e/issues
|
||||
*/
|
||||
|
@ -12,14 +12,27 @@ import { SW5E } from "./module/config.js";
|
|||
import { registerSystemSettings } from "./module/settings.js";
|
||||
import { preloadHandlebarsTemplates } from "./module/templates.js";
|
||||
import { _getInitiativeFormula } from "./module/combat.js";
|
||||
import { measureDistance, getBarAttribute } from "./module/canvas.js";
|
||||
import { Actor5e } from "./module/actor/entity.js";
|
||||
import { ActorSheet5eCharacter } from "./module/actor/sheets/character.js";
|
||||
import { Item5e } from "./module/item/entity.js";
|
||||
import { ItemSheet5e } from "./module/item/sheet.js";
|
||||
import { ActorSheet5eNPC } from "./module/actor/sheets/npc.js";
|
||||
import { Dice5e } from "./module/dice.js";
|
||||
import { measureDistances, getBarAttribute } from "./module/canvas.js";
|
||||
|
||||
// Import Entities
|
||||
import Actor5e from "./module/actor/entity.js";
|
||||
import Item5e from "./module/item/entity.js";
|
||||
|
||||
// Import Applications
|
||||
import AbilityTemplate from "./module/pixi/ability-template.js";
|
||||
import AbilityUseDialog from "./module/apps/ability-use-dialog.js";
|
||||
import ActorSheetFlags from "./module/apps/actor-flags.js";
|
||||
import ActorSheet5eCharacter from "./module/actor/sheets/character.js";
|
||||
import ActorSheet5eNPC from "./module/actor/sheets/npc.js";
|
||||
import ActorSheet5eVehicle from "./module/actor/sheets/vehicle.js";
|
||||
import ItemSheet5e from "./module/item/sheet.js";
|
||||
import ShortRestDialog from "./module/apps/short-rest.js";
|
||||
import TraitSelector from "./module/apps/trait-selector.js";
|
||||
|
||||
// Import Helpers
|
||||
import * as chat from "./module/chat.js";
|
||||
import * as dice from "./module/dice.js";
|
||||
import * as macros from "./module/macros.js";
|
||||
import * as migrations from "./module/migration.js";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -31,11 +44,28 @@ Hooks.once("init", function() {
|
|||
|
||||
// Create a SW5E namespace within the game global
|
||||
game.sw5e = {
|
||||
Actor5e,
|
||||
Dice5e,
|
||||
Item5e,
|
||||
migrations,
|
||||
rollItemMacro
|
||||
applications: {
|
||||
AbilityUseDialog,
|
||||
ActorSheetFlags,
|
||||
ActorSheet5eCharacter,
|
||||
ActorSheet5eNPC,
|
||||
ActorSheet5eVehicle,
|
||||
ItemSheet5e,
|
||||
ShortRestDialog,
|
||||
TraitSelector
|
||||
},
|
||||
canvas: {
|
||||
AbilityTemplate
|
||||
},
|
||||
config: SW5E,
|
||||
dice: dice,
|
||||
entities: {
|
||||
Actor5e,
|
||||
Item5e,
|
||||
},
|
||||
macros: macros,
|
||||
migrations: migrations,
|
||||
rollItemMacro: macros.rollItemMacro
|
||||
};
|
||||
|
||||
// Record Configuration Values
|
||||
|
@ -51,12 +81,14 @@ Hooks.once("init", function() {
|
|||
registerSystemSettings();
|
||||
|
||||
// Patch Core Functions
|
||||
CONFIG.Combat.initiative.formula = "1d20 + @attributes.init.mod + @attributes.init.prof + @attributes.init.bonus";
|
||||
Combat.prototype._getInitiativeFormula = _getInitiativeFormula;
|
||||
|
||||
// Register sheet application classes
|
||||
Actors.unregisterSheet("core", ActorSheet);
|
||||
Actors.registerSheet("sw5e", ActorSheet5eCharacter, { types: ["character"], makeDefault: true });
|
||||
Actors.registerSheet("sw5e", ActorSheet5eNPC, { types: ["npc"], makeDefault: true });
|
||||
Actors.registerSheet('sw5e', ActorSheet5eVehicle, {types: ['vehicle'], makeDefault: true});
|
||||
Items.unregisterSheet("core", ItemSheet);
|
||||
Items.registerSheet("sw5e", ItemSheet5e, {makeDefault: true});
|
||||
|
||||
|
@ -76,14 +108,28 @@ Hooks.once("setup", function() {
|
|||
|
||||
// Localize CONFIG objects once up-front
|
||||
const toLocalize = [
|
||||
"abilities", "alignments", "conditionTypes", "consumableTypes", "currencies", "damageTypes", "distanceUnits", "equipmentTypes",
|
||||
"healingTypes", "itemActionTypes", "limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes",
|
||||
"powerSchools", "powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages", "polymorphSettings",
|
||||
"armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes", "actorSizes", "proficiencyLevels", "armorpropertiesTypes"
|
||||
"abilities", "abilityAbbreviations", "alignments", "conditionTypes", "consumableTypes", "currencies",
|
||||
"damageTypes", "damageResistanceTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes",
|
||||
"limitedUsePeriods", "senses", "skills", "powerComponents", "powerLevels", "powerPreparationModes", "powerSchools",
|
||||
"powerScalingModes", "targetTypes", "timePeriods", "weaponProperties", "weaponTypes", "languages",
|
||||
"polymorphSettings", "armorProficiencies", "weaponProficiencies", "toolProficiencies", "abilityActivationTypes",
|
||||
"abilityConsumptionTypes", "actorSizes", "proficiencyLevels", "armorPropertiesTypes", "cover"
|
||||
];
|
||||
|
||||
// Exclude some from sorting where the default order matters
|
||||
const noSort = [
|
||||
"abilities", "alignments", "currencies", "distanceUnits", "itemActionTypes", "proficiencyLevels",
|
||||
"limitedUsePeriods", "powerComponents", "powerLevels", "weaponTypes"
|
||||
];
|
||||
|
||||
// Localize and sort CONFIG objects
|
||||
for ( let o of toLocalize ) {
|
||||
CONFIG.SW5E[o] = Object.entries(CONFIG.SW5E[o]).reduce((obj, e) => {
|
||||
obj[e[0]] = game.i18n.localize(e[1]);
|
||||
const localized = Object.entries(CONFIG.SW5E[o]).map(e => {
|
||||
return [e[0], game.i18n.localize(e[1])];
|
||||
});
|
||||
if ( !noSort.includes(o) ) localized.sort((a, b) => a[1].localeCompare(b[1]));
|
||||
CONFIG.SW5E[o] = localized.reduce((obj, e) => {
|
||||
obj[e[0]] = e[1];
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
@ -101,7 +147,6 @@ Hooks.once("ready", function() {
|
|||
const NEEDS_MIGRATION_VERSION = 0.84;
|
||||
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
||||
let needMigration = (currentVersion < NEEDS_MIGRATION_VERSION) || (currentVersion === null);
|
||||
const canMigrate = currentVersion >= COMPATIBLE_MIGRATION_VERSION;
|
||||
|
||||
// Perform the migration
|
||||
if ( needMigration && game.user.isGM ) {
|
||||
|
@ -112,7 +157,7 @@ Hooks.once("ready", function() {
|
|||
}
|
||||
|
||||
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
|
||||
Hooks.on("hotbarDrop", (bar, data, slot) => create5eMacro(data, slot));
|
||||
Hooks.on("hotbarDrop", (bar, data, slot) => macros.create5eMacro(data, slot));
|
||||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -123,7 +168,7 @@ Hooks.on("canvasInit", function() {
|
|||
|
||||
// Extend Diagonal Measurement
|
||||
canvas.grid.diagonalRule = game.settings.get("sw5e", "diagonalMovement");
|
||||
SquareGrid.prototype.measureDistance = measureDistance;
|
||||
SquareGrid.prototype.measureDistances = measureDistances;
|
||||
|
||||
// Extend Token Resource Bars
|
||||
Token.prototype.getBarAttribute = getBarAttribute;
|
||||
|
@ -149,63 +194,6 @@ Hooks.on("getChatLogEntryContext", chat.addChatMessageContextOptions);
|
|||
Hooks.on("renderChatLog", (app, html, data) => Item5e.chatListeners(html));
|
||||
Hooks.on('getActorDirectoryEntryContext', Actor5e.addDirectoryContextOptions);
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Hotbar Macros */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a Macro from an Item drop.
|
||||
* Get an existing item macro if one exists, otherwise create a new one.
|
||||
* @param {Object} data The dropped data
|
||||
* @param {number} slot The hotbar slot to use
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function create5eMacro(data, slot) {
|
||||
if ( data.type !== "Item" ) return;
|
||||
if (!( "data" in data ) ) return ui.notifications.warn("You can only create macro buttons for owned Items");
|
||||
const item = data.data;
|
||||
|
||||
// Create the macro command
|
||||
const command = `game.sw5e.rollItemMacro("${item.name}");`;
|
||||
let macro = game.macros.entities.find(m => (m.name === item.name) && (m.command === command));
|
||||
if ( !macro ) {
|
||||
macro = await Macro.create({
|
||||
name: item.name,
|
||||
type: "script",
|
||||
img: item.img,
|
||||
command: command,
|
||||
flags: {"sw5e.itemMacro": true}
|
||||
});
|
||||
}
|
||||
game.user.assignHotbarMacro(macro, slot);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a Macro from an Item drop.
|
||||
* Get an existing item macro if one exists, otherwise create a new one.
|
||||
* @param {string} itemName
|
||||
* @return {Promise}
|
||||
*/
|
||||
function rollItemMacro(itemName) {
|
||||
const speaker = ChatMessage.getSpeaker();
|
||||
let actor;
|
||||
if ( speaker.token ) actor = game.actors.tokens[speaker.token];
|
||||
if ( !actor ) actor = game.actors.get(speaker.actor);
|
||||
|
||||
// Get matching items
|
||||
const items = actor ? actor.items.filter(i => i.name === itemName) : [];
|
||||
if ( items.length > 1 ) {
|
||||
ui.notifications.warn(`Your controlled Actor ${actor.name} has more than one Item with name ${itemName}. The first matched item will be chosen.`);
|
||||
} else if ( items.length === 0 ) {
|
||||
return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`);
|
||||
}
|
||||
const item = items[0];
|
||||
|
||||
// Trigger the item roll
|
||||
if ( item.data.type === "power" ) return actor.usePower(item);
|
||||
return item.roll();
|
||||
}
|
||||
Handlebars.registerHelper('getProperty', function (data, property) {
|
||||
return getProperty(data, property);
|
||||
});
|
||||
|
|
|
@ -95,13 +95,12 @@
|
|||
}
|
||||
],
|
||||
"socket": true,
|
||||
"initiative": "1d20 + @abilities.dex.mod + @attributes.init.value + (@abilities.dex.value / 100)",
|
||||
"gridDistance": 5,
|
||||
"gridUnits": "ft",
|
||||
"primaryTokenAttribute": "attributes.hp",
|
||||
"secondaryTokenAttribute": null,
|
||||
"minimumCoreVersion": "0.5.2",
|
||||
"compatibleCoreVersion": "0.6.8",
|
||||
"minimumCoreVersion": "0.5.6",
|
||||
"compatibleCoreVersion": "0.7.2",
|
||||
"url": "https://github.com/unrealkakeman89/sw5e",
|
||||
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
|
||||
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"
|
||||
|
|
184
template.json
184
template.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"Actor": {
|
||||
"types": ["character", "npc"],
|
||||
"types": ["character", "npc", "vehicle"],
|
||||
"templates": {
|
||||
"common": {
|
||||
"abilities": {
|
||||
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"attributes": {
|
||||
"ac": {
|
||||
"min": 0
|
||||
"value": 10
|
||||
},
|
||||
"hp": {
|
||||
"value": 10,
|
||||
|
@ -43,19 +43,47 @@
|
|||
"init": {
|
||||
"value": 0,
|
||||
"bonus": 0
|
||||
},
|
||||
"speed": {
|
||||
"value": "30 ft",
|
||||
"special": ""
|
||||
},
|
||||
"powercasting": "int"
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"alignment": "",
|
||||
"biography": {
|
||||
"value": "",
|
||||
"public": ""
|
||||
}
|
||||
},
|
||||
"traits": {
|
||||
"size": "med",
|
||||
"di": {
|
||||
"value": [],
|
||||
"custom": ""
|
||||
},
|
||||
"dr": {
|
||||
"value": [],
|
||||
"custom": ""
|
||||
},
|
||||
"dv": {
|
||||
"value": [],
|
||||
"custom": ""
|
||||
},
|
||||
"ci": {
|
||||
"value": [],
|
||||
"custom": ""
|
||||
}
|
||||
},
|
||||
"currency": {
|
||||
"gc": 0
|
||||
}
|
||||
},
|
||||
"creature": {
|
||||
"attributes": {
|
||||
"powercasting": "int",
|
||||
"speed": {
|
||||
"value": "30 ft",
|
||||
"special": ""
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"alignment": "",
|
||||
"species": ""
|
||||
},
|
||||
"skills": {
|
||||
|
@ -133,32 +161,12 @@
|
|||
}
|
||||
},
|
||||
"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,
|
||||
|
@ -240,12 +248,16 @@
|
|||
}
|
||||
},
|
||||
"character": {
|
||||
"templates": ["common"],
|
||||
"templates": ["common", "creature"],
|
||||
"attributes": {
|
||||
"death": {
|
||||
"success": 0,
|
||||
"failure": 0
|
||||
},
|
||||
"encumbrance": {
|
||||
"value": null,
|
||||
"max": null
|
||||
},
|
||||
"exhaustion": 0,
|
||||
"inspiration": 0
|
||||
},
|
||||
|
@ -297,7 +309,7 @@
|
|||
}
|
||||
},
|
||||
"npc": {
|
||||
"templates": ["common"],
|
||||
"templates": ["common", "creature"],
|
||||
"details": {
|
||||
"type": "",
|
||||
"environment": "",
|
||||
|
@ -322,6 +334,61 @@
|
|||
"initiative": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"vehicle": {
|
||||
"templates": ["common"],
|
||||
"abilities": {
|
||||
"int": {
|
||||
"value": 0
|
||||
},
|
||||
"wis": {
|
||||
"value": 0
|
||||
},
|
||||
"cha": {
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"ac": {
|
||||
"value": null,
|
||||
"motionless": ""
|
||||
},
|
||||
"actions": {
|
||||
"stations": false,
|
||||
"value": 0,
|
||||
"thresholds": {
|
||||
"2": null,
|
||||
"1": null,
|
||||
"0": null
|
||||
}
|
||||
},
|
||||
"hp": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"dt": null,
|
||||
"mt": null
|
||||
},
|
||||
"capacity": {
|
||||
"creature": "",
|
||||
"cargo": 0
|
||||
},
|
||||
"speed": ""
|
||||
},
|
||||
"traits": {
|
||||
"size": "lg",
|
||||
"dimensions": "",
|
||||
"di": {
|
||||
"value": ["poison", "psychic"]
|
||||
},
|
||||
"ci": {
|
||||
"value": ["blinded", "charmed", "deafened", "frightened", "paralyzed", "petrified", "poisoned", "stunned",
|
||||
"unconscious"]
|
||||
}
|
||||
},
|
||||
"cargo": {
|
||||
"crew": [],
|
||||
"passengers": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"Item": {
|
||||
|
@ -335,15 +402,15 @@
|
|||
},
|
||||
"source": ""
|
||||
},
|
||||
"speciesDescription": {
|
||||
"data": "$characteristics-table",
|
||||
"description": {
|
||||
"value": "",
|
||||
"chat": "",
|
||||
"unidentified": ""
|
||||
},
|
||||
"traits": ""
|
||||
},
|
||||
"speciesDescription": {
|
||||
"data": "$characteristics-table",
|
||||
"description": {
|
||||
"value": "",
|
||||
"chat": "",
|
||||
"unidentified": ""
|
||||
},
|
||||
"traits": ""
|
||||
},
|
||||
"physicalItem": {
|
||||
"quantity": 1,
|
||||
"weight": 0,
|
||||
|
@ -377,6 +444,11 @@
|
|||
"value": 0,
|
||||
"max": 0,
|
||||
"per": null
|
||||
},
|
||||
"consume": {
|
||||
"type": "",
|
||||
"target": null,
|
||||
"amount": null
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
|
@ -395,6 +467,17 @@
|
|||
"dc": null,
|
||||
"scaling": "power"
|
||||
}
|
||||
},
|
||||
"mountable": {
|
||||
"armor": {
|
||||
"value": 10
|
||||
},
|
||||
"hp": {
|
||||
"value": 0,
|
||||
"max": 0,
|
||||
"dt": null,
|
||||
"conditions": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"backpack": {
|
||||
|
@ -425,20 +508,20 @@
|
|||
"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
|
||||
|
@ -456,8 +539,7 @@
|
|||
},
|
||||
"species": {
|
||||
"templates": ["speciesDescription"]
|
||||
|
||||
},
|
||||
},
|
||||
"tool": {
|
||||
"templates": ["itemDescription", "physicalItem"],
|
||||
"ability": "int",
|
||||
|
@ -487,13 +569,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
|
||||
}
|
||||
|
|
|
@ -4,31 +4,34 @@
|
|||
<header class="sheet-header flexrow">
|
||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||
|
||||
<div class="header-details flexrow">
|
||||
<section class="header-details flexrow">
|
||||
<h1 class="charname">
|
||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||
</h1>
|
||||
|
||||
<div class="charlevel">
|
||||
<div class="level {{#if disableExperience}}noxp{{/if}}">
|
||||
<aside class="header-exp flexcol">
|
||||
<div class="charlevel">
|
||||
<label>{{ localize "SW5E.Level" }} {{data.details.level}}</label>
|
||||
<span class="levels">{{classLabels}}</span>
|
||||
</div>
|
||||
|
||||
{{#unless disableExperience}}
|
||||
<div class="experience">
|
||||
<div class="experience flexrow">
|
||||
<input name="data.details.xp.value" type="text" value="{{data.details.xp.value}}"
|
||||
data-dtype="Number" placeholder="0"/>
|
||||
<span class="max"> / {{data.details.xp.max}}</span>
|
||||
<span class="sep">/</span>
|
||||
<span class="max">{{data.details.xp.max}}</span>
|
||||
</div>
|
||||
<div class="xpbar">
|
||||
<span class="bar" style="width: {{data.details.xp.pct}}%"></span>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{{!-- Character Summary --}}
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<input type="text" name="data.details.race" value="{{data.details.race}}" placeholder="{{ localize 'SW5E.Species' }}"/>
|
||||
<input type="text" name="data.details.species" value="{{data.details.species}}" placeholder="{{ localize 'SW5E.Species' }}"/>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.background" value="{{data.details.background}}" placeholder="{{ localize 'SW5E.Background' }}"/>
|
||||
|
@ -36,6 +39,9 @@
|
|||
<li>
|
||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
||||
</li>
|
||||
<li class="proficiency">
|
||||
<span>{{ localize "SW5E.Proficiency" }} {{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{!-- Header Attributes --}}
|
||||
|
@ -57,6 +63,17 @@
|
|||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.HitDice" }}</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<label class="hit-dice">{{data.attributes.hd}} <span class="sep"> / </span> {{data.details.level}}</label>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<a class="rest short-rest">{{ localize "SW5E.RestS" }}</a>
|
||||
<a class="rest long-rest">{{ localize "SW5E.RestL" }}</a>
|
||||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
||||
<div class="attribute-value">
|
||||
|
@ -64,8 +81,7 @@
|
|||
data-dtype="Number" placeholder="10"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<span>{{ localize "SW5E.Proficiency" }}</span>
|
||||
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
||||
<span class="power-dc">{{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}</span>
|
||||
</footer>
|
||||
</li>
|
||||
|
||||
|
@ -81,18 +97,19 @@
|
|||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.HitDice" }}</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<label class="hit-dice">{{data.attributes.hd}} / {{data.details.level}}</label>
|
||||
<li class="attribute initiative">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.Initiative" }}</h4>
|
||||
<div class="attribute-value">
|
||||
<span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<a class="rest short-rest">{{ localize "SW5E.RestS" }}</a>
|
||||
<a class="rest long-rest">{{ localize "SW5E.RestL" }}</a>
|
||||
<span>{{ localize "SW5E.Modifier" }}</span>
|
||||
<input name="data.attributes.init.value" type="text" placeholder="0" data-dtype="Number"
|
||||
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
|
||||
</footer>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
{{!-- NPC Sheet Navigation --}}
|
||||
|
@ -118,7 +135,7 @@
|
|||
<div class="ability-modifiers flexrow">
|
||||
<span class="ability-mod" title="Modifier">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
||||
<input type="hidden" name="data.abilities.{{id}}.proficient" value="{{ability.proficient}}" data-dtype="Number"/>
|
||||
<a class="proficiency-toggle ability-proficiency" title="Proficiency">{{{ability.icon}}}</a>
|
||||
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
||||
<span class="ability-save" title="Saving Throw">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -133,13 +150,13 @@
|
|||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||
<h4 class="skill-name rollable">{{skill.label}}</h4>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.mod decimals=0 sign=true}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
<span class="skill-passive">({{skill.passive}})</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<section class="center-pane">
|
||||
<section class="center-pane flexcol">
|
||||
|
||||
{{!-- Body Attributes --}}
|
||||
<ul class="attributes flexrow">
|
||||
|
@ -149,39 +166,27 @@
|
|||
<input name="data.resources.{{res.name}}.label" type="text" value="{{res.label}}"
|
||||
placeholder="{{res.placeholder}}" />
|
||||
</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<div class="attribute-value">
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox" {{checked res.sr}}/>
|
||||
</label>
|
||||
|
||||
<input name="data.resources.{{res.name}}.value" type="text" value="{{res.value}}"
|
||||
data-dtype="Number" placeholder="0"/>
|
||||
<span class="sep"> / </span>
|
||||
<input name="data.resources.{{res.name}}.max" type="text" value="{{res.max}}"
|
||||
<input name="data.resources.{{res.name}}.max" type="text" value="{{res.max}}"
|
||||
data-dtype="Number" placeholder="0"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<label class="checkbox">
|
||||
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox" {{checked res.sr}}/>
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationLR" }} <input name="data.resources.{{res.name}}.lr" type="checkbox" {{checked res.lr}}/>
|
||||
</label>
|
||||
</footer>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
<li class="attribute initiative">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.Initiative" }}</h4>
|
||||
<div class="attribute-value">
|
||||
<span>{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<span>{{ localize "SW5E.Modifier" }}</span>
|
||||
<input name="data.attributes.init.value" type="text" placeholder="0" data-dtype="Number"
|
||||
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
|
||||
</footer>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{!-- Counters --}}
|
||||
<div class="counters flexrow">
|
||||
<div class="counters">
|
||||
<div class="counter flexrow death-saves">
|
||||
<h4 class="death-save rollable">{{ localize "SW5E.DeathSave" }}</h4>
|
||||
<div class="counter-value">
|
||||
|
@ -193,14 +198,14 @@
|
|||
value="{{data.attributes.death.failure}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow">
|
||||
<div class="counter flexrow exhaustion">
|
||||
<h4>{{ localize "SW5E.Exhaustion" }}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.exhaustion" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.exhaustion}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow">
|
||||
<div class="counter flexrow inspiration">
|
||||
<h4>{{ localize "SW5E.Inspiration" }}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="checkbox" name="data.attributes.inspiration" data-dtype="Boolean"
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
<form class="{{cssClass}}" autocomplete="off">
|
||||
<form class="{{cssClass}} flexcol limited" autocomplete="off">
|
||||
|
||||
<!-- HEADER -->
|
||||
<header class="sheet-header">
|
||||
<h1 class="charname">
|
||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||
</h1>
|
||||
{{!-- Sheet Header --}}
|
||||
<header class="sheet-header flexrow">
|
||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||
|
||||
<section class="header-details flexrow">
|
||||
<h1 class="charname">
|
||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||
</h1>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<!-- BODY -->
|
||||
{{!-- Sheet Body --}}
|
||||
<section class="sheet-body">
|
||||
<div class="tab biography">
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<section class="sheet-sidebar">
|
||||
<img class="sheet-profile" src="{{actor.img}}" title="{{actor.name}}" height="220" width="220" data-edit="img"/>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -4,33 +4,79 @@
|
|||
<header class="sheet-header flexrow">
|
||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" data-edit="img"/>
|
||||
|
||||
<div class="header-details flexrow">
|
||||
<section class="header-details flexrow">
|
||||
<h1 class="charname">
|
||||
<input name="name" type="text" value="{{actor.name}}" placeholder="{{ localize 'SW5E.Name' }}"/>
|
||||
</h1>
|
||||
|
||||
<div class="charlevel">
|
||||
<div class="level">
|
||||
<aside class="header-exp flexcol">
|
||||
<div class="cr">
|
||||
<label>{{ localize "SW5E.AbbreviationCR" }}</label>
|
||||
<input name="data.details.cr" type="text" value="{{labels.cr}}" placeholder="1"/>
|
||||
</div>
|
||||
<div class="experience">
|
||||
<span>{{data.details.xp.value}} XP</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<ul class="summary">
|
||||
{{!-- Character Summary --}}
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<input type="text" name="data.details.type" value="{{data.details.type}}" placeholder="{{ localize 'SW5E.Type' }}"/>
|
||||
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.type" value="{{data.details.type}}" placeholder="{{ localize 'SW5E.Type' }}"/>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.source" value="{{data.details.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{!-- Header Attributes --}}
|
||||
<ul class="attributes flexrow">
|
||||
<li class="attribute health">
|
||||
<h4 class="attribute-name box-title rollable">{{ localize "SW5E.Health" }}</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<input name="data.attributes.hp.value" type="text" value="{{data.attributes.hp.value}}"
|
||||
data-dtype="Number" placeholder="10"/>
|
||||
<span class="sep"> / </span>
|
||||
<input name="data.attributes.hp.max" type="text" value="{{data.attributes.hp.max}}"
|
||||
data-dtype="Number" placeholder="10"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input name="data.attributes.hp.formula" class="hpformula" type="text" placeholder="{{ localize 'SW5E.HealthFormula' }}"
|
||||
value="{{data.attributes.hp.formula}}"/>
|
||||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.ac.value" type="text" value="{{data.attributes.ac.value}}"
|
||||
data-dtype="Number" placeholder="10"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<span>{{ localize "SW5E.Proficiency" }}</span>
|
||||
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
||||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.speed.value" type="text"
|
||||
value="{{data.attributes.speed.value}}" placeholder="0"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input type="text" class="speed" name="data.attributes.speed.special"
|
||||
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
|
||||
</footer>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
{{!-- NPC Sheet Navigation --}}
|
||||
|
@ -54,7 +100,7 @@
|
|||
<div class="ability-modifiers flexrow">
|
||||
<span class="ability-mod" title="Modifier">{{numberFormat ability.mod decimals=0 sign=true}}</span>
|
||||
<input type="hidden" name="data.abilities.{{id}}.proficient" value="{{ability.proficient}}" data-dtype="Number"/>
|
||||
<a class="proficiency-toggle ability-proficiency" title="Proficiency">{{{ability.icon}}}</a>
|
||||
<a class="proficiency-toggle ability-proficiency" title="{{ localize 'SW5E.Proficiency' }}">{{{ability.icon}}}</a>
|
||||
<span class="ability-save" title="Saving Throw">{{numberFormat ability.save decimals=0 sign=true}}</span>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -69,58 +115,16 @@
|
|||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||
<h4 class="skill-name rollable">{{skill.label}}</h4>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.mod decimals=0 sign=true}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
<span class="skill-passive">({{skill.passive}})</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<section class="center-pane">
|
||||
|
||||
{{!-- Attributes --}}
|
||||
<ul class="attributes flexrow">
|
||||
<li class="attribute health">
|
||||
<h4 class="attribute-name box-title rollable">{{ localize "SW5E.Health" }}</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<input name="data.attributes.hp.value" type="text" value="{{data.attributes.hp.value}}"
|
||||
data-dtype="Number" placeholder="10"/>
|
||||
<span class="sep"> / </span>
|
||||
<input name="data.attributes.hp.max" type="text" value="{{data.attributes.hp.max}}"
|
||||
data-dtype="Number" placeholder="10"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input name="data.attributes.hp.formula" class="hpformula" type="text" placeholder="{{ localize 'SW5E.HealthFormula' }}"
|
||||
value="{{data.attributes.hp.formula}}"/>
|
||||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.ArmorClass" }}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.ac.value" type="text" value="{{data.attributes.ac.value}}"
|
||||
data-dtype="Number" placeholder="10"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<span>{{ localize "SW5E.Proficiency" }}</span>
|
||||
<span>{{numberFormat data.attributes.prof decimals=0 sign=true}}</span>
|
||||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.Speed" }}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.speed.value" type="text"
|
||||
value="{{data.attributes.speed.value}}" placeholder="0"/>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input type="text" class="speed" name="data.attributes.speed.special"
|
||||
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}"/>
|
||||
</footer>
|
||||
</li>
|
||||
</ul>
|
||||
<section class="center-pane flexcol">
|
||||
|
||||
{{!-- Legendary Actions --}}
|
||||
<div class="counters flexrow">
|
||||
<div class="counters">
|
||||
<div class="counter flexrow legendary">
|
||||
<h4>{{ localize "SW5E.LegAct" }}</h4>
|
||||
<div class="counter-value">
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<div class="inventory-filters">
|
||||
{{#unless isVehicle}}
|
||||
<div class="inventory-filters flexrow">
|
||||
<ul class="filter-list flexrow" data-filter="features">
|
||||
<li class="filter-title">{{localize "SW5E.Filter"}}</li>
|
||||
<li class="filter-item" data-filter="action">{{localize "SW5E.Action"}}</li>
|
||||
<li class="filter-item" data-filter="bonus">{{localize "SW5E.BonusAction"}}</li>
|
||||
<li class="filter-item" data-filter="reaction">{{localize "SW5E.Reaction"}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<ol class="inventory-list">
|
||||
{{#each sections as |section sid|}}
|
||||
|
@ -17,6 +18,12 @@
|
|||
<div class="item-detail item-action">{{localize "SW5E.Usage"}}</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if section.columns}}
|
||||
{{#each section.columns}}
|
||||
<div class="item-detail {{css}}">{{label}}</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if ../owner}}
|
||||
<div class="item-controls">
|
||||
<a class="item-control item-create" title="{{localize 'SW5E.FeatureAdd'}}" {{#each section.dataset as |v k|}}data-{{k}}="{{v}}"{{/each}}>
|
||||
|
@ -56,13 +63,34 @@
|
|||
<div class="item-detail player-class">
|
||||
{{item.data.subclass}}
|
||||
</div>
|
||||
<div class="item-detail">
|
||||
<div class="item-detail item-action">
|
||||
Level {{item.data.levels}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if section.columns}}
|
||||
{{#each section.columns}}
|
||||
<div class="item-detail {{css}}">
|
||||
{{#with (getProperty item property)}}
|
||||
{{#if ../editable}}
|
||||
<input type="text" value="{{this}}" placeholder="—"
|
||||
data-dtype="{{../editable}}">
|
||||
{{else}}
|
||||
{{this}}
|
||||
{{/if}}
|
||||
{{/with}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if ../../owner}}
|
||||
<div class="item-controls">
|
||||
{{#if section.crewable}}
|
||||
<a class="item-control item-toggle {{item.toggleClass}}"
|
||||
title="{{item.toggleTitle}}">
|
||||
<i class="fas fa-sun"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
<a class="item-control item-edit" title="Edit Item"><i class="fas fa-edit"></i></a>
|
||||
<a class="item-control item-delete" title="Delete Item"><i class="fas fa-trash"></i></a>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<div class="inventory-filters powerbook-filters">
|
||||
<div class="inventory-filters powerbook-filters flexrow">
|
||||
<div class="form-group powercasting-ability">
|
||||
<h3>{{localize "SW5E.PowerAbility"}}</h3>
|
||||
{{#unless isNPC}}
|
||||
<label>{{localize "SW5E.PowerAbility"}}</label>
|
||||
{{else}}
|
||||
<label>{{localize "SW5E.Level"}}</label>
|
||||
<input class="powercasting-level" type="text" name="data.details.powerLevel"
|
||||
value="{{data.details.powerLevel}}" data-dtype="Number" placeholder="0"/>
|
||||
{{/unless}}
|
||||
<select name="data.attributes.powercasting" data-type="String">
|
||||
{{#select data.attributes.powercasting}}
|
||||
<option value="">{{localize "SW5E.None"}}</option>
|
||||
|
@ -9,21 +15,15 @@
|
|||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
{{#if isNPC}}
|
||||
<h3>{{localize "SW5E.PowercasterLevel"}}</h3>
|
||||
<input class="powercasting-level" type="text" name="data.details.powerLevel"
|
||||
value="{{data.details.powerLevel}}" data-dtype="Number" placeholder="0"/>
|
||||
{{/if}}
|
||||
<h3 class="power-dc">{{localize "SW5E.PowerDC"}} {{data.attributes.powerdc}}</h3>
|
||||
</div>
|
||||
|
||||
<ul class="filter-list flexrow" data-filter="powerbook">
|
||||
<li class="filter-title">{{localize "SW5E.Filter"}}</li>
|
||||
<li class="filter-item" data-filter="action">{{localize "SW5E.Action"}}</li>
|
||||
<li class="filter-item" data-filter="bonus">{{localize "SW5E.BonusAction"}}</li>
|
||||
<li class="filter-item" data-filter="reaction">{{localize "SW5E.Reaction"}}</li>
|
||||
<li class="filter-item" data-filter="concentration">{{localize "SW5E.AbbreviationConc"}}</li>
|
||||
<li class="filter-item" data-filter="ritual">{{localize "SW5E.Ritual"}}</li>
|
||||
<li class="filter-item" data-filter="prepared">{{localize "SW5E.Prepared"}}{{#if preparedPowers}} ({{preparedPowers}}){{/if}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
</a>
|
||||
{{/if}}
|
||||
{{ else }}
|
||||
<span class="power-slots">{{{section.uses}}}</span>
|
||||
<span>{{{section.uses}}}</span>
|
||||
<span class="sep"> / </span>
|
||||
<span class="power-max">{{{section.slots}}}</span>
|
||||
{{/if}}
|
||||
|
@ -72,7 +72,7 @@
|
|||
<div class="item-name flexrow rollable">
|
||||
<div class="item-image" style="background-image: url({{item.img}})"></div>
|
||||
<h4>{{item.name}}</h4>
|
||||
{{#if item.data.uses.value }}
|
||||
{{#if item.data.uses.per }}
|
||||
<div class="item-detail power-uses">Uses {{item.data.uses.value}} / {{item.data.uses.max}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -10,95 +10,115 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
{{#unless isVehicle}}
|
||||
<div class="form-group {{#unless data.traits.senses}}inactive{{/unless}}">
|
||||
<label>{{localize "SW5E.Senses"}}</label>
|
||||
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{data.traits.languages.cssClass}}">
|
||||
<label for="data.traits.languages">{{localize "SW5E.Languages"}}</label>
|
||||
<label>{{localize "SW5E.Languages"}}</label>
|
||||
<a class="trait-selector" data-options="languages" data-target="data.traits.languages">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.languages.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="languages"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class="form-group {{data.traits.di.cssClass}}">
|
||||
<label for="data.traits.di">{{localize "SW5E.DamImm"}}</label>
|
||||
<label>{{localize "SW5E.DamImm"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.di">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.di.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="damageTypes"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{data.traits.dr.cssClass}}">
|
||||
<label for="data.traits.dr">{{localize "SW5E.DamRes"}}</label>
|
||||
<label>{{localize "SW5E.DamRes"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dr">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.dr.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="damageTypes"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{data.traits.dv.cssClass}}">
|
||||
<label for="data.traits.dv">{{localize "SW5E.DamVuln"}}</label>
|
||||
<label>{{localize "SW5E.DamVuln"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dv">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.dv.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="damageTypes"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{data.traits.ci.cssClass}}">
|
||||
<label for="data.traits.ci">{{localize "SW5E.ConImm"}}</label>
|
||||
<label>{{localize "SW5E.ConImm"}}</label>
|
||||
<a class="trait-selector" data-options="conditionTypes" data-target="data.traits.ci">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.ci.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="conditionTypes"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
|
||||
{{#if isCharacter}}
|
||||
<div class="form-group {{data.traits.weaponProf.cssClass}}">
|
||||
<label for="data.traits.weaponProf">{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||
<a class="trait-selector" data-options="weaponProficiencies" data-target="data.traits.weaponProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.weaponProf.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="weaponProficiencies"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{data.traits.armorProf.cssClass}}">
|
||||
<label for="data.traits.armorProf">{{localize "SW5E.TraitArmorProf"}}</label>
|
||||
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
||||
<a class="trait-selector" data-options="armorProficiencies" data-target="data.traits.armorProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.armorProf.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="armorProficiencies"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{data.traits.toolProf.cssClass}}">
|
||||
<label for="data.traits.toolProf">{{localize "SW5E.TraitToolProf"}}</label>
|
||||
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
||||
<a class="trait-selector" data-options="toolProficiencies" data-target="data.traits.toolProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.toolProf.selected as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a class="trait-selector" data-options="toolProficiencies"><i class="fas fa-edit"></i></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#unless isVehicle}}
|
||||
<div class="form-group ">
|
||||
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
||||
<a class="configure-flags"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
|
159
templates/actors/vehicle-sheet.html
Normal file
159
templates/actors/vehicle-sheet.html
Normal file
|
@ -0,0 +1,159 @@
|
|||
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||
<header class="sheet-header flexrow">
|
||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
||||
data-edit="img">
|
||||
<section class="header-details flexrow">
|
||||
<h1 class="charnam">
|
||||
<input name="name" type="text" value="{{actor.name}}"
|
||||
placeholder="{{localize 'SW5E.Name'}}">
|
||||
</h1>
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>{{localize 'SW5E.Vehicle'}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.traits.dimensions"
|
||||
value="{{data.traits.dimensions}}"
|
||||
placeholder="{{localize 'SW5E.Dimensions'}}">
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.source"
|
||||
value="{{data.details.source}}"
|
||||
placeholder="{{localize 'SW5E.Source'}}">
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="attributes flexrow">
|
||||
<li class="attribute health">
|
||||
<h4 class="attribute-name box-title">{{localize 'SW5E.health'}}</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<input name="data.attributes.hp.value" type="text" placeholder="—"
|
||||
value="{{data.attributes.hp.value}}" data-dtype="Number">
|
||||
<span class="sep"> / </span>
|
||||
<input name="data.attributes.hp.max" type="text" placeholder="—"
|
||||
value="{{data.attributes.hp.max}}" data-dtype="Number">
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input name="data.attributes.hp.dt" type="text" class="temphp"
|
||||
placeholder="{{localize 'SW5E.Threshold'}}"
|
||||
value="{{data.attributes.hp.dt}}" data-dtype="Number">
|
||||
<input name="data.attributes.hp.mt" type="text" class="temphp"
|
||||
placeholder="{{localize 'SW5E.VehicleMishap'}}"
|
||||
value="{{data.attributes.hp.mt}}" data-dtype="Number">
|
||||
</footer>
|
||||
</li>
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.ac.value" type="text" placeholder="—"
|
||||
value="{{data.attributes.ac.value}}" data-dtype="Number">
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input type="text" name="data.attributes.ac.motionless"
|
||||
placeholder="—" value="{{data.attributes.ac.motionless}}">
|
||||
</footer>
|
||||
</li>
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{localize 'SW5E.Speed'}}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.speed.value" type="text" placeholder="—"
|
||||
value="{{data.attributes.speed.value}}">
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<nav class="sheet-navigation tabs" data-group="primary">
|
||||
<a class="item active" data-tab="attributes">{{localize 'SW5E.Attributes'}}</a>
|
||||
<a class="item" data-tab="features">{{localize 'SW5E.Features'}}</a>
|
||||
<a class="item" data-tab="cargo">{{localize 'SW5E.VehicleCargoCrew'}}</a>
|
||||
<a class="item" data-tab="biography">{{localize 'SW5E.Description'}}</a>
|
||||
</nav>
|
||||
|
||||
<section class="sheet-body">
|
||||
<div class="tab attributes flexrow" data-group="primary" data-tab="attributes">
|
||||
<ul class="ability-scores flexrow">
|
||||
{{#each data.abilities as |ability id|}}
|
||||
<li class="ability" data-ability="{{id}}">
|
||||
<h4 class="ability-name box-title rollable">{{ability.label}}</h4>
|
||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="text"
|
||||
value="{{ability.value}}" data-dtype="Number" placeholder="0">
|
||||
<div class="ability-modifiers flexrow">
|
||||
<span class="ability-mod" title="{{localize 'SW5E.Modifier'}}">
|
||||
{{numberFormat ability.mod decimals=0 sign=true}}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<section class="center-pane flexcol">
|
||||
<div class="counters">
|
||||
<div class="counter flexrow creature-cap">
|
||||
<h4>{{localize 'SW5E.VehicleCreatureCapacity'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" placeholder="—"
|
||||
name="data.attributes.capacity.creature"
|
||||
value="{{data.attributes.capacity.creature}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow cargo-cap">
|
||||
<h4>{{localize 'SW5E.VehicleCargoCapacity'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.capacity.cargo" placeholder="0"
|
||||
data-dtype="Number" value="{{data.attributes.capacity.cargo}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow stations">
|
||||
<h4>{{localize 'SW5E.VehicleActionStations'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input name="data.attributes.actions.stations" type="checkbox"
|
||||
data-dtype="Boolean" value="{{data.attributes.actions.stations}}"
|
||||
{{checked data.attributes.actions.stations}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow actions">
|
||||
<h4>{{localize 'SW5E.ActionPl'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.actions.value" placeholder="0"
|
||||
data-dtype="Number" value="{{data.attributes.actions.value}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow action-thresholds">
|
||||
<h4>{{localize 'SW5E.VehicleActionThresholds'}}</h4>
|
||||
<div class="counter-value">
|
||||
<span class="sep"><</span>
|
||||
<input type="text" placeholder="—" data-dtype="Number"
|
||||
value="{{data.attributes.actions.thresholds.[2]}}"
|
||||
name="data.attributes.actions.thresholds.2">
|
||||
<span class="sep"><</span>
|
||||
<input type="text" placeholder="—" data-dtype="Number"
|
||||
value="{{data.attributes.actions.thresholds.[1]}}"
|
||||
name="data.attributes.actions.thresholds.1">
|
||||
<span class="sep"><</span>
|
||||
<input type="text" placeholder="—" data-dtype="Number"
|
||||
value="{{data.attributes.actions.thresholds.[0]}}"
|
||||
name="data.attributes.actions.thresholds.0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{> 'systems/sw5e/templates/actors/parts/actor-traits.html'}}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="tab features flexcol" data-group="primary" data-tab="features">
|
||||
{{> 'systems/sw5e/templates/actors/parts/actor-features.html' sections=features}}
|
||||
</div>
|
||||
|
||||
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
||||
{{> 'systems/sw5e/templates/actors/parts/actor-inventory.html' sections=cargo}}
|
||||
</div>
|
||||
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||
button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
|
@ -1,26 +1,43 @@
|
|||
<form id="ability-use-form">
|
||||
<p>{{ localize "SW5E.AbilityUseHint" }} <strong>{{item.name}}</strong> {{ localize "SW5E.Ability" }}.</p>
|
||||
|
||||
<p type="note">
|
||||
{{#if recharges}}
|
||||
{{ localize "SW5E.AbilityUseRechargeHint" }} <strong>{{#if isCharged}}{{ localize "SW5E.AbilityUseCharged" }}{{else}}{{ localize "SW5E.AbilityUseDepleted" }}{{/if}}</strong>.</p>
|
||||
{{else}}
|
||||
{{ localize "SW5E.AbilityUseWarnStart" }} <strong>{{uses.value}} {{ localize "SW5E.of" }} {{uses.max}}</strong> {{ localize "SW5E.AbilityUseWarnEnd" }} {{perLabel}}.
|
||||
{{/if}}
|
||||
{{#unless canUse}}
|
||||
{{ localize "SW5E.AbilityUseCantUse" }}
|
||||
{{/unless}}
|
||||
</p>
|
||||
<p>{{ title }}</p>
|
||||
<p class="notes">{{note}}</p>
|
||||
{{#each errors}}
|
||||
<p class="notification error">{{localize this}}</p>
|
||||
{{/each}}
|
||||
|
||||
{{#if hasPowerSlots}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox">{{ localize "SW5E.AbilityUseConsume" }} <input type="checkbox" name="consume" {{checked consume}}/></label>
|
||||
|
||||
{{#if hasPlaceableTemplate}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox">{{ localize "SW5E.PlaceTemplate" }}
|
||||
<input type="checkbox" name="placeTemplate" checked/>
|
||||
</label>
|
||||
<label>{{ localize "SW5E.PowerCastUpcast" }}</label>
|
||||
<div class="form-fields">
|
||||
<select name="level" {{#unless canUpcast}}disabled{{/unless}}>
|
||||
{{#select item.data.level}}
|
||||
{{#each powerLevels as |l|}}
|
||||
<option value="{{l.level}}" {{#unless l.canCast}}disabled{{/unless}}>{{l.label}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if canUpcast}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox"><input type="checkbox" name="consumeSlot" checked/>{{ localize "SW5E.PowerCastConsume" }}</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if hasLimitedUses}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox"><input type="checkbox" name="consumeUse" checked/>{{ localize "SW5E.AbilityUseConsume" }}</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if hasPlaceableTemplate}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="placeTemplate" checked/>
|
||||
{{ localize "SW5E.PlaceTemplate" }}
|
||||
</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
</form>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<input type="text" name="{{key}}" value="{{flag.value}}" placeholder="{{flag.placeholder}}" data-dtype="{{flag.type}}"/>
|
||||
{{/if}}
|
||||
|
||||
<p class="notes">{{flag.hint}}</p>
|
||||
<p class="notes">{{localize flag.hint}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
|
20
templates/apps/long-rest.html
Normal file
20
templates/apps/long-rest.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<form id="long-rest" class="dialog-content" onsubmit="event.preventDefault();">
|
||||
<p>Take a long rest? On a long rest you will recover hit points, half your maximum hit dice, class resources, limited use item charges, and spell slots.</p>
|
||||
|
||||
{{#if promptNewDay}}
|
||||
<div class="form-group">
|
||||
<label>Is New Day?</label>
|
||||
<input type="checkbox" name="newDay" {{checked newDay}}/>
|
||||
<p class="hint">Recover limited use abilities which recharge "per day"?</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="dialog-buttons">
|
||||
{{#each buttons as |button id|}}
|
||||
<button class="dialog-button" data-button="{{id}}">
|
||||
{{{button.icon}}}
|
||||
{{{button.label}}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</div>
|
||||
</form>
|
|
@ -18,6 +18,15 @@
|
|||
<p class="notes">{{ localize "SW5E.ShortRestNoHD" }}</p>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
{{#if promptNewDay}}
|
||||
<div class="form-group">
|
||||
<label>Is New Day?</label>
|
||||
<input type="checkbox" name="newDay" {{checked newDay}}/>
|
||||
<p class="hint">Recover limited use abilities which recharge "per day"?</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="dialog-buttons">
|
||||
{{#each buttons as |button id|}}
|
||||
<button class="dialog-button" data-button="{{id}}">
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{#if allowCustom}}
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.TraitSelectorSpecial" }}</label>
|
||||
<input type="text" name="custom" value="{{custom}}" data-dtype="String"/>
|
||||
</div>
|
||||
{{/if}}
|
||||
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SW5E.Save"}}</button>
|
||||
</form>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li></li>
|
||||
<li>
|
||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<input type="text" name="data.source" value="{{data.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||
</li>
|
||||
|
@ -113,10 +113,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Granted Abilities (TODO) --}}
|
||||
<h3 class="form-header">{{ localize "SW5E.GrantedAbilities" }}</h3>
|
||||
<p class="notification warning">This is still to-do</p>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
{{lookup config.consumableTypes data.consumableType }}
|
||||
</li>
|
||||
|
@ -73,14 +73,8 @@
|
|||
|
||||
{{!-- Item Activation Template --}}
|
||||
{{> "systems/sw5e/templates/items/parts/item-activation.html"}}
|
||||
|
||||
{{!-- Consumable Usage --}}
|
||||
{{#if data.activation.type}}
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.ItemConsumableUsage" }}</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.uses.autoUse" {{checked data.uses.autoUse}}/> {{ localize "SW5E.ItemConsumeOnUse" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.uses.autoDestroy" {{checked data.uses.autoDestroy}}/> {{ localize "SW5E.ItemDestroyEmpty" }}
|
||||
</label>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
{{lookup config.equipmentTypes data.armor.type }}
|
||||
</li>
|
||||
|
@ -57,6 +57,7 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
{{#unless isMountable}}
|
||||
{{!-- Equipment Status --}}
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.ItemEquipmentStatus" }}</label>
|
||||
|
@ -73,33 +74,34 @@
|
|||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{#unless isWeapon }}
|
||||
{{!-- ArmorProperties Formula --}}
|
||||
<div class="form-group stacked weapon-properties">
|
||||
<h4 class="armorproperties-header">
|
||||
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
||||
<a class="armorproperties-control add-armorproperties"><i class="fas fa-plus"></i></a>
|
||||
</h4>
|
||||
<ol class="armorproperties-parts form-group">
|
||||
{{#each data.armorproperties.parts as |part i| }}
|
||||
<li class="armorproperties-part flexrow" data-armorproperties-part="{{i}}">
|
||||
|
||||
<select name="data.armorproperties.parts.{{i}}.1">
|
||||
{{#select (lookup this "1") }}
|
||||
<option value="">{{ localize "SW5E.None" }}</option>
|
||||
{{#each ../config.armorpropertiesTypes as |name type|}}
|
||||
<option value="{{type}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<input type="text" name="data.armorproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
||||
<a class="armorproperties-control delete-armorproperties"><i class="fas fa-minus"></i></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
{{#unless isWeapon }}
|
||||
{{!-- ArmorProperties Formula --}}
|
||||
<div class="form-group stacked weapon-properties">
|
||||
<h4 class="armorproperties-header">
|
||||
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
||||
<a class="armorproperties-control add-armorproperties"><i class="fas fa-plus"></i></a>
|
||||
</h4>
|
||||
<ol class="armorproperties-parts form-group">
|
||||
{{#each data.armorproperties.parts as |part i| }}
|
||||
<li class="armorproperties-part flexrow" data-armorproperties-part="{{i}}">
|
||||
|
||||
<select name="data.armorproperties.parts.{{i}}.1">
|
||||
{{#select (lookup this "1") }}
|
||||
<option value="">{{ localize "SW5E.None" }}</option>
|
||||
{{#each ../config.armorpropertiesTypes as |name type|}}
|
||||
<option value="{{type}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<input type="text" name="data.armorproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
||||
<a class="armorproperties-control delete-armorproperties"><i class="fas fa-minus"></i></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{!-- Armor Class --}}
|
||||
<div class="form-group">
|
||||
|
@ -109,6 +111,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#unless isMountable}}
|
||||
{{!-- Dexterity Modifier --}}
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.ItemEquipmentDexMod" }}</label>
|
||||
|
@ -130,6 +133,21 @@
|
|||
<label>{{ localize "SW5E.ItemEquipmentStealthDisav" }}</label>
|
||||
<input type="checkbox" name="data.stealth" value="1" {{checked data.stealth}}/>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#if isMountable}}
|
||||
{{> 'systems/sw5e/templates/items/parts/item-mountable.html'}}
|
||||
<div class="form-group">
|
||||
<label>{{localize 'SW5E.Speed'}}</label>
|
||||
<div class="form-fields">
|
||||
<input type="text" name="data.speed.value" value="{{data.speed.value}}"
|
||||
placeholder="0" data-dtype="Number">
|
||||
<span class="sep">{{localize 'SW5E.FeetAbbr'}}</span>
|
||||
<input type="text" name="data.speed.conditions"
|
||||
value="{{data.speed.conditions}}">
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<h3 class="form-header">{{ localize "SW5E.ItemEquipmentUsage" }}</h3>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
{{labels.featType}}
|
||||
</li>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
||||
</li>
|
||||
|
|
|
@ -23,6 +23,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if isCrewed}}
|
||||
<div class="form-group">
|
||||
<label>{{localize 'SW5E.Cover'}}</label>
|
||||
<div class="form-fields">
|
||||
<select name="data.cover" data-dtype="Number">
|
||||
{{#select data.cover}}
|
||||
<option value="">—</option>
|
||||
{{#each config.cover as |v k|}}
|
||||
<option value="{{k}}">{{v}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{!-- Ability Target --}}
|
||||
<div class="form-group input-select-select">
|
||||
|
@ -99,4 +114,28 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{!-- Consumption --}}
|
||||
<div class="form-group uses-per">
|
||||
<label>{{ localize "SW5E.ConsumeTitle" }}</label>
|
||||
<div class="form-fields">
|
||||
<select name="data.consume.type">
|
||||
{{#select data.consume.type}}
|
||||
<option value=""></option>
|
||||
{{#each config.abilityConsumptionTypes as |name key|}}
|
||||
<option value="{{key}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<select name="data.consume.target">
|
||||
{{#select data.consume.target}}
|
||||
<option value=""></option>
|
||||
{{#each abilityConsumptionTargets as |name key|}}
|
||||
<option value="{{key}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<input type="text" name="data.consume.amount" value="{{data.consume.amount}}" data-dtype="Number"/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
19
templates/items/parts/item-mountable.html
Normal file
19
templates/items/parts/item-mountable.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<div class="form-group">
|
||||
<label>{{localize 'SW5E.Health'}}</label>
|
||||
<div class="form-fields">
|
||||
<input type="text" name="data.hp.value" value="{{data.hp.value}}"
|
||||
placeholder="0" data-dtype="Number">
|
||||
<span class="sep">/</span>
|
||||
<input type="text" name="data.hp.max" value="{{data.hp.max}}" placeholder="0"
|
||||
data-dtype="Number">
|
||||
<input type="text" name="data.hp.dt" value="{{data.hp.dt}}" data-dtype="Number"
|
||||
placeholder="{{localize 'SW5E.Threshold'}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{localize 'SW5E.HealthConditions'}}</label>
|
||||
<div class="form-fields">
|
||||
<input type="text" name="data.hp.conditions" value="{{data.hp.conditions}}">
|
||||
</div>
|
||||
</div>
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
{{labels.level}}
|
||||
</li>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<input type="text" name="data.rarity" value="{{data.rarity}}" placeholder="{{ localize 'SW5E.Rarity' }}"/>
|
||||
</li>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<span class="item-status">{{itemStatus}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="summary">
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
{{lookup config.weaponTypes data.weaponType }}
|
||||
</li>
|
||||
|
@ -56,22 +56,26 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
{{#unless isMountable}}
|
||||
{{!-- Weapon Status --}}
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.ItemWeaponStatus" }}</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.proficient" {{checked data.proficient}}/> {{ localize "SW5E.Proficient" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.equipped" {{checked data.equipped}}/> {{ localize "SW5E.Equipped" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||
</label>
|
||||
<div class="form-fields">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.proficient" {{checked data.proficient}}/> {{ localize "SW5E.Proficient" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.equipped" {{checked data.equipped}}/> {{ localize "SW5E.Equipped" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#if sss}}
|
||||
{{!-- Weapon Properties --}}
|
||||
|
@ -85,33 +89,44 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isWeapon }}
|
||||
<div class="form-group stacked weapon-properties">
|
||||
{{!-- weaponproperties Formula --}}
|
||||
<h4 class="weaponproperties-header">
|
||||
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
||||
<a class="weaponproperties-control add-weaponproperties"><i class="fas fa-plus"></i></a>
|
||||
</h4>
|
||||
<ol class="weaponproperties-parts form-group">
|
||||
{{#each data.weaponproperties.parts as |part i| }}
|
||||
<li class="weaponproperties-part flexrow" data-weaponproperties-part="{{i}}">
|
||||
|
||||
<select name="data.weaponproperties.parts.{{i}}.1">
|
||||
{{#select (lookup this "1") }}
|
||||
<option value="">{{ localize "SW5E.None" }}</option>
|
||||
{{#each ../config.weaponProperties as |name type|}}
|
||||
<option value="{{type}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<input type="text" name="data.weaponproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
||||
<a class="weaponproperties-control delete-weaponproperties"><i class="fas fa-minus"></i></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{/if}}
|
||||
{{#if isWeapon }}
|
||||
<div class="form-group stacked weapon-properties">
|
||||
{{!-- weaponproperties Formula --}}
|
||||
<h4 class="weaponproperties-header">
|
||||
{{#unless isWeapon }}{{ localize "SW5E.ArmorProperties" }}{{ else }}{{ localize "SW5E.ItemWeaponProperties" }}{{/unless}}
|
||||
<a class="weaponproperties-control add-weaponproperties"><i class="fas fa-plus"></i></a>
|
||||
</h4>
|
||||
<ol class="weaponproperties-parts form-group">
|
||||
{{#each data.weaponproperties.parts as |part i| }}
|
||||
<li class="weaponproperties-part flexrow" data-weaponproperties-part="{{i}}">
|
||||
|
||||
<select name="data.weaponproperties.parts.{{i}}.1">
|
||||
{{#select (lookup this "1") }}
|
||||
<option value="">{{ localize "SW5E.None" }}</option>
|
||||
{{#each ../config.weaponProperties as |name type|}}
|
||||
<option value="{{type}}">{{name}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<input type="text" name="data.weaponproperties.parts.{{i}}.0" value="{{lookup this "0"}}"/>
|
||||
<a class="weaponproperties-control delete-weaponproperties"><i class="fas fa-minus"></i></a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
{{#if isMountable}}
|
||||
<div class="form-group">
|
||||
<label>{{localize 'SW5E.ArmorClass'}}</label>
|
||||
<div class="form-fields">
|
||||
<input type="text" name="data.armor.value" value="{{data.armor.value}}"
|
||||
data-dtype="Number">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{> 'systems/dnd5e/templates/items/parts/item-mountable.html'}}
|
||||
{{/if}}
|
||||
|
||||
<h3 class="form-header">{{ localize "SW5E.ItemWeaponUsage" }}</h3>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue