Merging with upsteam/master
BIN
fonts/Aurebesh.ttf
Normal file
BIN
fonts/OpenSans-Bold.ttf
Normal file
BIN
fonts/OpenSans-BoldItalic.ttf
Normal file
BIN
fonts/OpenSans-Italic.ttf
Normal file
21
gulpfile.js
|
@ -5,13 +5,28 @@ const less = require('gulp-less');
|
|||
/* Compile LESS
|
||||
/* ----------------------------------------- */
|
||||
|
||||
const SW5E_LESS = ["less/*.less"];
|
||||
const SW5E_LESS = ["less/**/*.less"];
|
||||
function compileLESS() {
|
||||
return gulp.src("less/sw5e.less")
|
||||
return gulp.src("less/original/sw5e.less")
|
||||
.pipe(less())
|
||||
.pipe(gulp.dest("./"))
|
||||
}
|
||||
const css = gulp.series(compileLESS);
|
||||
function compileGlobalLess() {
|
||||
return gulp.src("less/update/sw5e-global.less")
|
||||
.pipe(less())
|
||||
.pipe(gulp.dest("./"))
|
||||
}
|
||||
function compileLightLess() {
|
||||
return gulp.src("less/update/sw5e-light.less")
|
||||
.pipe(less())
|
||||
.pipe(gulp.dest("./"))
|
||||
}
|
||||
function compileDarkLess() {
|
||||
return gulp.src("less/update/sw5e-dark.less")
|
||||
.pipe(less())
|
||||
.pipe(gulp.dest("./"))
|
||||
}
|
||||
const css = gulp.series(compileLESS, compileGlobalLess, compileLightLess, compileDarkLess);
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Watch Updates
|
||||
|
|
94
lang/en.json
|
@ -11,6 +11,13 @@
|
|||
"ITEM.TypePower": "Power",
|
||||
"ITEM.TypeTool": "Tool",
|
||||
"ITEM.TypeWeapon": "Weapon",
|
||||
"ITEM.TypeArchetype": "Archetype",
|
||||
"ITEM.TypeBackground": "Background",
|
||||
"ITEM.TypeLightsaberForm": "Lightsaber Form",
|
||||
"ITEM.TypeClassfeature": "Class Feature",
|
||||
"ITEM.TypeFightingmastery": "FightingMastery",
|
||||
"ITEM.TypeFightingstyle": "Fighting Style",
|
||||
"ITEM.TypeSpecies": "Species",
|
||||
|
||||
"Star Wars 5th Edition": "Star Wars 5th Edition",
|
||||
"SW5E.title": "Star Wars 5th Edition",
|
||||
|
@ -263,6 +270,11 @@
|
|||
"SW5E.FeatureActionRecharge": "Action Recharge",
|
||||
"SW5E.Flaws": "Flaws",
|
||||
|
||||
"SW5E.EffectCreate": "Create Effect",
|
||||
"SW5E.EffectToggle": "Toggle Effect",
|
||||
"SW5E.EffectEdit": "Edit Effect",
|
||||
"SW5E.EffectDelete": "Delete Effect",
|
||||
|
||||
"SW5E.ItemTypeArchetype": "Archetype",
|
||||
"SW5E.ItemTypeBackground": "Background",
|
||||
"Sw5E.ItemTypeBackgroundPl": "Backgrounds",
|
||||
|
@ -276,6 +288,10 @@
|
|||
"SW5E.ItemTypeContainerPl": "Containers",
|
||||
"SW5E.ItemTypeEquipment": "Equipment",
|
||||
"SW5E.ItemTypeEquipmentPl": "Equipment",
|
||||
"SW5E.ItemTypeFightingMastery": "Fighting Mastery",
|
||||
"SW5E.ItemTypeFightingMasteryPl": "Fighting Masteries",
|
||||
"SW5E.ItemTypeFightingStyle": "Fighting Style",
|
||||
"SW5E.ItemTypeFightingStylePl": "Fighting Styles",
|
||||
"SW5E.ItemTypeLightsaberForm": "Lightsaber Form",
|
||||
"SW5E.ItemTypeLightsaberFormPl": "Lightsaber Forms",
|
||||
"SW5E.ItemTypeLoot": "Loot",
|
||||
|
@ -306,10 +322,56 @@
|
|||
"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.FlagsAdaptiveResilience": "Adaptive Resilience",
|
||||
"SW5E.FlagsAggressive": "Aggressive",
|
||||
"SW5E.FlagsAmphibious": "Amphibious",
|
||||
"SW5E.FlagsArmorIntegration": "Armor Integration",
|
||||
"SW5E.FlagsBusinessSavvy": "Business Savvy",
|
||||
"SW5E.FlagsCannibalize": "Cannibalize",
|
||||
"SW5E.FlagsClosedMind": "Closed Mind",
|
||||
"SW5E.FlagsCrudeWeaponSpecialists": "Crude Weapon Specialists",
|
||||
"SW5E.FlagsDefiant": "Defiant",
|
||||
"SW5E.FlagsDetailOriented": "Detail Oriented",
|
||||
"SW5E.FlagsEnthrallingPheromones": "Enthralling Pheromones",
|
||||
"SW5E.FlagsExtraArms": "Extra Arms",
|
||||
"SW5E.FlagsForceContention": "Force Contention",
|
||||
"SW5E.FlagsForceInsensitive": "Force Insensitive",
|
||||
"SW5E.FlagsForeignBiology": "Foreign Biology",
|
||||
"SW5E.FlagsFuryOfTheSmall": "Fury of the Small",
|
||||
"SW5E.FlagsGrovelCowerAndBeg": "Grovel, Cower, and Beg",
|
||||
"SW5E.FlagsInscrutable": "Inscrutable",
|
||||
"SW5E.FlagsKeenSenses": "Keen Senses",
|
||||
"SW5E.FlagsLongLimbed": "Long-Limbed",
|
||||
"SW5E.FlagsMaintenanceMode": "Maintenance Mode",
|
||||
"SW5E.FlagsMaskOfTheWild": "Mask of the Wild",
|
||||
"SW5E.FlagsMultipleHearts": "Multiple Hearts",
|
||||
"SW5E.FlagsNaturallyStealthy": "Naturally Stealthy",
|
||||
"SW5E.FlagsNimbleAgility": "Nimble Agility",
|
||||
"SW5E.FlagsNimbleEscape": "Nimble Escape",
|
||||
"SW5E.FlagsNimbleness": "Nimbleness",
|
||||
"SW5E.FlagsPintsized": "Pintsized",
|
||||
"SW5E.FlagsPowerfulBuild": "Powerful Build",
|
||||
"SW5E.FlagsPowerfulBuildHint": "Provides increased carrying capacity.",
|
||||
"SW5E.FlagsPowerfulBuildHint": "You count as two sizes larger when determining your carrying capacity and the weight you can push, drag, or lift.",
|
||||
"SW5E.FlagsPrecognition": "Precognition",
|
||||
"SW5E.FlagsProgrammer": "Programmer",
|
||||
"SW5E.FlagsPuny": "Puny",
|
||||
"SW5E.FlagsRapidReconstruction": "Rapid Reconstruction",
|
||||
"SW5E.FlagsRapidlyRegenerative": "Rapidly Regenerative",
|
||||
"SW5E.FlagsRegenerative": "Regenerative",
|
||||
"SW5E.FlagsSavageAttacks": "Savage Attacks",
|
||||
"SW5E.FlagsSavageAttacksHint": "Adds extra critical hit weapon dice.",
|
||||
"SW5E.FlagsSavageAttacksHint": "When you score a critical hit with a melee weapon attack, you can roll one of the weapon’s damage dice one additional time and add it to the extra damage of the critical hit.",
|
||||
"SW5E.FlagsShapechanger": "Shapechanger",
|
||||
"SW5E.FlagsStrongLegged": "Strong-Legged",
|
||||
"SW5E.FlagsSunlightSensitivity": "Sunlight Sensitivity",
|
||||
"SW5E.FlagsSurpriseAttack": "Surprise Attack",
|
||||
"SW5E.FlagsTechImpaired": "Tech-Impaired",
|
||||
"SW5E.FlagsTechResistance": "Tech Resistance",
|
||||
"SW5E.FlagsTinker": "Tinker",
|
||||
"SW5E.FlagsToughness": "Toughness",
|
||||
"SW5E.FlagsTrance": "Trance",
|
||||
"SW5E.FlagsUnarmedCombatant": "Unarmed Combatant",
|
||||
"SW5E.FlagsUndersized": "Undersized",
|
||||
"SW5E.FlagsUnsettlingVisage": "Unsettling Visage",
|
||||
"SW5E.FlagsElvenAccuracy": "Elven Accuracy",
|
||||
"SW5E.FlagsElvenAccuracyHint": "Roll an extra d20 with advantage to Dex, Int, Wis, or Cha.",
|
||||
"SW5E.FlagsHalflingLucky": "Halfling Lucky",
|
||||
|
@ -328,6 +390,12 @@
|
|||
"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.FlagsWeaponCritThreshold": "Weapon Critical Hit Threshold",
|
||||
"SW5E.FlagsWeaponCritThresholdHint": "An expanded critical hit threshold for weapon attacks.",
|
||||
"SW5E.FlagsPowerCritThreshold": "Power Critical Hit Threshold",
|
||||
"SW5E.FlagsPowerCritThresholdHint": "An expanded critical hit threshold for power attacks.",
|
||||
"SW5E.FlagsMeleeCriticalDice": "Melee Critical Damage Dice",
|
||||
"SW5E.FlagsMeleeCriticalDiceHint": "A number of additional damage dice added to melee weapon critical hits.",
|
||||
|
||||
"SW5E.Flat": "Flat",
|
||||
"SW5E.Formula": "Formula",
|
||||
|
@ -578,8 +646,21 @@
|
|||
"SW5E.RollMode": "Roll Mode",
|
||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||
"SW5E.Save": "Save",
|
||||
|
||||
"SW5E.MovementConfig": "Configure Movement Speed",
|
||||
"SW5E.MovementConfigHint": "Configure the movement speed and special movement attributes of this creature.",
|
||||
"SW5E.MovementWalk": "Walk",
|
||||
"SW5E.MovementBurrow": "Burrow",
|
||||
"SW5E.MovementClimb": "Climb",
|
||||
"SW5E.MovementHover": "Hover",
|
||||
"SW5E.MovementFly": "Fly",
|
||||
"SW5E.MovementSwim": "Swim",
|
||||
"SW5E.MovementUnits": "Units",
|
||||
|
||||
"SW5E.SheetClassCharacter": "Default Character Sheet",
|
||||
"SW5E.SheetClassCharacterOld": "Old Character Sheet",
|
||||
"SW5E.SheetClassNPC": "Default NPC Sheet",
|
||||
"SW5E.SheetClassNPCOld": "Old NPC Sheet",
|
||||
"SW5E.SheetClassVehicle": "Default Vehicle Sheet",
|
||||
"SW5E.SheetClassItem": "Default Item Sheet",
|
||||
|
||||
|
@ -690,7 +771,7 @@
|
|||
"SW5E.SpeciesDescription": "Description",
|
||||
"SW5E.SpeciesTraits": "Species Traits",
|
||||
"SW5E.StealthDisadvantage": "Stealth Disadvantage",
|
||||
"SW5E.SubclassName": "Subclass Name",
|
||||
"SW5E.ArchetypeName": "Archetype Name",
|
||||
"SW5E.Supply": "Supply",
|
||||
"SW5E.Target": "Target",
|
||||
"SW5E.TargetAlly": "Ally",
|
||||
|
@ -831,6 +912,7 @@
|
|||
"SW5E.available": "available",
|
||||
"SW5E.description": "A comprehensive game system for running games of Star Wars 5th Edition in the Foundry VTT environment.",
|
||||
"SW5E.of": "of",
|
||||
"SW5E.per": "per",
|
||||
"SW5E.power": "power",
|
||||
"SETTINGS.5eAllowPolymorphingL": "Allow players to polymorph their own actors.",
|
||||
"SETTINGS.5eAllowPolymorphingN": "Allow Polymorphing",
|
||||
|
@ -854,5 +936,9 @@
|
|||
"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)"
|
||||
"SETTINGS.5eRestEpic": "Epic Heroism (LR: 1 hour, SR: 1 min)",
|
||||
"SETTINGS.SWColorL": "Set the color theme of the game",
|
||||
"SETTINGS.SWColorN": "Display Theme",
|
||||
"SETTINGS.SWColorLight": "Light Theme",
|
||||
"SETTINGS.SWColorDark": "Dark Theme"
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
.russoOne(14px);
|
||||
color: @colorOlive;
|
||||
border-bottom: 1px solid @colorFaint;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
|
@ -142,6 +143,7 @@
|
|||
font-family: "Signika", sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -413,46 +415,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Inventory item lists
|
||||
.inventory-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 5px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
color: @colorTan;
|
||||
|
||||
// Inventory Item
|
||||
.item {
|
||||
line-height: 30px;
|
||||
padding: 0 2px; // to align with the header border
|
||||
border-bottom: 1px solid @colorFaint;
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
// Item Header Name
|
||||
.item-name {
|
||||
cursor: pointer;
|
||||
max-height: 30px;
|
||||
overflow: hidden;
|
||||
|
||||
.item-image {
|
||||
flex: 0 0 30px;
|
||||
background-size: 30px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&.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;
|
||||
}
|
||||
|
@ -474,49 +448,26 @@
|
|||
flex: 0 0 80px;
|
||||
text-align: right;
|
||||
font-size: 11px;
|
||||
color: @colorTan;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// Inventory Header
|
||||
.inventory-header {
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: @borderGroove;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
|
||||
h3 {
|
||||
margin: 0 -5px 0 0;
|
||||
padding-left: 5px;
|
||||
.russoOne();
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.item-controls a.item-create {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// Item names
|
||||
.item-name {
|
||||
color: @colorDark;
|
||||
}
|
||||
|
||||
// Item Detail Sections
|
||||
.item-detail {
|
||||
flex: 0 0 70px;
|
||||
font-size: 12px;
|
||||
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}
|
||||
}
|
||||
|
@ -527,24 +478,9 @@
|
|||
border-right: 1px solid @colorFaint;
|
||||
}
|
||||
|
||||
.item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Item Control Buttons
|
||||
.item-controls {
|
||||
flex: 0 0 44px;
|
||||
.flexrow();
|
||||
justify-content: flex-end;
|
||||
|
||||
a {
|
||||
flex: 0 0 22px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: @colorTan;
|
||||
}
|
||||
}
|
||||
|
||||
// Item Dropdown Summary
|
||||
|
@ -553,6 +489,7 @@
|
|||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 0.25em 0.5em;
|
||||
color: @colorDark;
|
||||
border-top: 1px solid @colorFaint;
|
||||
}
|
||||
}
|
||||
|
@ -693,44 +630,6 @@
|
|||
// Empty powerbook controls
|
||||
.powerbook-empty .item-controls { flex: 1; }
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Active Effects */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.effects {
|
||||
.effect-name{
|
||||
flex: 2;
|
||||
align-items: center;
|
||||
color: @colorDark;
|
||||
h4 { margin: 0; }
|
||||
}
|
||||
|
||||
.effect-icon {
|
||||
flex: 0 0 30px;
|
||||
height: 30px;
|
||||
margin-right: 5px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.effect-source,
|
||||
.effect-duration {
|
||||
text-align: center;
|
||||
border-left: 1px solid @colorFaint;
|
||||
border-right: 1px solid @colorFaint;
|
||||
}
|
||||
|
||||
.effect-controls {
|
||||
flex: 0 0 60px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.effect {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid @colorFaint;
|
||||
&:last-child { border-bottom: none; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* TinyMCE */
|
||||
/* ----------------------------------------- */
|
||||
|
@ -739,3 +638,18 @@
|
|||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
#actor-flags {
|
||||
.window-content {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
form {
|
||||
height: 100%;
|
||||
}
|
||||
.form-body {
|
||||
height: calc(100% - 40px);
|
||||
padding-right: 8px;
|
||||
margin-bottom: 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
|
@ -362,6 +362,108 @@
|
|||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Items Lists */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.items-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
color: @colorTan;
|
||||
|
||||
// Child lists
|
||||
.item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Individual Item
|
||||
.item {
|
||||
align-items: center;
|
||||
padding: 0 2px; // to align with the header border
|
||||
border-bottom: 1px solid @colorFaint;
|
||||
&:last-child { border-bottom: none; }
|
||||
|
||||
.item-name {
|
||||
color: @colorDark;
|
||||
.item-image {
|
||||
flex: 0 0 30px;
|
||||
height: 30px;
|
||||
background-size: 30px;
|
||||
border: none;
|
||||
margin-right: 5px;
|
||||
}
|
||||
h4 {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Section Header
|
||||
.items-header {
|
||||
height: 28px;
|
||||
margin: 2px 0;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: @borderGroove;
|
||||
font-weight: bold;
|
||||
> * {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.item-name {
|
||||
padding-left: 5px;
|
||||
//.modesto();
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// Item Name
|
||||
.item-name {
|
||||
flex: 2;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Control Buttons
|
||||
.item-controls {
|
||||
flex: 0 0 60px;
|
||||
justify-content: space-between;
|
||||
a {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Active Effects */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.effects .item {
|
||||
.effect-source,
|
||||
.effect-duration,
|
||||
.effect-controls {
|
||||
text-align: center;
|
||||
border-left: 1px solid @colorFaint;
|
||||
border-right: 1px solid @colorFaint;
|
||||
font-size: 12px;
|
||||
}
|
||||
.effect-controls {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,17 +116,17 @@
|
|||
}
|
||||
|
||||
.form-group.uses-per {
|
||||
.form-fields {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
input {
|
||||
flex: 1;
|
||||
flex: 0 0 32px;
|
||||
}
|
||||
span {
|
||||
flex: 0 0 16px;
|
||||
}
|
||||
select {
|
||||
flex: 3;
|
||||
margin: 0 4px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
span.sep {
|
||||
flex: 0 0 8px;
|
||||
}
|
146
less/update/_variables-dark.less
Normal file
|
@ -0,0 +1,146 @@
|
|||
//override Primary Red
|
||||
@colorRed: #E81111;
|
||||
@colorDarkBg: #2b2b2b;
|
||||
//Background
|
||||
@primaryBackground: linear-gradient(90deg,#626262 0,#4d4d4d 30%,#4d4d4d 70%,#626262);
|
||||
|
||||
//Typography
|
||||
@headingColor: @colorRed;
|
||||
@headerBorderColor: @colorBlue;
|
||||
@bodyFontColor: white;
|
||||
@linkColor: @colorRed;
|
||||
@linkSecondaryColor: @colorPaleGray;
|
||||
|
||||
@blockquoteBackground: @colorPaleRed;
|
||||
@blockquoteBorder: @colorRed;
|
||||
@blockquoteShadow: 0 0 20px rgba(@colorRed, 0.8);
|
||||
|
||||
//forms
|
||||
@inputBackgroundColor: @colorDarkGray;
|
||||
@inputBorderNormal: @colorLightGray;
|
||||
@inputBorderHover: @colorGray;
|
||||
@inputBorderFocus: @colorRed;
|
||||
@inputTextColor: white;
|
||||
|
||||
@buttonBackground: @colorRed;
|
||||
@buttonTextColor: white;
|
||||
@buttonHoverBackground: lighten(@colorRed, 5);
|
||||
@buttonSecondaryBackground: @colorLightGray;
|
||||
@buttonSecondaryTextColor: white;
|
||||
@buttonSecondaryHoverBackground: lighten(@colorLightGray, 5);
|
||||
|
||||
//other bits
|
||||
@hrColor: @colorBlue;
|
||||
@tableTextColor: white;
|
||||
@tableHeaderTextColor: @colorPaleGray;
|
||||
@tableBackground: @colorGray;
|
||||
@tableRowHoverBackground: lighten(@colorLightGray, 10);
|
||||
@tableRowBorderColor: @colorLightGray;
|
||||
|
||||
//universalColors
|
||||
@windowHeaderBackground: @colorDarkBg;
|
||||
@windowHeaderLinkColor: @colorRed;
|
||||
|
||||
//Sidebar
|
||||
@sidebarTabBackground: @windowHeaderBackground;
|
||||
@sidebarTabLinkColor: @windowHeaderLinkColor;
|
||||
@sidebarTabLinkUnderline: @colorRed;
|
||||
|
||||
@chatBackground: @colorDarkGray;
|
||||
@chatHeaderColor: @colorRed;
|
||||
@chatHeaderBottomBorderColor: @colorBlue;
|
||||
@chatNotificationColor: @colorBlue;
|
||||
@cardButtonBorder: @colorLightGray;
|
||||
@cardFooterBorder: @colorLightBlue;
|
||||
@cardFooterSeparator: @colorPaleGray;
|
||||
|
||||
@diceFormulaBackground: @colorGray;
|
||||
@diceFormualColor: white;
|
||||
@diceTotalBackground: @colorPaleRed;
|
||||
@diceTotalBorder: @colorRed;
|
||||
@diceTotalShadow: @colorRed;
|
||||
@diceSuccessColor: @colorGreen;
|
||||
@diceFailureColor: @colorRed;
|
||||
@diceCriticalBackground: @colorPaleGreen;
|
||||
@diceCriticalColor: @colorGreen;
|
||||
@diceFumbleBackground: @colorPaleRed;
|
||||
@diceFumbleColor: @colorRed;
|
||||
|
||||
@altRowBackground: @colorGray;
|
||||
|
||||
@combatRoundColor: @colorRed;
|
||||
@combatRoundBorder: @colorBlue;
|
||||
@combatCombatantControlColor: @colorPaleGray;
|
||||
@combatCombatantControlColorActive: @colorRed;
|
||||
@combatActiveCombatantColor: @colorBlue;
|
||||
@combatTokenResourceColor: white;
|
||||
@combatTokenResouceBorder: @colorLightGray;
|
||||
@combatControlsBorder: @colorBlue;
|
||||
|
||||
@folderSearchIconColor: @colorBlue;
|
||||
@folderSubdirectoryBackground: @colorDarkBg;
|
||||
@folderSubdirectoryBorder: @colorLightGray;
|
||||
@directoryListItemBorder: @colorBlue;
|
||||
@folderHeaderBackground: @colorDarkBg;
|
||||
@folderHeaderColor: white;
|
||||
@folderIconColor: @colorBlue;
|
||||
|
||||
@entityBackgroundColor: @colorDarkBg;
|
||||
@entityNameColor: @colorBlack;
|
||||
|
||||
@sceneBorderColor: @colorBlue;
|
||||
@sceneBackgroundColor: @colorDarkBg;
|
||||
|
||||
@playlistBackgroundColor: @colorDarkBg;
|
||||
@playlistHeaderBorder: @colorBlue;
|
||||
@playlistSoundColor: @colorBlack;
|
||||
|
||||
@compendiumEntityBackground: @colorDarkBg;
|
||||
@compendiumStatusIcon: @colorLightGray;
|
||||
|
||||
@foundryNavBgColor: rgba(@colorLightBlue, 0.4);
|
||||
@foundryNavTextColor: white;
|
||||
@foundryNavBorderColor: @colorBlue;
|
||||
@foundryNavBgColorGM: @colorBlue;
|
||||
@foundryNavBorderColorGM: @colorPaleBlue;
|
||||
@foundryNavSceneLinkColor: white;
|
||||
@foundryNavActiveBgColor: rgba(@colorRed, 0.6);
|
||||
@foundryNavActiveBorderColor: lighten(@colorRed, 20);
|
||||
@foundryNavActiveGlow: darken(@colorRed, 20);
|
||||
@foundryNavContextShadow: darken(@colorBlue, 20);
|
||||
@foundryNavContextBorderColor: @colorBlue;
|
||||
|
||||
@foundryPlayersArrowColor: @colorLightGray;
|
||||
|
||||
@actorPanelBgColor: white;
|
||||
@actorNameColor: @colorRed;
|
||||
@actorXPBarBorder: @colorGray;
|
||||
@actorXPBarBackground: @colorPaleBlue;
|
||||
@actorXPBarColor: @colorBlue;
|
||||
@actorProficiencyTextColor: @colorGray;
|
||||
@actorAttributeInputColor: @colorGray;
|
||||
@actorSeparatorColor: @colorLightGray;
|
||||
@actorAttributeButtonBorder: @colorPaleGray;
|
||||
@actorAttributeButtonBorderHover: @colorRed;
|
||||
@actorNavigationTabsColor: @colorGray;
|
||||
@actorNavigationTabsActiveColor: @colorRed;
|
||||
@actorNavigationTabsHoverBgColor: rgba(@colorGray, 0.1);
|
||||
@actorNavigationTabsActiveHoverBgColor: rgba(@colorRed, 0.1);
|
||||
@actorFilterBorderColor: @colorLightGray;
|
||||
@actorFilterHoverColor: @colorRed;
|
||||
@actorFilterActiveColor: @colorRed;
|
||||
@actorGroupListHeaderBgColor: lighten(@colorPaleGray, 10);
|
||||
@actorGroupListTitleBorderColor: @colorBlue;
|
||||
@actorGroupListColumnBorderColor: @colorPaleGray;
|
||||
@actorGroupListAltRowColor: lighten(@colorPaleGray, 10);
|
||||
@actorItemRollableD20Color: @colorGray;
|
||||
@actorItemRollableD20HoverColor: @colorRed;
|
||||
@actorItemControlToggleColor: @colorLightGray;
|
||||
@actorAbilityScoreColor: @colorGray;
|
||||
@actorAbilityBorderColor: @colorPaleGray;
|
||||
@actorSkillsAltRowColor: lighten(@colorPaleGray, 10);
|
||||
@actorEncumbranceLabelBackground: @colorPaleGray;
|
||||
@actorEncumbranceTextColor: @colorBlack;
|
||||
@actorEncumbranceBorderColor: @colorBlack;
|
||||
@actorEncumbranceBarBgColor: @colorPaleBlue;
|
||||
@actorEncumbranceBarColor: @colorBlue;
|
143
less/update/_variables-light.less
Normal file
|
@ -0,0 +1,143 @@
|
|||
//Background
|
||||
@primaryBackground: linear-gradient(90deg,#afc6d6 0,#d6d6d6 30%,#d6d6d6 70%,#afc6d6);// linear-gradient(90deg, @colorPaleBlue 0%, @colorPaleGray 30%, @colorPaleGray 70%, @colorPaleBlue);
|
||||
|
||||
//Typography
|
||||
@headingColor: @colorRed;
|
||||
@headerBorderColor: @colorBlue;
|
||||
@bodyFontColor: @colorBlack;
|
||||
@linkColor: @colorRed;
|
||||
@linkSecondaryColor: @colorGray;
|
||||
|
||||
@blockquoteBackground: @colorPaleBlue;
|
||||
@blockquoteBorder: @colorBlue;
|
||||
@blockquoteShadow: 0 0 20px rgba(@colorBlue, 0.8);
|
||||
|
||||
//forms
|
||||
@inputBackgroundColor: white;
|
||||
@inputBorderNormal: @colorLightGray;
|
||||
@inputBorderHover: @colorGray;
|
||||
@inputBorderFocus: @colorRed;
|
||||
@inputTextColor: @colorBlack;
|
||||
|
||||
@buttonBackground: @colorRed;
|
||||
@buttonTextColor: white;
|
||||
@buttonHoverBackground: lighten(@colorRed, 5);
|
||||
@buttonSecondaryBackground: @colorPaleGray;
|
||||
@buttonSecondaryTextColor: @colorBlack;
|
||||
@buttonSecondaryHoverBackground: lighten(@colorPaleGray, 5);
|
||||
|
||||
//other bits
|
||||
@hrColor: @colorBlue;
|
||||
@tableTextColor: @colorBlack;
|
||||
@tableHeaderTextColor: @colorLightGray;
|
||||
@tableBackground: white;
|
||||
@tableRowHoverBackground: lighten(@colorPaleGray, 10);
|
||||
@tableRowBorderColor: @colorPaleGray;
|
||||
|
||||
//universalColors
|
||||
@windowHeaderBackground: white;
|
||||
@windowHeaderLinkColor: @colorRed;
|
||||
|
||||
//Sidebar
|
||||
@sidebarTabBackground: @windowHeaderBackground;
|
||||
@sidebarTabLinkColor: @windowHeaderLinkColor;
|
||||
@sidebarTabLinkUnderline: @colorRed;
|
||||
|
||||
@chatBackground: white;
|
||||
@chatHeaderColor: @colorRed;
|
||||
@chatHeaderBottomBorderColor: @colorBlue;
|
||||
@chatNotificationColor: @colorBlue;
|
||||
@cardButtonBorder: @colorLightGray;
|
||||
@cardFooterBorder: @colorLightBlue;
|
||||
@cardFooterSeparator: @colorPaleGray;
|
||||
|
||||
@diceFormulaBackground: @colorPaleGray;
|
||||
@diceFormualColor: @colorBlack;
|
||||
@diceTotalBackground: @colorPaleBlue;
|
||||
@diceTotalBorder: @colorBlue;
|
||||
@diceTotalShadow: @colorBlue;
|
||||
@diceSuccessColor: @colorGreen;
|
||||
@diceFailureColor: @colorRed;
|
||||
@diceCriticalBackground: @colorPaleGreen;
|
||||
@diceCriticalColor: @colorGreen;
|
||||
@diceFumbleBackground: @colorPaleRed;
|
||||
@diceFumbleColor: @colorRed;
|
||||
|
||||
@altRowBackground: @colorPaleBlue;
|
||||
|
||||
@combatRoundColor: @colorRed;
|
||||
@combatRoundBorder: @colorBlue;
|
||||
@combatCombatantControlColor: @colorLightGray;
|
||||
@combatCombatantControlColorActive: @colorDarkGray;
|
||||
@combatActiveCombatantColor: @colorBlue;
|
||||
@combatTokenResourceColor: @colorGray;
|
||||
@combatTokenResouceBorder: @colorLightGray;
|
||||
@combatControlsBorder: @colorBlue;
|
||||
|
||||
@folderSearchIconColor: @colorBlue;
|
||||
@folderSubdirectoryBackground: white;
|
||||
@folderSubdirectoryBorder: @colorBlack;
|
||||
@directoryListItemBorder: @colorBlue;
|
||||
@folderHeaderBackground: white;
|
||||
@folderHeaderColor: @colorBlack;
|
||||
@folderIconColor: @colorBlue;
|
||||
|
||||
@entityBackgroundColor: white;
|
||||
@entityNameColor: @colorBlack;
|
||||
|
||||
@sceneBorderColor: @colorBlue;
|
||||
@sceneBackgroundColor: white;
|
||||
|
||||
@playlistBackgroundColor: white;
|
||||
@playlistHeaderBorder: @colorBlue;
|
||||
@playlistSoundColor: @colorBlack;
|
||||
|
||||
@compendiumEntityBackground: white;
|
||||
@compendiumStatusIcon: @colorLightGray;
|
||||
|
||||
@foundryNavBgColor: rgba(@colorLightBlue, 0.4);
|
||||
@foundryNavTextColor: white;
|
||||
@foundryNavBorderColor: @colorBlue;
|
||||
@foundryNavBgColorGM: @colorBlue;
|
||||
@foundryNavBorderColorGM: @colorPaleBlue;
|
||||
@foundryNavSceneLinkColor: white;
|
||||
@foundryNavActiveBgColor: rgba(@colorRed, 0.6);
|
||||
@foundryNavActiveBorderColor: lighten(@colorRed, 20);
|
||||
@foundryNavActiveGlow: darken(@colorRed, 20);
|
||||
@foundryNavContextShadow: darken(@colorBlue, 20);
|
||||
@foundryNavContextBorderColor: @colorBlue;
|
||||
|
||||
@foundryPlayersArrowColor: @colorLightGray;
|
||||
|
||||
@actorPanelBgColor: white;
|
||||
@actorNameColor: @colorRed;
|
||||
@actorXPBarBorder: @colorGray;
|
||||
@actorXPBarBackground: @colorPaleBlue;
|
||||
@actorXPBarColor: @colorBlue;
|
||||
@actorProficiencyTextColor: @colorGray;
|
||||
@actorAttributeInputColor: @colorGray;
|
||||
@actorSeparatorColor: @colorLightGray;
|
||||
@actorAttributeButtonBorder: @colorPaleGray;
|
||||
@actorAttributeButtonBorderHover: @colorRed;
|
||||
@actorNavigationTabsColor: @colorGray;
|
||||
@actorNavigationTabsActiveColor: @colorRed;
|
||||
@actorNavigationTabsHoverBgColor: rgba(@colorGray, 0.1);
|
||||
@actorNavigationTabsActiveHoverBgColor: rgba(@colorRed, 0.1);
|
||||
@actorFilterBorderColor: @colorLightGray;
|
||||
@actorFilterHoverColor: @colorRed;
|
||||
@actorFilterActiveColor: @colorRed;
|
||||
@actorGroupListHeaderBgColor: lighten(@colorPaleGray, 10);
|
||||
@actorGroupListTitleBorderColor: @colorBlue;
|
||||
@actorGroupListColumnBorderColor: @colorPaleGray;
|
||||
@actorGroupListAltRowColor: lighten(@colorPaleGray, 10);
|
||||
@actorItemRollableD20Color: @colorGray;
|
||||
@actorItemRollableD20HoverColor: @colorRed;
|
||||
@actorItemControlToggleColor: @colorLightGray;
|
||||
@actorAbilityScoreColor: @colorGray;
|
||||
@actorAbilityBorderColor: @colorPaleGray;
|
||||
@actorSkillsAltRowColor: lighten(@colorPaleGray, 10);
|
||||
@actorEncumbranceLabelBackground: @colorPaleGray;
|
||||
@actorEncumbranceTextColor: @colorBlack;
|
||||
@actorEncumbranceBorderColor: @colorBlack;
|
||||
@actorEncumbranceBarBgColor: @colorPaleBlue;
|
||||
@actorEncumbranceBarColor: @colorBlue;
|
67
less/update/_variables.less
Normal file
|
@ -0,0 +1,67 @@
|
|||
|
||||
/* ----------------------------------------- */
|
||||
/* Fonts */
|
||||
/* ----------------------------------------- */
|
||||
.russoOne(@size: 20px) {
|
||||
font-family: 'Russo One';
|
||||
font-size: @size;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.openSans(@size: 13px, @weight: 400) {
|
||||
font-family: 'Open Sans';
|
||||
font-size: @size;
|
||||
font-weight: @weight;
|
||||
}
|
||||
.fontAwesome() {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Sheet Styles */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
@colorDark: #191813;
|
||||
@colorFaint: #c9c7b8;
|
||||
@colorBeige: #b5b3a4;
|
||||
@colorTan: #7a7971;
|
||||
@colorOlive: #4b4a44;
|
||||
@colorCrimson: #44191A;
|
||||
@borderGroove: 2px groove #eeede0;
|
||||
//@sheetBackground: url("ui/parchment.jpg") repeat;
|
||||
|
||||
|
||||
//SW5e Colors
|
||||
@colorBlack: #1C1C1C;
|
||||
@colorDarkGray: #363636;
|
||||
@colorGray: #4f4f4f;
|
||||
@colorLightGray: #828282;
|
||||
@colorPaleGray: #D6D6D6;
|
||||
@colorRed: #c40f0f;
|
||||
@colorPaleRed: #FBF4F4;
|
||||
@colorLightRed: #F6E1E1;
|
||||
@colorBlue: #0d99cc;
|
||||
@colorLightBlue: #7ed6f7;
|
||||
@colorPaleBlue: #afc6d6;
|
||||
@colorGreen: #0dce0d;
|
||||
@colorPaleGreen: #bcdcbe;
|
||||
|
||||
@sheetBackground: linear-gradient(90deg, @colorPaleBlue 0%, @colorPaleGray 30%, @colorPaleGray 70%, @colorPaleBlue);
|
||||
|
||||
|
||||
.dropShadow1(){
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2);
|
||||
}
|
||||
.dropShadow2() {
|
||||
box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3);
|
||||
}
|
||||
.dropShadow3() {
|
||||
box-shadow: 0 8px 17px 2px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2);
|
||||
}
|
1059
less/update/components/actor-global.less
Normal file
416
less/update/components/actor-themes.less
Normal file
|
@ -0,0 +1,416 @@
|
|||
.panel {
|
||||
background: @actorPanelBgColor;
|
||||
}
|
||||
|
||||
.sw5e.sheet .window-content {
|
||||
color: @colorBlack;
|
||||
background: linear-gradient(90deg,#afc6d6 0,#d6d6d6 30%,#d6d6d6 70%,#afc6d6);
|
||||
input,
|
||||
select {
|
||||
color: @colorBlack;
|
||||
&:hover {
|
||||
border-color: @inputBorderHover;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: @inputBorderFocus;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: @inputBorderFocus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sw5e.sheet.actor {
|
||||
color: @colorBlack;
|
||||
input, select, textarea {
|
||||
&:hover {
|
||||
border-color: @inputBorderFocus;
|
||||
}
|
||||
&:focus {
|
||||
border-color: @inputBorderFocus;
|
||||
}
|
||||
}
|
||||
.swalt-sheet {
|
||||
section>h1 {
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
}
|
||||
|
||||
header {
|
||||
|
||||
h1.character-name {
|
||||
color: @actorNameColor;
|
||||
|
||||
input[type="text"] {
|
||||
color: @actorNameColor;
|
||||
}
|
||||
}
|
||||
|
||||
.level-experience {
|
||||
|
||||
.xpbar {
|
||||
border: 1px solid @actorXPBarBorder;
|
||||
background-color: @actorXPBarBackground;
|
||||
|
||||
.bar {
|
||||
background-color: @actorXPBarColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
|
||||
input,
|
||||
.proficiency {
|
||||
color: @actorProficiencyTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
.attributes {
|
||||
|
||||
.attribute-value,
|
||||
.attribute-value input {
|
||||
color: @actorAttributeInputColor;
|
||||
}
|
||||
|
||||
.attribute-value {
|
||||
|
||||
.value-separator {
|
||||
color: @actorSeparatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
button {
|
||||
border: 1px solid @actorAttributeButtonBorder;
|
||||
|
||||
&:hover {
|
||||
color: @actorAttributeButtonBorderHover;
|
||||
}
|
||||
}
|
||||
|
||||
&.hit-points,
|
||||
&.hit-dice,
|
||||
&.initiative {
|
||||
button {
|
||||
border: 1px solid @actorAttributeButtonBorder;
|
||||
color: @colorRed;
|
||||
|
||||
&:hover {
|
||||
border-color: @actorAttributeButtonBorderHover;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nav.sheet-navigation {
|
||||
.item {
|
||||
color: @actorNavigationTabsColor;
|
||||
|
||||
&.active {
|
||||
color: @actorNavigationTabsActiveColor;
|
||||
border-bottom-color: @actorNavigationTabsActiveColor;
|
||||
|
||||
&:hover {
|
||||
background: @actorNavigationTabsHoverBgColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @actorNavigationTabsHoverBgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
|
||||
.filter-list {
|
||||
|
||||
.filter-item {
|
||||
border-bottom: 2px solid @actorFilterBorderColor;
|
||||
|
||||
&:hover {
|
||||
color: @actorFilterHoverColor;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: @actorFilterActiveColor;
|
||||
border-bottom-color: @actorFilterActiveColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-list-header {
|
||||
background: @actorGroupListHeaderBgColor;
|
||||
}
|
||||
|
||||
.group-list-title {
|
||||
border-bottom: 1px solid @actorGroupListTitleBorderColor;
|
||||
}
|
||||
|
||||
.group-list-header,
|
||||
.group-list {
|
||||
.item-detail {
|
||||
border-left: 1px solid @actorGroupListColumnBorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.group-list,
|
||||
.group-list ol {
|
||||
li.item {
|
||||
&:nth-child(even) {
|
||||
background-color: @actorGroupListAltRowColor;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: @colorBlack;
|
||||
}
|
||||
|
||||
|
||||
.item-name {
|
||||
|
||||
.item-image {
|
||||
|
||||
&::before {
|
||||
color: @actorItemRollableD20Color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
&.rollable:hover {
|
||||
|
||||
.item-image {
|
||||
&:hover {
|
||||
&::before {
|
||||
color: @actorItemRollableD20HoverColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-control {
|
||||
&:hover {
|
||||
color: @linkColor !important;
|
||||
}
|
||||
|
||||
&.item-toggle {
|
||||
color: @actorItemControlToggleColor;
|
||||
|
||||
&.active {
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
.tab.attributes {
|
||||
.abilities {
|
||||
|
||||
.scores {
|
||||
li {
|
||||
border: 1px solid @actorAbilityBorderColor;
|
||||
|
||||
h2 {
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
|
||||
.ability-score {
|
||||
color: @actorAbilityScoreColor;
|
||||
}
|
||||
|
||||
.ability-modifiers {
|
||||
|
||||
.ability-mod,
|
||||
.ability-save {
|
||||
border-color: @actorAbilityBorderColor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.skills {
|
||||
li {
|
||||
&:nth-child(even) {
|
||||
background-color: @actorSkillsAltRowColor;
|
||||
}
|
||||
.proficiency-toggle {
|
||||
color: @colorBlack;
|
||||
}
|
||||
|
||||
.skill-name {
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.traits-resources {
|
||||
nav {
|
||||
button {
|
||||
color: @actorNavigationTabsColor;
|
||||
|
||||
&.active {
|
||||
color: @actorNavigationTabsActiveColor;
|
||||
border-bottom-color: @actorNavigationTabsActiveColor;
|
||||
|
||||
&:hover {
|
||||
background: @actorNavigationTabsActiveHoverBgColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @actorNavigationTabsHoverBgColor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
section.traits {
|
||||
.trait-selector {
|
||||
i.fas {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
|
||||
.languages {
|
||||
label {
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
section.resources {
|
||||
.resource-items {
|
||||
.resource {
|
||||
h1 {
|
||||
|
||||
input {
|
||||
color: @headingColor;
|
||||
border-bottom: 2px solid @headerBorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.attribute-value,
|
||||
.attribute-value input {
|
||||
color: @actorAttributeInputColor;
|
||||
}
|
||||
|
||||
.attribute-value {
|
||||
.value-separator {
|
||||
color: @actorSeparatorColor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.counters {
|
||||
.counter {
|
||||
h4 {
|
||||
&.rollable {
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.death-success {
|
||||
i {
|
||||
color: @colorGreen;
|
||||
}
|
||||
}
|
||||
|
||||
.death-fail {
|
||||
i {
|
||||
color: @colorRed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab.inventory {
|
||||
.currency {
|
||||
color: @headingColor;
|
||||
}
|
||||
|
||||
.encumbrance-wrapper {
|
||||
.encumbrance-label {
|
||||
background: @actorEncumbranceLabelBackground;
|
||||
color: @actorEncumbranceTextColor;
|
||||
border: 1px solid @actorEncumbranceBorderColor;
|
||||
}
|
||||
|
||||
.encumbrance {
|
||||
background: @actorEncumbranceBarBgColor;
|
||||
.encumbrance-bar {
|
||||
background: @actorEncumbranceBarColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tab.powerbook {
|
||||
.powercasting-ability {
|
||||
label,
|
||||
h3 {
|
||||
color: @headingColor;
|
||||
|
||||
span {
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab.notes {
|
||||
section {
|
||||
&>input {
|
||||
color: @headingColor;
|
||||
border-bottom: 2px solid @headerBorderColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.npc {
|
||||
.swalt-sheet {
|
||||
header {
|
||||
.experience {
|
||||
color: @actorProficiencyTextColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
105
less/update/components/forms-global.less
Normal file
|
@ -0,0 +1,105 @@
|
|||
input[type="text"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], select, textarea {
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
input[type=range] {
|
||||
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
||||
width: 100%; /* Specific width is required for Firefox. */
|
||||
background: transparent; /* Otherwise white in Chrome */
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb{
|
||||
-webkit-appearance: none;
|
||||
background: @colorRed;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 32px;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type=range]::-moz-range-thumb{
|
||||
-webkit-appearance: none;
|
||||
background: @colorRed;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 32px;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type=range]::-ms-thumb {
|
||||
-webkit-appearance: none;
|
||||
background: @colorRed;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 32px;
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
background: @colorLightBlue;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @colorBlue;
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: @colorBlue;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
background: @colorLightBlue;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @colorBlue;
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type=range]::-ms-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
cursor: pointer;
|
||||
background: @colorLightBlue;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @colorBlue;
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type=range]:focus {
|
||||
outline: none; /* Removes the blue border. You should probably do some kind of focus styling for accessibility reasons though. */
|
||||
}
|
||||
|
||||
input[type=range]::-ms-track {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
/* Hides the slider so custom styles can be added */
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
button, input[type="button"], input[type="submit"], input[type="reset"] {
|
||||
.openSans(13px, 700);
|
||||
text-align: center;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&:hover, &:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
|
||||
}
|
||||
|
||||
}
|
53
less/update/components/forms-themes.less
Normal file
|
@ -0,0 +1,53 @@
|
|||
input[type="text"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], select, textarea {
|
||||
border: 1px solid @inputBorderNormal;
|
||||
color: @inputTextColor;
|
||||
&:hover {
|
||||
border-color: @inputBorderHover;
|
||||
}
|
||||
&:focus {
|
||||
border-color: @inputBorderFocus;
|
||||
}
|
||||
&::placeholder {
|
||||
color: @inputTextColor;
|
||||
opacity: 0.5;
|
||||
}
|
||||
::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: @inputTextColor;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
button, input[type="button"], input[type="submit"], input[type="reset"] {
|
||||
background: @buttonBackground;
|
||||
color: @buttonTextColor;
|
||||
&:hover, &:focus {
|
||||
background: @buttonHoverBackground;
|
||||
}
|
||||
&:disabled {
|
||||
&:hover, &:focus {
|
||||
background: @buttonBackground;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
input[type="reset"], button.secondary, button[type="reset"], input[type="button"].secondary, input[type="submit"].secondary {
|
||||
background: @buttonSecondaryBackground;
|
||||
color: @buttonSecondaryTextColor;
|
||||
&:hover {
|
||||
background: @buttonSecondaryHoverBackground;
|
||||
}
|
||||
&:disabled {
|
||||
&:hover, &:focus {
|
||||
background: @buttonSecondaryBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
button {
|
||||
border: none;
|
||||
}
|
||||
.notes, .hint {
|
||||
color: rgba(@bodyFontColor, 0.8);
|
||||
}
|
||||
}
|
76
less/update/components/foundry-app-window-themes.less
Normal file
|
@ -0,0 +1,76 @@
|
|||
.window-app {
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
.dropShadow2();
|
||||
& > header {
|
||||
background: @windowHeaderBackground;
|
||||
border-radius: 4px 4px 0 0;
|
||||
border: none;
|
||||
.dropShadow1();
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.window-content {
|
||||
background: @primaryBackground;
|
||||
color: @bodyFontColor;
|
||||
footer {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
&.minimized {
|
||||
& > header, & > .window-header {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#client-settings {
|
||||
nav.tabs {
|
||||
border: none;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
a.item {
|
||||
border-bottom: 3px solid transparent;
|
||||
color: @bodyFontColor;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
&.active {
|
||||
text-shadow: none;
|
||||
border-bottom-color: @sidebarTabLinkUnderline;
|
||||
}
|
||||
}
|
||||
}
|
||||
section.content {
|
||||
border: none;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-buttons {
|
||||
margin-top: 8px;
|
||||
button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
button:not(.default) {
|
||||
border: 1px solid @buttonBackground;
|
||||
margin-right: 4px;
|
||||
background: @buttonSecondaryBackground;
|
||||
color: @buttonSecondaryTextColor;
|
||||
&:hover {
|
||||
background: @buttonSecondaryHoverBackground;
|
||||
}
|
||||
|
||||
}
|
||||
button.normal.default {
|
||||
border: none;
|
||||
background: @buttonBackground;
|
||||
color: @buttonTextColor;
|
||||
&:hover {
|
||||
background: @buttonHoverBackground;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
84
less/update/components/foundry-nav-themes.less
Normal file
|
@ -0,0 +1,84 @@
|
|||
#navigation {
|
||||
#nav-toggle {
|
||||
background: @foundryNavBgColor;
|
||||
color: @foundryNavTextColor;
|
||||
|
||||
transform: rotate(-90deg);
|
||||
|
||||
}
|
||||
.nav-item {
|
||||
border: 1px solid @foundryNavBorderColor;
|
||||
}
|
||||
#scene-list {
|
||||
.scene {
|
||||
border: 1px solid @foundryNavBorderColor;
|
||||
background: rgba(@foundryNavBgColor, 0.4);
|
||||
a {
|
||||
color: @foundryNavSceneLinkColor;
|
||||
}
|
||||
&.gm {
|
||||
border: 1px solid @foundryNavBorderColorGM;
|
||||
background: rgba(@foundryNavBgColorGM, 0.4);
|
||||
}
|
||||
&.view, &.context {
|
||||
box-shadow: 0 0 8px @foundryNavContextShadow;
|
||||
border-color: @foundryNavContextBorderColor;
|
||||
}
|
||||
&.active {
|
||||
border-color: @foundryNavActiveBorderColor;
|
||||
background: @foundryNavActiveBgColor;
|
||||
box-shadow: 0 0 8px @foundryNavActiveGlow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#controls {
|
||||
.scene-control, .control-tool {
|
||||
background: @foundryNavBgColor;
|
||||
color: @foundryNavTextColor;
|
||||
border: 1px solid @foundryNavBorderColor;
|
||||
box-shadow: none;
|
||||
&:hover {
|
||||
background: @foundryNavBgColor;
|
||||
box-shadow: 0 0 8px @foundryNavContextShadow;
|
||||
}
|
||||
&.active {
|
||||
border-color: @foundryNavActiveBorderColor;
|
||||
background: @foundryNavActiveBgColor;
|
||||
box-shadow: 0 0 8px @foundryNavActiveGlow;
|
||||
}
|
||||
}
|
||||
}
|
||||
#players {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
h3 {
|
||||
background: @sidebarTabBackground;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
font-size: 17px;
|
||||
line-height: 30px;
|
||||
.dropShadow1();
|
||||
border-radius: 4px 4px 0 0;
|
||||
.players-mode {
|
||||
color: @foundryPlayersArrowColor;
|
||||
}
|
||||
}
|
||||
ol {
|
||||
margin: 4px 0;
|
||||
.player-name.self {
|
||||
color: inherit;
|
||||
font-weight: 700;
|
||||
}
|
||||
.player {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
.player-active {
|
||||
margin-top: 7px;
|
||||
&.active {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
381
less/update/components/sidebar-global.less
Normal file
|
@ -0,0 +1,381 @@
|
|||
#sidebar {
|
||||
border: none; //1px solid @colorBlue;
|
||||
&.collapsed {
|
||||
#sidebar-tabs {
|
||||
min-height: 370px;
|
||||
justify-content: center;
|
||||
& > .item.active {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar-tabs {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
justify-content: space-between;
|
||||
.dropShadow1();
|
||||
|
||||
.item {
|
||||
font-size: 16px;
|
||||
}
|
||||
.item.active {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*-----------
|
||||
** Chat Tab
|
||||
-----------*/
|
||||
|
||||
#chat-log {
|
||||
.chat-message {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
.dropShadow1();
|
||||
& > header {
|
||||
color: @colorRed;
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
margin-bottom: 4px;
|
||||
span {
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.notification-pip {
|
||||
color: @colorBlue;
|
||||
}
|
||||
|
||||
.sw5e.chat-card {
|
||||
.card-header {
|
||||
padding: 0;
|
||||
border: none;
|
||||
img {
|
||||
flex: 0 0 36px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
line-height: 36px;
|
||||
.russoOne(17px);
|
||||
border-bottom: none;
|
||||
&:hover {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin: 4px 0;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> * {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.card-buttons {
|
||||
margin: 4px 0;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
.openSans(13px, 700);
|
||||
padding: 4px 0;
|
||||
height: auto;
|
||||
line-height: 1.6;
|
||||
margin: 4px 0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
&:hover, &:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 4px 0 0;
|
||||
|
||||
span {
|
||||
padding: 0 4px 0 0;
|
||||
font-size: 10px;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dice-roll {
|
||||
.dice-formula {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.dice-total {
|
||||
border-radius: 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
#chat-controls {
|
||||
padding-top: 4px;
|
||||
}
|
||||
#chat-form textarea {
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------
|
||||
** Combat Tab
|
||||
-----------*/
|
||||
#combat {
|
||||
h3 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#combat-tracker {
|
||||
li.combatant {
|
||||
padding: 4px 0;
|
||||
background: none;
|
||||
.token-name {
|
||||
text-shadow: none;
|
||||
}
|
||||
h4 {
|
||||
color: @colorBlack;
|
||||
}
|
||||
.roll {
|
||||
background: none;
|
||||
&::before {
|
||||
content: "\f6cf";
|
||||
.fontAwesome();
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.initiative {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
#combat-controls {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Folders
|
||||
*/
|
||||
.sidebar-tab {
|
||||
.directory-header {
|
||||
margin-bottom: 4px;
|
||||
.header-search {
|
||||
position: relative;
|
||||
i.fa-search {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
}
|
||||
input {
|
||||
text-align: left;
|
||||
padding-left: 22px;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.subdirectory {
|
||||
border: none;
|
||||
margin-left: 8px;
|
||||
min-height: 8px;
|
||||
|
||||
}
|
||||
.directory-list {
|
||||
padding-bottom: 4px;
|
||||
.folder {
|
||||
& > .folder-header {
|
||||
line-height: default;
|
||||
padding: 0 0 0 8px;
|
||||
position: relative;
|
||||
border: none;
|
||||
h3 {
|
||||
padding: 8px 4px;
|
||||
.openSans(13px, 700);
|
||||
line-height: 1.6;
|
||||
& > i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
a {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 4px;
|
||||
height: 100%;
|
||||
padding: 0 4px;
|
||||
i {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&.create-folder {
|
||||
right: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.directory-item img {
|
||||
flex: 0 0 32px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
align-self: center;
|
||||
}
|
||||
.actor, .item, .journal, .table {
|
||||
border: none;
|
||||
.entity-name {
|
||||
.openSans(13px, 700);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#scenes {
|
||||
.subdirectory {
|
||||
border-left: none;
|
||||
}
|
||||
.scene {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
height: 128px;
|
||||
& + .scene {
|
||||
margin-top: 4px;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 99px;
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 0;
|
||||
}
|
||||
h3 {
|
||||
.openSans(13px, 700);
|
||||
text-align: left;
|
||||
text-shadow: none;
|
||||
padding: 4px 4px 4px 12px;
|
||||
line-height: 1.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#playlists {
|
||||
.directory-list {
|
||||
padding: 0 8px;
|
||||
li.playlist {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
border-top: inherit;
|
||||
.dropShadow1();
|
||||
.playlist-header {
|
||||
text-decoration: none;
|
||||
}
|
||||
li.sound {
|
||||
border: none;
|
||||
h4 {
|
||||
.openSans(13px, 400);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#compendium {
|
||||
.compendium-entity {
|
||||
margin: 0 4px;
|
||||
padding: 8px;
|
||||
.dropShadow1();
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
&+ .compendium-entity {
|
||||
margin-top: 4px;
|
||||
}
|
||||
h3 {
|
||||
background: none;
|
||||
border: none;
|
||||
.russoOne(17px);
|
||||
padding: 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
ol.compendium-list {
|
||||
li.compendium-pack {
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
.pack-title {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
a {
|
||||
.openSans(13px, 700);
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.status-icons {
|
||||
top: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#settings {
|
||||
h2 {
|
||||
border: none;
|
||||
margin: 0 8px;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
#game-details, #settings-game, #settings-documentation, #settings-access {
|
||||
padding: 0 8px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
}
|
295
less/update/components/sidebar-themes.less
Normal file
|
@ -0,0 +1,295 @@
|
|||
#sidebar-tabs {
|
||||
background: @sidebarTabBackground;
|
||||
& > .collapse {
|
||||
color: @sidebarTabLinkColor;
|
||||
}
|
||||
.item.active {
|
||||
color: @sidebarTabLinkColor;
|
||||
border-bottom: 3px solid @sidebarTabLinkUnderline;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------
|
||||
** Chat Tab
|
||||
-----------*/
|
||||
|
||||
#chat-log {
|
||||
.chat-message {
|
||||
background: @chatBackground;
|
||||
color: @bodyFontColor;
|
||||
& > header {
|
||||
color: @chatHeaderColor;
|
||||
border-bottom: 2px solid @chatHeaderBottomBorderColor;
|
||||
span {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.notification-pip {
|
||||
color: @chatNotificationColor;
|
||||
}
|
||||
|
||||
.sw5e.chat-card {
|
||||
|
||||
.card-header {
|
||||
h3 {
|
||||
color: @bodyFontColor;
|
||||
&:hover {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card-buttons {
|
||||
span {
|
||||
border: 1px solid @cardButtonBorder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
border-top: 1px solid @cardFooterBorder;
|
||||
|
||||
span {
|
||||
border-right: 1px solid @cardFooterSeparator;
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dice-roll {
|
||||
|
||||
.dice-formula {
|
||||
background: @diceFormulaBackground;
|
||||
color: @diceFormualColor;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dice-total {
|
||||
background: @diceTotalBackground;
|
||||
border: 1px solid @diceTotalBorder;
|
||||
box-shadow: 0 0 12px rgba(@diceTotalShadow,.8);
|
||||
&.success {
|
||||
color: @diceSuccessColor;
|
||||
}
|
||||
&.failure {
|
||||
color: @diceFailureColor;
|
||||
}
|
||||
&.critical {
|
||||
color: @diceCriticalColor;
|
||||
background: @diceCriticalBackground;
|
||||
box-shadow: 0 0 12px rgba(@diceCriticalColor,.5);
|
||||
}
|
||||
&.fumble {
|
||||
color: @diceFumbleColor;
|
||||
background: @diceFumbleBackground;
|
||||
box-shadow: 0 0 12px rgba(@diceFumbleColor,.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
#chat-controls {
|
||||
.roll-type-select {
|
||||
background: @inputBackgroundColor;
|
||||
}
|
||||
label {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
|
||||
}
|
||||
#chat-form textarea {
|
||||
background: @inputBackgroundColor;
|
||||
|
||||
}
|
||||
|
||||
/*-----------
|
||||
** Combat Tab
|
||||
-----------*/
|
||||
#combat {
|
||||
#combat-round {
|
||||
color: @combatRoundColor;
|
||||
border-bottom: 2px solid @combatRoundColor;
|
||||
.encounters {
|
||||
h4 {
|
||||
color: @combatRoundColor;
|
||||
}
|
||||
a {
|
||||
color: @linkSecondaryColor;
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#combat-tracker {
|
||||
//padding-top: 4px;
|
||||
li.combatant {
|
||||
color: @bodyFontColor;
|
||||
&:nth-child(even) {
|
||||
background: rgba(@altRowBackground, 0.5);
|
||||
}
|
||||
h4 {
|
||||
color: @bodyFontColor
|
||||
}
|
||||
.roll {
|
||||
color: @linkSecondaryColor;
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
.combatant-control {
|
||||
color: @combatCombatantControlColor;
|
||||
&.active {
|
||||
color: @combatCombatantControlColorActive;
|
||||
}
|
||||
}
|
||||
.token-resource {
|
||||
color: @combatTokenResourceColor;
|
||||
border-right: 1px solid @combatTokenResouceBorder;
|
||||
}
|
||||
&.active {
|
||||
color: @combatActiveCombatantColor;
|
||||
.initiative, h4 {
|
||||
color: @combatActiveCombatantColor;
|
||||
}
|
||||
}
|
||||
&.hidden {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
#combat-controls {
|
||||
border-top: 1px solid @combatControlsBorder;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Folders
|
||||
*/
|
||||
.sidebar-tab {
|
||||
.directory-header {
|
||||
.header-search {
|
||||
i.fa-search {
|
||||
color: @folderSearchIconColor;
|
||||
}
|
||||
input {
|
||||
background: @inputBackgroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.subdirectory {
|
||||
background: @folderSubdirectoryBackground;
|
||||
.folder {
|
||||
border-left: 2px solid rgba(@folderSubdirectoryBorder, 0.4);
|
||||
}
|
||||
}
|
||||
.directory-list {
|
||||
li + li {
|
||||
border-top: 1px solid @directoryListItemBorder;
|
||||
}
|
||||
.folder {
|
||||
& > .folder-header {
|
||||
background: @folderHeaderBackground;
|
||||
h3 {
|
||||
background: @folderHeaderBackground;
|
||||
color: @folderHeaderColor;
|
||||
& > i {
|
||||
color: @folderIconColor;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: @linkSecondaryColor;
|
||||
&:hover {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.collapsed > .folder-header {
|
||||
background: @folderHeaderBackground;
|
||||
}
|
||||
& + .entity {
|
||||
border-top: 1px solid @directoryListItemBorder;
|
||||
}
|
||||
}
|
||||
|
||||
.actor, .item, .journal, .table {
|
||||
background: @entityBackgroundColor;
|
||||
.entity-name {
|
||||
color: @entityNameColor;
|
||||
}
|
||||
&:nth-child(even) {
|
||||
background: rgba(@altRowBackground, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#scenes {
|
||||
.scene {
|
||||
border-top: 1px solid @sceneBorderColor;
|
||||
border-left: 4px solid @sceneBorderColor;
|
||||
&::after {
|
||||
box-shadow: 0 0 20px @sceneBorderColor inset;
|
||||
}
|
||||
h3 {
|
||||
background: @sceneBackgroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#playlists {
|
||||
.directory-list {
|
||||
li.playlist {
|
||||
background: @playlistBackgroundColor;
|
||||
.playlist-header {
|
||||
background: @playlistBackgroundColor;
|
||||
color: @colorRed;
|
||||
border-bottom: 2px solid @playlistHeaderBorder;
|
||||
}
|
||||
li.sound {
|
||||
color: @playlistSoundColor;
|
||||
|
||||
}
|
||||
a.sound-control {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#compendium {
|
||||
.compendium-entity {
|
||||
background: @compendiumEntityBackground !important;
|
||||
h3 {
|
||||
border-bottom: 2px solid @headerBorderColor;
|
||||
}
|
||||
ol.compendium-list {
|
||||
li.compendium-pack {
|
||||
&:nth-child(even) {
|
||||
background: rgba(@altRowBackground, 0.3);
|
||||
}
|
||||
.pack-title {
|
||||
.status-icons {
|
||||
color: @compendiumStatusIcon;
|
||||
}
|
||||
}
|
||||
footer.compendium-footer {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#settings {
|
||||
h2 {
|
||||
color: @headingColor;
|
||||
border-bottom: 2px solid @headerBorderColor;
|
||||
}
|
||||
#game-details, #settings-game, #settings-documentation, #settings-access {
|
||||
color: @bodyFontColor;
|
||||
}
|
||||
}
|
495
less/update/components/sidebar.less
Normal file
|
@ -0,0 +1,495 @@
|
|||
#sidebar {
|
||||
border: none; //1px solid @colorBlue;
|
||||
}
|
||||
|
||||
#sidebar-tabs {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: white;
|
||||
.dropShadow1();
|
||||
& > .collapse {
|
||||
color: @colorRed;
|
||||
}
|
||||
.item {
|
||||
font-size: 16px;
|
||||
}
|
||||
.item.active {
|
||||
color: @colorRed;
|
||||
border: none;
|
||||
border-bottom: 3px solid @colorRed;
|
||||
box-shadow: none;
|
||||
background: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------
|
||||
** Chat Tab
|
||||
-----------*/
|
||||
|
||||
#chat-log {
|
||||
.chat-message {
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
.dropShadow1();
|
||||
& > header {
|
||||
color: @colorRed;
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
margin-bottom: 4px;
|
||||
span {
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.notification-pip {
|
||||
color: @colorBlue;
|
||||
text-shadow: none;
|
||||
|
||||
}
|
||||
|
||||
.sw5e.chat-card {
|
||||
font-size: 13px;
|
||||
|
||||
.card-header {
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
||||
img {
|
||||
flex: 0 0 36px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
line-height: 36px;
|
||||
.russoOne(17px);
|
||||
color: @colorBlack;
|
||||
&:hover {
|
||||
color: @colorBlack;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin: 4px 0;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
> * {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.card-buttons {
|
||||
margin: 4px 0;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
border: 1px solid @colorLightGray;
|
||||
}
|
||||
|
||||
button {
|
||||
.openSans(13px, 700);
|
||||
padding: 4px 0;
|
||||
height: auto;
|
||||
line-height: 1.6;
|
||||
margin: 4px 0;
|
||||
background: @colorRed;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
&:hover, &:focus {
|
||||
background-color: lighten(@colorRed, 5);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 4px 0 0;
|
||||
border-top: 1px solid @colorLightBlue;
|
||||
|
||||
span {
|
||||
border-right: 2px groove #FFF;
|
||||
padding: 0 4px 0 0;
|
||||
font-size: 10px;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dice-roll {
|
||||
|
||||
.dice-formula {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
.dice-total {
|
||||
background: @colorPaleBlue;
|
||||
border: 1px solid @colorBlue;
|
||||
border-radius: 0;
|
||||
padding: 4px 0;
|
||||
box-shadow: 0 0 12px rgba(@colorBlue,.5);
|
||||
&.success {
|
||||
color: inherit;
|
||||
background: #c7d0c0;
|
||||
border: 1px solid #006c00;
|
||||
}
|
||||
&.failure {
|
||||
color: inherit;
|
||||
background: #ffdddd;
|
||||
border: 1px solid #6e0000;
|
||||
}
|
||||
&.critical {
|
||||
color: @colorGreen;
|
||||
background: @colorPaleGreen;
|
||||
box-shadow: 0 0 12px rgba(@colorGreen,.5);
|
||||
}
|
||||
&.fumble {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
#chat-controls {
|
||||
padding-top: 4px;
|
||||
label {
|
||||
color: @colorBlack;
|
||||
}
|
||||
|
||||
}
|
||||
#chat-form textarea {
|
||||
background: white;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------
|
||||
** Combat Tab
|
||||
-----------*/
|
||||
#combat {
|
||||
#combat-round {
|
||||
color: @colorRed;
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
.encounters {
|
||||
h4 {
|
||||
color: @colorRed;
|
||||
}
|
||||
a {
|
||||
color: @colorGray;
|
||||
&:hover {
|
||||
color: @colorRed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#combat-tracker {
|
||||
//padding-top: 4px;
|
||||
li.combatant {
|
||||
padding: 4px 0;
|
||||
color: @colorBlack;
|
||||
background: none;
|
||||
&:nth-child(even) {
|
||||
background: rgba(@colorPaleBlue, 0.5);
|
||||
}
|
||||
h4 {
|
||||
color: @colorBlack;
|
||||
text-shadow: none;
|
||||
}
|
||||
.roll {
|
||||
background: none;
|
||||
color: @colorGray;
|
||||
&::before {
|
||||
content: "\f6cf";
|
||||
.fontAwesome();
|
||||
font-size: 28px;
|
||||
}
|
||||
&:hover {
|
||||
color: @colorRed;
|
||||
}
|
||||
}
|
||||
.combatant-control {
|
||||
color: @colorLightGray;
|
||||
text-shadow: none;
|
||||
&.active {
|
||||
color: @colorDarkGray;
|
||||
}
|
||||
}
|
||||
.token-resource {
|
||||
color: @colorGray;
|
||||
border-right: 1px solid @colorLightGray;
|
||||
}
|
||||
.initiative {
|
||||
text-shadow: none;
|
||||
}
|
||||
&.active {
|
||||
color: @colorBlue;
|
||||
.initiative, h4 {
|
||||
color: @colorBlue;
|
||||
}
|
||||
}
|
||||
&.hidden {
|
||||
opacity: 0.5;
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
||||
}
|
||||
#combat-controls {
|
||||
padding-top: 0;
|
||||
border-top: 1px solid @colorBlue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Folders
|
||||
*/
|
||||
.sidebar-tab {
|
||||
.directory-header {
|
||||
margin-bottom: 4px;
|
||||
.header-search {
|
||||
position: relative;
|
||||
i.fa-search {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
color: @colorBlue;
|
||||
}
|
||||
input {
|
||||
text-align: left;
|
||||
padding-left: 22px;
|
||||
background: white;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.subdirectory {
|
||||
border: none;
|
||||
margin-left: 8px;
|
||||
background: white;
|
||||
min-height: 8px;
|
||||
.folder {
|
||||
border-left: 2px solid rgba(@colorBlack, 0.4);
|
||||
}
|
||||
}
|
||||
.directory-list {
|
||||
padding-bottom: 4px;
|
||||
li + li {
|
||||
border-top: 1px solid @colorBlue;
|
||||
}
|
||||
.folder {
|
||||
& > .folder-header {
|
||||
line-height: default;
|
||||
padding: 0 0 0 8px;
|
||||
position: relative;
|
||||
border: none;
|
||||
background: white;
|
||||
h3 {
|
||||
padding: 8px 4px;
|
||||
background: white;
|
||||
color: @colorBlack;
|
||||
.openSans(13px, 700);
|
||||
line-height: 1.6;
|
||||
& > i {
|
||||
margin-right: 4px;
|
||||
color: @colorBlue;
|
||||
}
|
||||
}
|
||||
a {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 4px;
|
||||
height: 100%;
|
||||
padding: 0 4px;
|
||||
color: @colorLightGray;
|
||||
&:hover {
|
||||
color: @colorRed;
|
||||
}
|
||||
i {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&.create-folder {
|
||||
right: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.collapsed > .folder-header {
|
||||
background: white;
|
||||
}
|
||||
& + .entity {
|
||||
border-top: 1px solid @colorBlue;
|
||||
}
|
||||
}
|
||||
.directory-item img {
|
||||
flex: 0 0 32px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
align-self: center;
|
||||
}
|
||||
.actor, .item, .journal, .table {
|
||||
background: white;
|
||||
border: none;
|
||||
.entity-name {
|
||||
.openSans(13px, 700);
|
||||
color: @colorBlack;
|
||||
}
|
||||
&:nth-child(even) {
|
||||
background: rgba(@colorPaleBlue, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#scenes {
|
||||
.subdirectory {
|
||||
border-left: none;
|
||||
}
|
||||
.scene {
|
||||
border: none;
|
||||
border-top: 1px solid @colorBlue;
|
||||
border-left: 4px solid @colorBlue;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
height: 128px;
|
||||
//margin-bottom: 4px;
|
||||
& + .scene {
|
||||
margin-top: 4px;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 99px;
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 0;
|
||||
box-shadow: 0 0 20px @colorBlue inset;
|
||||
}
|
||||
h3 {
|
||||
.openSans(13px, 700);
|
||||
text-align: left;
|
||||
text-shadow: none;
|
||||
padding: 4px 4px 4px 12px;
|
||||
background: white;
|
||||
line-height: 1.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#playlists {
|
||||
.directory-list {
|
||||
padding: 0 8px;
|
||||
li.playlist {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
margin-bottom: 8px;
|
||||
border-top: inherit;
|
||||
.dropShadow1();
|
||||
.playlist-header {
|
||||
background: white;
|
||||
color: @colorRed;
|
||||
text-decoration: none;
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
}
|
||||
li.sound {
|
||||
border: none;
|
||||
color: @colorBlack;
|
||||
h4 {
|
||||
.openSans(13px, 400);
|
||||
}
|
||||
|
||||
}
|
||||
a.sound-control {
|
||||
color: @colorRed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#compendium {
|
||||
.compendium-entity {
|
||||
margin: 0 4px;
|
||||
padding: 8px;
|
||||
background: white !important;
|
||||
.dropShadow1();
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
&+ .compendium-entity {
|
||||
margin-top: 4px;
|
||||
}
|
||||
h3 {
|
||||
border: none;
|
||||
color: @colorRed;
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
.russoOne(17px);
|
||||
padding: 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
ol.compendium-list {
|
||||
li.compendium-pack {
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
&:nth-child(even) {
|
||||
background: rgba(@colorPaleBlue, 0.3);
|
||||
}
|
||||
.pack-title {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
a {
|
||||
.openSans(13px, 700);
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.status-icons {
|
||||
top: 4px;
|
||||
color: @colorLightGray;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
footer.compendium-footer {
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#settings {
|
||||
h2 {
|
||||
color: @colorRed;
|
||||
border: none;
|
||||
border-bottom: 2px solid @colorBlue;
|
||||
margin: 0 8px;
|
||||
padding: 0;
|
||||
}
|
||||
#game-details, #settings-game, #settings-documentation, #settings-access {
|
||||
padding: 0 8px;
|
||||
margin: 0 0 8px;
|
||||
color: @colorBlack;
|
||||
}
|
||||
}
|
47
less/update/sw5e-dark.less
Normal file
|
@ -0,0 +1,47 @@
|
|||
@import "_variables.less";
|
||||
@import "_variables-dark.less";
|
||||
|
||||
body.dark-theme {
|
||||
.app {
|
||||
background: @primaryBackground;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: @headingColor;
|
||||
}
|
||||
h3 {
|
||||
border-bottom: 2px solid @headerBorderColor;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @linkColor;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
text-shadow: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 4px 8px;
|
||||
background-color: @blockquoteBackground;
|
||||
border: 1px solid @blockquoteBorder;
|
||||
box-shadow: @blockquoteShadow;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-width: 0 0 1px 0;
|
||||
border-bottom: 1px solid @hrColor;
|
||||
}
|
||||
@import "components/forms-themes.less";
|
||||
@import "components/sidebar-themes.less";
|
||||
@import "components/foundry-nav-themes.less";
|
||||
@import "components/foundry-app-window-themes.less";
|
||||
@import "components/actor-themes.less";
|
||||
}
|
185
less/update/sw5e-global.less
Normal file
|
@ -0,0 +1,185 @@
|
|||
/* open-sans-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/OpenSans-Regular.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/OpenSans-Italic.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('./fonts/OpenSans-Bold.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('./fonts/OpenSans-BoldItalic.ttf');
|
||||
}
|
||||
/* russo-one-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Russo One';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/RussoOne.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Russo One';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/RussoOne.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Russo One';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('./fonts/RussoOne.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Aurebesh';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./fonts/Aurebesh.ttf');
|
||||
}
|
||||
@import "_variables.less";
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
// ::-webkit-scrollbar {
|
||||
// width: 6px;
|
||||
// height: 6px;
|
||||
// }
|
||||
::-webkit-scrollbar-track {
|
||||
border: 1px solid @colorBlue;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
background: @colorBlue;
|
||||
border: none;
|
||||
}
|
||||
:root {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: @colorBlue @colorPaleBlue;
|
||||
}
|
||||
|
||||
body {
|
||||
.openSans(13px, 400);
|
||||
}
|
||||
|
||||
h1 {
|
||||
.russoOne(34px);
|
||||
}
|
||||
h2 {
|
||||
.russoOne(27px);
|
||||
}
|
||||
h3 {
|
||||
.russoOne(21px);
|
||||
}
|
||||
h4 {
|
||||
.russoOne(17px);
|
||||
}
|
||||
h5, h6 {
|
||||
.russoOne(13px);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
&:hover, &:active {
|
||||
text-shadow: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.app {
|
||||
border: none;// 1px solid @colorBlue;
|
||||
.dropShadow1();
|
||||
}
|
||||
#pause {
|
||||
img {display: none;}
|
||||
background: none;
|
||||
height: 128px;
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-left: -64px;
|
||||
left: 50%;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background: url("ui/pause-inner.svg") no-repeat 50% 50%;
|
||||
animation-name: pause-spin;
|
||||
animation-duration: 10000ms;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
margin-left: -64px;
|
||||
left: 50%;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background: url("ui/pause-outer.svg") no-repeat 50% 50%;
|
||||
animation-name: pause-spin;
|
||||
animation-duration: 5000ms;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
h3 {
|
||||
border-bottom: 0;
|
||||
line-height: 1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 256px;
|
||||
margin-left: -128px;
|
||||
margin-top: -13px;
|
||||
text-shadow: 0 0 24px @colorBlue;
|
||||
&::before, &::after {
|
||||
position: absolute;
|
||||
font-family: "Aurebesh", sans-serif;
|
||||
font-size: 13px;
|
||||
color: @colorGray;
|
||||
animation: none;
|
||||
opacity: 0.8;
|
||||
text-shadow: 0 0 8px @colorBlue;
|
||||
}
|
||||
&::before {
|
||||
content: "GAME";
|
||||
top: -13px;
|
||||
left: 42px;
|
||||
}
|
||||
&::after {
|
||||
content: "PAUSED";
|
||||
bottom: -13px;
|
||||
right: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@import "components/forms-global.less";
|
||||
@import "components/sidebar-global.less";
|
||||
@import "components/actor-global.less";
|
||||
|
||||
@keyframes pause-spin {
|
||||
from {
|
||||
transform:rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
47
less/update/sw5e-light.less
Normal file
|
@ -0,0 +1,47 @@
|
|||
@import "_variables.less";
|
||||
@import "_variables-light.less";
|
||||
|
||||
body.light-theme {
|
||||
.app {
|
||||
background: @primaryBackground;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: @headingColor;
|
||||
}
|
||||
h3 {
|
||||
border-bottom: 2px solid @headerBorderColor;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @linkColor;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
text-shadow: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 4px 8px;
|
||||
background-color: @blockquoteBackground;
|
||||
border: 1px solid @blockquoteBorder;
|
||||
box-shadow: @blockquoteShadow;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-width: 0 0 1px 0;
|
||||
border-bottom: 1px solid @hrColor;
|
||||
}
|
||||
@import "components/forms-themes.less";
|
||||
@import "components/sidebar-themes.less";
|
||||
@import "components/foundry-nav-themes.less";
|
||||
@import "components/foundry-app-window-themes.less";
|
||||
@import "components/actor-themes.less";
|
||||
}
|
61
less/update/sw5e-update.less
Normal file
|
@ -0,0 +1,61 @@
|
|||
@import "variables.less";
|
||||
|
||||
|
||||
|
||||
a {
|
||||
color: @colorRed;
|
||||
text-decoration: none;
|
||||
&:hover, &:active {
|
||||
text-shadow: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.app {
|
||||
background: @sheetBackground;
|
||||
border: none;// 1px solid @colorBlue;
|
||||
.dropShadow1();
|
||||
}
|
||||
|
||||
#context-menu {
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
color: @colorBlack;
|
||||
padding: 0 8px;
|
||||
ol.context-items {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @colorLightGray;
|
||||
.dropShadow2();
|
||||
li.context-item {
|
||||
&:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
i {
|
||||
color: @colorBlue;
|
||||
}
|
||||
&:hover {
|
||||
background: @colorRed;
|
||||
color: white;
|
||||
text-shadow: none;
|
||||
cursor: pointer;
|
||||
i {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
& + li {
|
||||
border-top: 1px solid @colorPaleGray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@import "components/forms.less";
|
||||
@import "components/sidebar.less";
|
|
@ -6,7 +6,7 @@ import AbilityTemplate from "../pixi/ability-template.js";
|
|||
import {SW5E} from '../config.js';
|
||||
|
||||
/**
|
||||
* Extend the base Actor class to implement additional logic specialized for SW5e.
|
||||
* Extend the base Actor class to implement additional system-specific logic for SW5e.
|
||||
*/
|
||||
export default class Actor5e extends Actor {
|
||||
|
||||
|
@ -20,48 +20,6 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @override
|
||||
* TODO: This becomes unnecessary after 0.7.x is released
|
||||
*/
|
||||
initialize() {
|
||||
try {
|
||||
this.prepareData();
|
||||
} catch(err) {
|
||||
console.error(`Failed to initialize data for ${this.constructor.name} ${this.id}:`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @override
|
||||
* TODO: This becomes unnecessary after 0.7.x is released
|
||||
*/
|
||||
prepareData() {
|
||||
const is07x = !isNewerVersion("0.7.1", game.data.version);
|
||||
if ( is07x ) this.data = duplicate(this._data);
|
||||
if (!this.data.img) this.data.img = CONST.DEFAULT_TOKEN;
|
||||
if ( !this.data.name ) this.data.name = "New " + this.entity;
|
||||
this.prepareBaseData();
|
||||
this.prepareEmbeddedEntities();
|
||||
if ( is07x ) this.applyActiveEffects();
|
||||
this.prepareDerivedData();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @override
|
||||
* TODO: This becomes unnecessary after 0.7.x is released
|
||||
*/
|
||||
applyActiveEffects() {
|
||||
if (!isNewerVersion("0.7.1", game.data.version)) return super.applyActiveEffects();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
prepareBaseData() {
|
||||
switch ( this.data.type ) {
|
||||
|
@ -116,6 +74,11 @@ export default class Actor5e extends Actor {
|
|||
abl.save = Math.max(abl.save, originalSaves[id].save);
|
||||
}
|
||||
}
|
||||
|
||||
// Inventory encumbrance
|
||||
data.attributes.encumbrance = this._computeEncumbrance(actorData);
|
||||
|
||||
// Prepare skills
|
||||
this._prepareSkills(actorData, bonuses, checkBonus, originalSkills);
|
||||
|
||||
// Determine Initiative Modifier
|
||||
|
@ -126,7 +89,8 @@ export default class Actor5e extends Actor {
|
|||
if ( joat ) init.prof = Math.floor(0.5 * data.attributes.prof);
|
||||
else if ( athlete ) init.prof = Math.ceil(0.5 * data.attributes.prof);
|
||||
else init.prof = 0;
|
||||
init.bonus = Number(init.value + (flags.initiativeAlert ? 5 : 0));
|
||||
init.value = init.value ?? 0;
|
||||
init.bonus = init.value + (flags.initiativeAlert ? 5 : 0);
|
||||
init.total = init.mod + init.prof + init.bonus;
|
||||
|
||||
// Prepare power-casting data
|
||||
|
@ -169,7 +133,7 @@ export default class Actor5e extends Actor {
|
|||
}
|
||||
return obj;
|
||||
}, {});
|
||||
data.prof = this.data.data.attributes.prof;
|
||||
data.prof = this.data.data.attributes.prof || 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -177,33 +141,36 @@ export default class Actor5e extends Actor {
|
|||
|
||||
/**
|
||||
* Return the features which a character is awarded for each class level
|
||||
* @param cls {Object} Data object for class, equivalent to Item5e.data or raw compendium entry
|
||||
* @param {string} className The class name being added
|
||||
* @param {string} archetypeName The archetype of the class being added, if any
|
||||
* @param {number} level The number of levels in the added class
|
||||
* @param {number} priorLevel The previous level of the added class
|
||||
* @return {Promise<Item5e[]>} Array of Item5e entities
|
||||
*/
|
||||
static async getClassFeatures(cls) {
|
||||
const level = cls.data.levels;
|
||||
const className = cls.name.toLowerCase();
|
||||
static async getClassFeatures({className="", archetypeName="", level=1, priorLevel=0}={}) {
|
||||
className = className.toLowerCase();
|
||||
archetypeName = archetypeName.slugify();
|
||||
|
||||
// Get the configuration of features which may be added
|
||||
const clsConfig = CONFIG.SW5E.classFeatures[className];
|
||||
if (!clsConfig) return [];
|
||||
let featureIDs = clsConfig["features"][level] || [];
|
||||
const subclassName = cls.data.subclass.toLowerCase().slugify();
|
||||
if (!clsConfig) return [];
|
||||
|
||||
// Identify subclass features
|
||||
if ( subclassName !== "" ) {
|
||||
const subclassConfig = clsConfig["subclasses"][subclassName];
|
||||
if ( subclassConfig !== undefined ) {
|
||||
const subclassFeatureIDs = subclassConfig["features"][level];
|
||||
if ( subclassFeatureIDs ) {
|
||||
featureIDs = featureIDs.concat(subclassFeatureIDs);
|
||||
}
|
||||
}
|
||||
else console.warn("Invalid subclass: " + subclassName);
|
||||
// Acquire class features
|
||||
let ids = [];
|
||||
for ( let [l, f] of Object.entries(clsConfig.features || {}) ) {
|
||||
l = parseInt(l);
|
||||
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
|
||||
}
|
||||
|
||||
// Acquire archetype features
|
||||
const subConfig = clsConfig.archetypes[archetypeName] || {};
|
||||
for ( let [l, f] of Object.entries(subConfig.features || {}) ) {
|
||||
l = parseInt(l);
|
||||
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
|
||||
}
|
||||
|
||||
// Load item data for all identified features
|
||||
const features = await Promise.all(featureIDs.map(id => fromUuid(id)));
|
||||
const features = await Promise.all(ids.map(id => fromUuid(id)));
|
||||
|
||||
// Class powers should always be prepared
|
||||
for ( const feature of features ) {
|
||||
|
@ -237,35 +204,28 @@ export default class Actor5e extends Actor {
|
|||
for (let u of updated instanceof Array ? updated : [updated]) {
|
||||
const item = this.items.get(u._id);
|
||||
if (!item || (item.data.type !== "class")) continue;
|
||||
const classData = duplicate(item.data);
|
||||
let changed = false;
|
||||
const updateData = expandObject(u);
|
||||
const config = {
|
||||
className: updateData.name || item.data.name,
|
||||
archetypeName: updateData.data.archetype || item.data.data.archetype,
|
||||
level: getProperty(updateData, "data.levels"),
|
||||
priorLevel: item ? item.data.data.levels : 0
|
||||
}
|
||||
|
||||
// Get and create features for an increased class level
|
||||
const newLevels = getProperty(u, "data.levels");
|
||||
if (newLevels && (newLevels > item.data.data.levels)) {
|
||||
classData.data.levels = newLevels;
|
||||
changed = true;
|
||||
}
|
||||
let changed = false;
|
||||
if ( config.level && (config.level > config.priorLevel)) changed = true;
|
||||
if ( config.archetypeName !== item.data.data.archetype ) changed = true;
|
||||
|
||||
// Get features for a newly changed subclass
|
||||
const newSubclass = getProperty(u, "data.subclass");
|
||||
if (newSubclass && (newSubclass !== item.data.data.subclass)) {
|
||||
classData.data.subclass = newSubclass;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Get the new features
|
||||
// Get features to create
|
||||
if ( changed ) {
|
||||
const features = await Actor5e.getClassFeatures(classData);
|
||||
if ( features.length ) toCreate.push(...features);
|
||||
const existing = new Set(this.items.map(i => i.name));
|
||||
const features = await Actor5e.getClassFeatures(config);
|
||||
for ( let f of features ) {
|
||||
if ( !existing.has(f.name) ) toCreate.push(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// De-dupe created items with ones that already exist (by name)
|
||||
if ( toCreate.length ) {
|
||||
const existing = new Set(this.items.map(i => i.name));
|
||||
toCreate = toCreate.filter(c => !existing.has(c.name));
|
||||
}
|
||||
return toCreate
|
||||
}
|
||||
|
||||
|
@ -301,9 +261,6 @@ export default class Actor5e extends Actor {
|
|||
const required = xp.max - prior;
|
||||
const pct = Math.round((xp.value - prior) * 100 / required);
|
||||
xp.pct = Math.clamped(pct, 0, 100);
|
||||
|
||||
// Inventory encumbrance
|
||||
data.attributes.encumbrance = this._computeEncumbrance(actorData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -369,17 +326,17 @@ export default class Actor5e extends Actor {
|
|||
}
|
||||
if ( joat && (skl.value === 0 ) ) multi = 0.5;
|
||||
|
||||
// Retain the maximum skill proficiency when skill proficiencies are merged
|
||||
if ( originalSkills ) {
|
||||
skl.value = Math.max(skl.value, originalSkills[id].value);
|
||||
}
|
||||
|
||||
// Compute modifier
|
||||
skl.bonus = checkBonus + skillBonus;
|
||||
skl.mod = data.abilities[skl.ability].mod;
|
||||
skl.prof = round(multi * data.attributes.prof);
|
||||
skl.total = skl.mod + skl.prof + skl.bonus;
|
||||
|
||||
// If we merged skills when transforming, take the highest bonus here.
|
||||
if (originalSkills && skl.value > 0.5) {
|
||||
skl.total = Math.max(skl.total, originalSkills[id].total);
|
||||
}
|
||||
|
||||
// Compute passive bonus
|
||||
const passive = observant && (feats.observantFeat.skills.includes(id)) ? 5 : 0;
|
||||
skl.passive = 10 + skl.total + passive;
|
||||
|
@ -489,8 +446,8 @@ export default class Actor5e extends Actor {
|
|||
else powers.pact.max = Math.max(1, Math.min(pl, 2), Math.min(pl - 8, 3), Math.min(pl - 13, 4));
|
||||
powers.pact.value = Math.min(powers.pact.value, powers.pact.max);
|
||||
} else {
|
||||
powers.pact.level = 0;
|
||||
powers.pact.max = 0;
|
||||
powers.pact.max = parseInt(powers.pact.override) || 0
|
||||
powers.pact.level = powers.pact.max > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -513,14 +470,14 @@ export default class Actor5e extends Actor {
|
|||
if ( !physicalItems.includes(i.type) ) return weight;
|
||||
const q = i.data.quantity || 0;
|
||||
const w = i.data.weight || 0;
|
||||
return weight + Math.round(q * w * 10) / 10;
|
||||
return weight + (q * w);
|
||||
}, 0);
|
||||
|
||||
// [Optional] add Currency Weight
|
||||
if ( game.settings.get("sw5e", "currencyWeight") ) {
|
||||
const currency = actorData.data.currency;
|
||||
const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0);
|
||||
weight += Math.round((numCoins * 10) / CONFIG.SW5E.encumbrance.currencyPerWeight) / 10;
|
||||
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||
}
|
||||
|
||||
// Determine the encumbrance size class
|
||||
|
@ -535,9 +492,10 @@ export default class Actor5e extends Actor {
|
|||
if ( this.getFlag("sw5e", "powerfulBuild") ) mod = Math.min(mod * 2, 8);
|
||||
|
||||
// Compute Encumbrance percentage
|
||||
weight = weight.toNearest(0.1);
|
||||
const max = actorData.data.abilities.str.value * CONFIG.SW5E.encumbrance.strMultiplier * mod;
|
||||
const pct = Math.clamped((weight* 100) / max, 0, 100);
|
||||
return { value: weight, max, pct, encumbered: pct > (2/3) };
|
||||
const pct = Math.clamped((weight * 100) / max, 0, 100);
|
||||
return { value: weight.toNearest(0.1), max, pct, encumbered: pct > (2/3) };
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -564,9 +522,6 @@ export default class Actor5e extends Actor {
|
|||
/** @override */
|
||||
async update(data, options={}) {
|
||||
|
||||
// TODO: 0.7.1 compatibility - remove when stable
|
||||
if ( !data.hasOwnProperty("data") ) data = expandObject(data);
|
||||
|
||||
// Apply changes in Actor size to Token width/height
|
||||
const newSize = getProperty(data, "data.traits.size");
|
||||
if ( newSize && (newSize !== getProperty(this.data, "data.traits.size")) ) {
|
||||
|
@ -577,7 +532,7 @@ export default class Actor5e extends Actor {
|
|||
data["token.width"] = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reset death save counters
|
||||
if ( (this.data.data.attributes.hp.value <= 0) && (getProperty(data, "data.attributes.hp.value") > 0) ) {
|
||||
setProperty(data, "data.attributes.death.success", 0);
|
||||
|
@ -594,7 +549,7 @@ export default class Actor5e extends Actor {
|
|||
async createOwnedItem(itemData, options) {
|
||||
|
||||
// Assume NPCs are always proficient with weapons and always have powers prepared
|
||||
if ( !this.isPC ) {
|
||||
if ( !this.hasPlayerOwner ) {
|
||||
let t = itemData.type;
|
||||
let initial = {};
|
||||
if ( t === "weapon" ) initial["data.proficient"] = true;
|
||||
|
@ -858,7 +813,7 @@ export default class Actor5e extends Actor {
|
|||
rollAbilitySave(abilityId, options={}) {
|
||||
const label = CONFIG.SW5E.abilities[abilityId];
|
||||
const abl = this.data.data.abilities[abilityId];
|
||||
|
||||
|
||||
// Construct parts
|
||||
const parts = ["@mod"];
|
||||
const data = {mod: abl.mod};
|
||||
|
@ -937,7 +892,7 @@ export default class Actor5e extends Actor {
|
|||
|
||||
// Take action depending on the result
|
||||
const success = roll.total >= 10;
|
||||
const d20 = roll.dice[0].total;
|
||||
const d20 = roll.dice[0].total;
|
||||
|
||||
// Save success
|
||||
if ( success ) {
|
||||
|
@ -1429,14 +1384,16 @@ export default class Actor5e extends Actor {
|
|||
if ( !original ) return;
|
||||
|
||||
// Get the Tokens which represent this actor
|
||||
const tokens = this.getActiveTokens(true);
|
||||
const tokenUpdates = tokens.map(t => {
|
||||
const tokenData = duplicate(original.data.token);
|
||||
tokenData._id = t.id;
|
||||
tokenData.actorId = original.id;
|
||||
return tokenData;
|
||||
});
|
||||
canvas.scene.updateEmbeddedEntity("Token", tokenUpdates);
|
||||
if ( canvas.ready ) {
|
||||
const tokens = this.getActiveTokens(true);
|
||||
const tokenUpdates = tokens.map(t => {
|
||||
const tokenData = duplicate(original.data.token);
|
||||
tokenData._id = t.id;
|
||||
tokenData.actorId = original.id;
|
||||
return tokenData;
|
||||
});
|
||||
canvas.scene.updateEmbeddedEntity("Token", tokenUpdates);
|
||||
}
|
||||
|
||||
// Delete the polymorphed Actor and maybe re-render the original sheet
|
||||
const isRendered = this.sheet.rendered;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import Item5e from "../../item/entity.js";
|
||||
import TraitSelector from "../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../apps/actor-flags.js";
|
||||
import MovementConfig from "../../apps/movement-config.js";
|
||||
import {SW5E} from '../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../effects.js";
|
||||
|
||||
/**
|
||||
* Extend the basic ActorSheet class to do all the SW5e things!
|
||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||
* This sheet is an Abstract layer which is not used.
|
||||
* @extends {ActorSheet}
|
||||
*/
|
||||
|
@ -43,8 +45,8 @@ export default 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`;
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/oldActor/limited-sheet.html";
|
||||
return `systems/sw5e/templates/actors/oldActor/${this.actor.data.type}-sheet.html`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -94,6 +96,9 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
}
|
||||
|
||||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
|
||||
|
@ -101,7 +106,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
this._prepareEffects(data);
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
|
@ -109,6 +114,28 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare the display of movement speed data for the Actor
|
||||
* @param {object} actorData
|
||||
* @returns {{primary: string, special: string}}
|
||||
* @private
|
||||
*/
|
||||
_getMovementSpeed(actorData) {
|
||||
const movement = actorData.data.attributes.movement;
|
||||
const speeds = [
|
||||
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
|
||||
[movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`],
|
||||
[movement.fly, `${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` + (movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")],
|
||||
[movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
|
||||
].filter(s => !!s[0]).sort((a, b) => b[0] - a[0]);
|
||||
return {
|
||||
primary: `${movement.walk || 0} ${movement.units}`,
|
||||
special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
||||
* @param {object} traits The raw traits data object from the actor data
|
||||
|
@ -147,43 +174,6 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare the data structure for Active Effects which are currently applied to the Actor.
|
||||
* @param {object} data The object of rendering data which is being prepared
|
||||
* @private
|
||||
*/
|
||||
_prepareEffects(data) {
|
||||
|
||||
// Define effect header categories
|
||||
const categories = {
|
||||
temporary: {
|
||||
label: "Temporary Effects",
|
||||
effects: []
|
||||
},
|
||||
passive: {
|
||||
label: "Passive Effects",
|
||||
effects: []
|
||||
},
|
||||
inactive: {
|
||||
label: "Inactive Effects",
|
||||
effects: []
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over active effects, classifying them into categories
|
||||
for ( let e of this.actor.effects ) {
|
||||
e._getSourceName(); // Trigger a lookup for the source name
|
||||
if ( e.data.disabled ) categories.inactive.effects.push(e);
|
||||
else if ( e.isTemporary ) categories.temporary.effects.push(e);
|
||||
else categories.passive.effects.push(e);
|
||||
}
|
||||
|
||||
// Add the prepared categories of effects to the rendering data
|
||||
return data.effects = categories;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Insert a power into the powerbook object when rendering the character sheet
|
||||
* @param {Object} data The Actor data being prepared
|
||||
|
@ -242,7 +232,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
registerSection(sl, lvl, CONFIG.SW5E.powerLevels[lvl], levels[sl]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Pact magic users have cantrips and a pact magic section
|
||||
if ( levels.pact && levels.pact.max ) {
|
||||
if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
|
||||
|
@ -363,7 +353,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
|
||||
|
||||
// Item summaries
|
||||
html.find('.item .item-name h4').click(event => this._onItemSummary(event));
|
||||
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
|
||||
|
||||
// Editable Only Listeners
|
||||
if ( this.isEditable ) {
|
||||
|
@ -383,6 +373,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
|
||||
|
||||
// Configure Special Flags
|
||||
html.find('.configure-movement').click(this._onMovementConfig.bind(this));
|
||||
html.find('.configure-flags').click(this._onConfigureFlags.bind(this));
|
||||
|
||||
// Owned Item management
|
||||
|
@ -393,8 +384,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
|
||||
|
||||
// Active Effect management
|
||||
html.find(".effect-control").click(this._onManageActiveEffect.bind(this));
|
||||
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
|
@ -565,7 +555,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
|
@ -576,9 +566,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// 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);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -731,28 +719,6 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Manage Active Effect instances through the Actor Sheet via effect control buttons.
|
||||
* @param {MouseEvent} event The left-click event on the effect control
|
||||
* @private
|
||||
*/
|
||||
_onManageActiveEffect(event) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
const li = a.closest(".effect");
|
||||
const effect = this.actor.effects.get(li.dataset.effectId);
|
||||
switch ( a.dataset.action ) {
|
||||
case "edit":
|
||||
return effect.sheet.render(true);
|
||||
case "delete":
|
||||
return effect.delete();
|
||||
case "toggle":
|
||||
return effect.update({disabled: !effect.data.disabled});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling an Ability check, either a test or a saving throw
|
||||
* @param {Event} event The originating click event
|
||||
|
@ -825,6 +791,18 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onMovementConfig(event) {
|
||||
event.preventDefault();
|
||||
new MovementConfig(this.object).render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
|
@ -839,90 +817,4 @@ export default 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);
|
||||
}
|
||||
}
|
638
module/actor/sheets/newSheet/character.js
Normal file
|
@ -0,0 +1,638 @@
|
|||
import ActorSheet5e from "../base.js";
|
||||
import Actor5e from "../../entity.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for player character type actors in the SW5E system.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @type {ActorSheet5e}
|
||||
*/
|
||||
export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||
|
||||
get template() {
|
||||
if (!game.user.isGM && this.actor.limited) return "systems/sw5e/templates/actors/newActor/limited-sheet.html";
|
||||
return "systems/sw5e/templates/actors/newActor/character-sheet.html";
|
||||
}
|
||||
/**
|
||||
* Define default rendering options for the NPC sheet
|
||||
* @return {Object}
|
||||
*/
|
||||
static get defaultOptions() {
|
||||
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["swalt", "sw5e", "sheet", "actor", "character"],
|
||||
blockFavTab: true,
|
||||
subTabs: null,
|
||||
width: 800,
|
||||
tabs: [{
|
||||
navSelector: ".root-tabs",
|
||||
contentSelector: ".sheet-body",
|
||||
initial: "attributes"
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Add some extra data when rendering the sheet to reduce the amount of logic required within the template.
|
||||
*/
|
||||
getData() {
|
||||
const sheetData = super.getData();
|
||||
|
||||
// Temporary HP
|
||||
let hp = sheetData.data.attributes.hp;
|
||||
if (hp.temp === 0) delete hp.temp;
|
||||
if (hp.tempmax === 0) delete hp.tempmax;
|
||||
|
||||
// Resources
|
||||
sheetData["resources"] = ["primary", "secondary", "tertiary"].reduce((arr, r) => {
|
||||
const res = sheetData.data.resources[r] || {};
|
||||
res.name = r;
|
||||
res.placeholder = game.i18n.localize("SW5E.Resource"+r.titleCase());
|
||||
if (res && res.value === 0) delete res.value;
|
||||
if (res && res.max === 0) delete res.max;
|
||||
return arr.concat([res]);
|
||||
}, []);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize and classify Owned Items for Character sheets
|
||||
* @private
|
||||
*/
|
||||
_prepareItems(data) {
|
||||
|
||||
// Categorize items as inventory, powerbook, features, and classes
|
||||
const inventory = {
|
||||
weapon: { label: "SW5E.ItemTypeWeaponPl", items: [], dataset: {type: "weapon"} },
|
||||
equipment: { label: "SW5E.ItemTypeEquipmentPl", items: [], dataset: {type: "equipment"} },
|
||||
consumable: { label: "SW5E.ItemTypeConsumablePl", items: [], dataset: {type: "consumable"} },
|
||||
tool: { label: "SW5E.ItemTypeToolPl", items: [], dataset: {type: "tool"} },
|
||||
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} },
|
||||
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
||||
};
|
||||
|
||||
// Partition items by category
|
||||
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, lightsaberforms] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
|
||||
// Item usage
|
||||
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));
|
||||
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type));
|
||||
|
||||
// Item toggle state
|
||||
this._prepareItemToggleState(item);
|
||||
|
||||
// Classify items into types
|
||||
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 === "archetype" ) arr[5].push(item);
|
||||
else if ( item.type === "classfeature" ) arr[6].push(item);
|
||||
else if ( item.type === "background" ) arr[7].push(item);
|
||||
else if ( item.type === "lightsaberform" ) arr[8].push(item);
|
||||
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
||||
return arr;
|
||||
}, [[], [], [], [], [], [], [], [], []]);
|
||||
|
||||
// Apply active item filters
|
||||
items = this._filterItems(items, this._filters.inventory);
|
||||
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 Features
|
||||
const features = {
|
||||
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
||||
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: false, dataset: {type: "classfeature"}, isClassfeature: true },
|
||||
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true },
|
||||
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true },
|
||||
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true },
|
||||
lightsaberform: { label: "SW5E.ItemTypeLightsaberForm", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true },
|
||||
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
||||
};
|
||||
for ( let f of feats ) {
|
||||
if ( f.data.activation.type ) features.active.items.push(f);
|
||||
else features.passive.items.push(f);
|
||||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
features.classes.items = classes;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
features.species.items = species;
|
||||
features.background.items = backgrounds;
|
||||
features.lightsaberform.items = lightsaberforms;
|
||||
|
||||
// Assign and return
|
||||
data.inventory = Object.values(inventory);
|
||||
data.powerbook = powerbook;
|
||||
data.preparedPowers = nPrepared;
|
||||
data.features = Object.values(features);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A helper method to establish the displayed preparation state for an item
|
||||
* @param {Item} item
|
||||
* @private
|
||||
*/
|
||||
_prepareItemToggleState(item) {
|
||||
if (item.type === "power") {
|
||||
const isAlways = getProperty(item.data, "preparation.mode") === "always";
|
||||
const isPrepared = getProperty(item.data, "preparation.prepared");
|
||||
item.toggleClass = isPrepared ? "active" : "";
|
||||
if ( isAlways ) item.toggleClass = "fixed";
|
||||
if ( isAlways ) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.always;
|
||||
else if ( isPrepared ) item.toggleTitle = CONFIG.SW5E.powerPreparationModes.prepared;
|
||||
else item.toggleTitle = game.i18n.localize("SW5E.PowerUnprepared");
|
||||
}
|
||||
else {
|
||||
const isActive = getProperty(item.data, "equipped");
|
||||
item.toggleClass = isActive ? "active" : "";
|
||||
item.toggleTitle = game.i18n.localize(isActive ? "SW5E.Equipped" : "SW5E.Unequipped");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* 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
|
||||
*/
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if ( !this.options.editable ) return;
|
||||
|
||||
// Inventory Functions
|
||||
html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
|
||||
|
||||
// Item State Toggling
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
|
||||
// Short and Long Rest
|
||||
html.find('.short-rest').click(this._onShortRest.bind(this));
|
||||
html.find('.long-rest').click(this._onLongRest.bind(this));
|
||||
|
||||
// Death saving throws
|
||||
html.find('.death-save').click(this._onDeathSave.bind(this));
|
||||
|
||||
// Send Languages to Chat onClick
|
||||
html.find('[data-options="share-languages"]').click(event => {
|
||||
event.preventDefault();
|
||||
let langs = this.actor.data.data.traits.languages.value.map(l => SW5E.languages[l] || l).join(", ");
|
||||
let custom = this.actor.data.data.traits.languages.custom;
|
||||
if (custom) langs += ", " + custom.replace(/;/g, ",");
|
||||
let content = `
|
||||
<div class="sw5e chat-card item-card" data-acor-id="${this.actor._id}">
|
||||
<header class="card-header flexrow">
|
||||
<img src="${this.actor.data.token.img}" title="" width="36" height="36" style="border: none;"/>
|
||||
<h3>Known Languages</h3>
|
||||
</header>
|
||||
<div class="card-content">${langs}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Send to Chat
|
||||
let rollWhisper = null;
|
||||
let rollBlind = false;
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
if (["gmroll", "blindroll"].includes(rollMode)) rollWhisper = ChatMessage.getWhisperIDs("GM");
|
||||
if (rollMode === "blindroll") rollBlind = true;
|
||||
ChatMessage.create({
|
||||
user: game.user._id,
|
||||
content: content,
|
||||
speaker: {
|
||||
actor: this.actor._id,
|
||||
token: this.actor.token,
|
||||
alias: this.actor.name
|
||||
},
|
||||
type: CONST.CHAT_MESSAGE_TYPES.OTHER
|
||||
});
|
||||
});
|
||||
|
||||
// Item Delete Confirmation
|
||||
html.find('.item-delete').off("click");
|
||||
html.find('.item-delete').click(event => {
|
||||
let li = $(event.currentTarget).parents('.item');
|
||||
let itemId = li.attr("data-item-id");
|
||||
let item = this.actor.getOwnedItem(itemId);
|
||||
new Dialog({
|
||||
title: `Deleting ${item.data.name}`,
|
||||
content: `<p>Are you sure you want to delete ${item.data.name}?</p>`,
|
||||
buttons: {
|
||||
Yes: {
|
||||
icon: '<i class="fa fa-check"></i>',
|
||||
label: 'Yes',
|
||||
callback: dlg => {
|
||||
this.actor.deleteOwnedItem(itemId);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: 'No'
|
||||
},
|
||||
},
|
||||
default: 'cancel'
|
||||
}).render(true);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling a death saving throw for the Character
|
||||
* @param {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onDeathSave(event) {
|
||||
event.preventDefault();
|
||||
return this.actor.rollDeathSave({event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Handle toggling the state of an Owned Item within the Actor
|
||||
* @param {Event} event The triggering click event
|
||||
* @private
|
||||
*/
|
||||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const attr = item.data.type === "power" ? "data.preparation.prepared" : "data.equipped";
|
||||
return item.update({[attr]: !getProperty(item.data, attr)});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Take a short rest, calling the relevant function on the Actor instance
|
||||
* @param {Event} event The triggering click event
|
||||
* @private
|
||||
*/
|
||||
async _onShortRest(event) {
|
||||
event.preventDefault();
|
||||
await this._onSubmit(event);
|
||||
return this.actor.shortRest();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Take a long rest, calling the relevant function on the Actor instance
|
||||
* @param {Event} event The triggering click event
|
||||
* @private
|
||||
*/
|
||||
async _onLongRest(event) {
|
||||
event.preventDefault();
|
||||
await this._onSubmit(event);
|
||||
return this.actor.longRest();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle mouse click events to convert currency to the highest possible denomination
|
||||
* @param {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
async _onConvertCurrency(event) {
|
||||
event.preventDefault();
|
||||
return Dialog.confirm({
|
||||
title: `${game.i18n.localize("SW5E.CurrencyConvert")}`,
|
||||
content: `<p>${game.i18n.localize("SW5E.CurrencyConvertHint")}</p>`,
|
||||
yes: () => this.actor.convertCurrency()
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
// Upgrade the number of class levels a character has and add features
|
||||
if ( itemData.type === "class" ) {
|
||||
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||
const classWasAlreadyPresent = !!cls;
|
||||
|
||||
// Add new features for class level
|
||||
if ( !classWasAlreadyPresent ) {
|
||||
Actor5e.getClassFeatures(itemData).then(features => {
|
||||
this.actor.createEmbeddedEntity("OwnedItem", features);
|
||||
});
|
||||
}
|
||||
|
||||
// If the actor already has the class, increment the level instead of creating a new item
|
||||
// then add new features as long as level increases
|
||||
if ( classWasAlreadyPresent ) {
|
||||
const lvl = cls.data.data.levels;
|
||||
const newLvl = Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level);
|
||||
if ( !(lvl === newLvl) ) {
|
||||
cls.update({"data.levels": newLvl});
|
||||
itemData.data.levels = newLvl;
|
||||
Actor5e.getClassFeatures(itemData).then(features => {
|
||||
this.actor.createEmbeddedEntity("OwnedItem", features);
|
||||
});
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
||||
|
||||
async function addFavorites(app, html, data) {
|
||||
// Thisfunction is adapted for the SwaltSheet from the Favorites Item
|
||||
// Tab Module created for Foundry VTT - by Felix Müller (Felix#6196 on Discord).
|
||||
// It is licensed under a Creative Commons Attribution 4.0 International License
|
||||
// and can be found at https://github.com/syl3r86/favtab.
|
||||
let favItems = [];
|
||||
let favFeats = [];
|
||||
let favPowers = {
|
||||
0: {
|
||||
isCantrip: true,
|
||||
powers: []
|
||||
},
|
||||
1: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power1.value,
|
||||
max: data.actor.data.powers.power1.max
|
||||
},
|
||||
2: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power2.value,
|
||||
max: data.actor.data.powers.power2.max
|
||||
},
|
||||
3: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power3.value,
|
||||
max: data.actor.data.powers.power3.max
|
||||
},
|
||||
4: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power4.value,
|
||||
max: data.actor.data.powers.power4.max
|
||||
},
|
||||
5: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power5.value,
|
||||
max: data.actor.data.powers.power5.max
|
||||
},
|
||||
6: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power6.value,
|
||||
max: data.actor.data.powers.power6.max
|
||||
},
|
||||
7: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power7.value,
|
||||
max: data.actor.data.powers.power7.max
|
||||
},
|
||||
8: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power8.value,
|
||||
max: data.actor.data.powers.power8.max
|
||||
},
|
||||
9: {
|
||||
powers: [],
|
||||
value: data.actor.data.powers.power9.value,
|
||||
max: data.actor.data.powers.power9.max
|
||||
}
|
||||
}
|
||||
|
||||
let powerCount = 0
|
||||
let items = data.actor.items;
|
||||
for (let item of items) {
|
||||
if (item.type == "class") continue;
|
||||
if (item.flags.favtab === undefined || item.flags.favtab.isFavourite === undefined) {
|
||||
item.flags.favtab = {
|
||||
isFavourite: false
|
||||
};
|
||||
}
|
||||
let isFav = item.flags.favtab.isFavourite;
|
||||
if (app.options.editable) {
|
||||
let favBtn = $(`<a class="item-control item-toggle item-fav ${isFav ? "active" : ""}" data-fav="${isFav}" title="${isFav ? "Remove from Favourites" : "Add to Favourites"}"><i class="fas fa-star"></i></a>`);
|
||||
favBtn.click(ev => {
|
||||
app.actor.getOwnedItem(item._id).update({
|
||||
"flags.favtab.isFavourite": !item.flags.favtab.isFavourite
|
||||
});
|
||||
});
|
||||
html.find(`.item[data-item-id="${item._id}"]`).find('.item-controls').prepend(favBtn);
|
||||
}
|
||||
|
||||
if (isFav) {
|
||||
item.powerComps = "";
|
||||
if (item.data.components) {
|
||||
let comps = item.data.components;
|
||||
let v = (comps.vocal) ? "V" : "";
|
||||
let s = (comps.somatic) ? "S" : "";
|
||||
let m = (comps.material) ? "M" : "";
|
||||
let c = (comps.concentration) ? true : false;
|
||||
let r = (comps.ritual) ? true : false;
|
||||
item.powerComps = `${v}${s}${m}`;
|
||||
item.powerCon = c;
|
||||
item.powerRit = r;
|
||||
}
|
||||
|
||||
item.editable = app.options.editable;
|
||||
switch (item.type) {
|
||||
case 'feat':
|
||||
if (item.flags.favtab.sort === undefined) {
|
||||
item.flags.favtab.sort = (favFeats.count + 1) * 100000; // initial sort key if not present
|
||||
}
|
||||
favFeats.push(item);
|
||||
break;
|
||||
case 'power':
|
||||
if (item.data.preparation.mode) {
|
||||
item.powerPrepMode = ` (${CONFIG.SW5E.powerPreparationModes[item.data.preparation.mode]})`
|
||||
}
|
||||
if (item.data.level) {
|
||||
favPowers[item.data.level].powers.push(item);
|
||||
} else {
|
||||
favPowers[0].powers.push(item);
|
||||
}
|
||||
powerCount++;
|
||||
break;
|
||||
default:
|
||||
if (item.flags.favtab.sort === undefined) {
|
||||
item.flags.favtab.sort = (favItems.count + 1) * 100000; // initial sort key if not present
|
||||
}
|
||||
favItems.push(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alter core CSS to fit new button
|
||||
// if (app.options.editable) {
|
||||
// html.find('.powerbook .item-controls').css('flex', '0 0 88px');
|
||||
// html.find('.inventory .item-controls, .features .item-controls').css('flex', '0 0 90px');
|
||||
// html.find('.favourite .item-controls').css('flex', '0 0 22px');
|
||||
// }
|
||||
|
||||
let tabContainer = html.find('.favtabtarget');
|
||||
data.favItems = favItems.length > 0 ? favItems.sort((a, b) => (a.flags.favtab.sort) - (b.flags.favtab.sort)) : false;
|
||||
data.favFeats = favFeats.length > 0 ? favFeats.sort((a, b) => (a.flags.favtab.sort) - (b.flags.favtab.sort)) : false;
|
||||
data.favPowers = powerCount > 0 ? favPowers : false;
|
||||
data.editable = app.options.editable;
|
||||
|
||||
await loadTemplates(['systems/sw5e/templates/actors/newActor/item.hbs']);
|
||||
let favtabHtml = $(await renderTemplate('systems/sw5e/templates/actors/newActor/template.hbs', data));
|
||||
favtabHtml.find('.item-name h4').click(event => app._onItemSummary(event));
|
||||
|
||||
if (app.options.editable) {
|
||||
favtabHtml.find('.item-image').click(ev => app._onItemRoll(ev));
|
||||
let handler = ev => app._onDragStart(ev);
|
||||
favtabHtml.find('.item').each((i, li) => {
|
||||
if (li.classList.contains("inventory-header")) return;
|
||||
li.setAttribute("draggable", true);
|
||||
li.addEventListener("dragstart", handler, false);
|
||||
});
|
||||
//favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event));
|
||||
favtabHtml.find('.item-edit').click(ev => {
|
||||
let itemId = $(ev.target).parents('.item')[0].dataset.itemId;
|
||||
app.actor.getOwnedItem(itemId).sheet.render(true);
|
||||
});
|
||||
favtabHtml.find('.item-fav').click(ev => {
|
||||
let itemId = $(ev.target).parents('.item')[0].dataset.itemId;
|
||||
let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite
|
||||
app.actor.getOwnedItem(itemId).update({
|
||||
"flags.favtab.isFavourite": val
|
||||
});
|
||||
});
|
||||
|
||||
// Sorting
|
||||
favtabHtml.find('.item').on('drop', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
let dropData = JSON.parse(ev.originalEvent.dataTransfer.getData('text/plain'));
|
||||
// if (dropData.actorId !== app.actor.id || dropData.data.type === 'power') return;
|
||||
if (dropData.actorId !== app.actor.id) return;
|
||||
let list = null;
|
||||
if (dropData.data.type === 'feat') list = favFeats;
|
||||
else list = favItems;
|
||||
let dragSource = list.find(i => i._id === dropData.data._id);
|
||||
let siblings = list.filter(i => i._id !== dropData.data._id);
|
||||
let targetId = ev.target.closest('.item').dataset.itemId;
|
||||
let dragTarget = siblings.find(s => s._id === targetId);
|
||||
|
||||
if (dragTarget === undefined) return;
|
||||
const sortUpdates = SortingHelpers.performIntegerSort(dragSource, {
|
||||
target: dragTarget,
|
||||
siblings: siblings,
|
||||
sortKey: 'flags.favtab.sort'
|
||||
});
|
||||
const updateData = sortUpdates.map(u => {
|
||||
const update = u.update;
|
||||
update._id = u.target._id;
|
||||
return update;
|
||||
});
|
||||
app.actor.updateEmbeddedEntity("OwnedItem", updateData);
|
||||
});
|
||||
}
|
||||
tabContainer.append(favtabHtml);
|
||||
// if(app.options.editable) {
|
||||
// let handler = ev => app._onDragItemStart(ev);
|
||||
// tabContainer.find('.item').each((i, li) => {
|
||||
// if (li.classList.contains("inventory-header")) return;
|
||||
// li.setAttribute("draggable", true);
|
||||
// li.addEventListener("dragstart", handler, false);
|
||||
// });
|
||||
//}
|
||||
// try {
|
||||
// if (game.modules.get("betterrolls5e") && game.modules.get("betterrolls5e").active) BetterRolls.addItemContent(app.object, favtabHtml, ".item .item-name h4", ".item-properties", ".item > .rollable div");
|
||||
// }
|
||||
// catch (err) {
|
||||
// // Better Rolls not found!
|
||||
// }
|
||||
Hooks.callAll("renderedSwaltSheet", app, html, data);
|
||||
}
|
||||
async function addSubTabs(app, html, data) {
|
||||
if(data.options.subTabs == null) {
|
||||
//let subTabs = []; //{subgroup: '', target: '', active: false}
|
||||
data.options.subTabs = {};
|
||||
html.find('[data-subgroup-selection] [data-subgroup]').each((idx, el) => {
|
||||
let subgroup = el.getAttribute('data-subgroup');
|
||||
let target = el.getAttribute('data-target');
|
||||
let targetObj = {target: target, active: el.classList.contains("active")}
|
||||
if(data.options.subTabs.hasOwnProperty(subgroup)) {
|
||||
data.options.subTabs[subgroup].push(targetObj);
|
||||
} else {
|
||||
data.options.subTabs[subgroup] = [];
|
||||
data.options.subTabs[subgroup].push(targetObj);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for(const group in data.options.subTabs) {
|
||||
data.options.subTabs[group].forEach(tab => {
|
||||
if(tab.active) {
|
||||
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).addClass('active');
|
||||
} else {
|
||||
html.find(`[data-subgroup=${group}][data-target=${tab.target}]`).removeClass('active');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
html.find('[data-subgroup-selection]').children().on('click', event => {
|
||||
let subgroup = event.target.closest('[data-subgroup]').getAttribute('data-subgroup');
|
||||
let target = event.target.closest('[data-target]').getAttribute('data-target');
|
||||
html.find(`[data-subgroup=${subgroup}]`).removeClass('active');
|
||||
html.find(`[data-subgroup=${subgroup}][data-target=${target}]`).addClass('active');
|
||||
let tabId = data.options.subTabs[subgroup].find(tab => {
|
||||
return tab.target == target
|
||||
});
|
||||
data.options.subTabs[subgroup].map(el => {
|
||||
if(el.target == target) {
|
||||
el.active = true;
|
||||
} else {
|
||||
el.active = false;
|
||||
}
|
||||
return el;
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Hooks.on("renderActorSheet5eCharacterNew", (app, html, data) => {
|
||||
addFavorites(app, html, data);
|
||||
addSubTabs(app, html, data);
|
||||
});
|
137
module/actor/sheets/newSheet/npc.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
import ActorSheet5e from "../base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for NPC type characters in the SW5E system.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @extends {ActorSheet5e}
|
||||
*/
|
||||
export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/limited-sheet.html";
|
||||
return `systems/sw5e/templates/actors/newActor/npc-sheet.html`;
|
||||
}
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "npc"],
|
||||
width: 600,
|
||||
width: 800,
|
||||
tabs: [{
|
||||
navSelector: ".root-tabs",
|
||||
contentSelector: ".sheet-body",
|
||||
initial: "attributes"
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the NPC sheet
|
||||
* @private
|
||||
*/
|
||||
_prepareItems(data) {
|
||||
|
||||
// Categorize Items as Features and Powers
|
||||
const features = {
|
||||
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 = 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));
|
||||
item.hasTarget = !!item.data.target && !(["none",""].includes(item.data.target.type));
|
||||
if ( item.type === "power" ) arr[0].push(item);
|
||||
else arr[1].push(item);
|
||||
return arr;
|
||||
}, [[], []]);
|
||||
|
||||
// Apply item filters
|
||||
powers = this._filterItems(powers, this._filters.powerbook);
|
||||
other = this._filterItems(other, this._filters.features);
|
||||
|
||||
// Organize Powerbook
|
||||
const powerbook = this._preparePowerbook(data, powers);
|
||||
|
||||
// Organize Features
|
||||
for ( let item of other ) {
|
||||
if ( item.type === "weapon" ) features.weapons.items.push(item);
|
||||
else if ( item.type === "feat" ) {
|
||||
if ( item.data.activation.type ) features.actions.items.push(item);
|
||||
else features.passive.items.push(item);
|
||||
}
|
||||
else features.equipment.items.push(item);
|
||||
}
|
||||
|
||||
// Assign and return
|
||||
data.features = Object.values(features);
|
||||
data.powerbook = powerbook;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
|
||||
// Challenge Rating
|
||||
const cr = parseFloat(data.data.details.cr || 0);
|
||||
const crLabels = {0: "0", 0.125: "1/8", 0.25: "1/4", 0.5: "1/2"};
|
||||
data.labels["cr"] = cr >= 1 ? String(cr) : crLabels[cr] || 1;
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Object Updates */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
let crv = "data.details.cr";
|
||||
let cr = formData[crv];
|
||||
cr = crs[cr] || parseFloat(cr);
|
||||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling NPC health values using the provided formula
|
||||
* @param {Event} event The original click event
|
||||
* @private
|
||||
*/
|
||||
_onRollHealthFormula(event) {
|
||||
event.preventDefault();
|
||||
const formula = this.actor.data.data.attributes.hp.formula;
|
||||
if ( !formula ) return;
|
||||
const hp = new Roll(formula).roll().total;
|
||||
AudioHelper.play({src: CONFIG.sounds.dice});
|
||||
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import ActorSheet5e from "./base.js";
|
||||
import Actor5e from "../entity.js";
|
||||
import ActorSheet5e from "../base.js";
|
||||
import Actor5e from "../../entity.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for player character type actors in the SW5E system.
|
||||
|
@ -68,9 +68,9 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} },
|
||||
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
||||
};
|
||||
|
||||
|
||||
// Partition items by category
|
||||
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, lightsaberforms] = data.items.reduce((arr, item) => {
|
||||
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
|
@ -93,10 +93,12 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
else if ( item.type === "archetype" ) arr[5].push(item);
|
||||
else if ( item.type === "classfeature" ) arr[6].push(item);
|
||||
else if ( item.type === "background" ) arr[7].push(item);
|
||||
else if ( item.type === "lightsaberform" ) arr[8].push(item);
|
||||
else if ( item.type === "fightingstyle" ) arr[8].push(item);
|
||||
else if ( item.type === "fightingmastery" ) arr[9].push(item);
|
||||
else if ( item.type === "lightsaberform" ) arr[10].push(item);
|
||||
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
||||
return arr;
|
||||
}, [[], [], [], [], [], [], [], [], []]);
|
||||
}, [[], [], [], [], [], [], [], [], [], [], []]);
|
||||
|
||||
// Apply active item filters
|
||||
items = this._filterItems(items, this._filters.inventory);
|
||||
|
@ -107,7 +109,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
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;
|
||||
i.totalWeight = (i.data.quantity * i.data.weight).toNearest(0.1);
|
||||
inventory[i.type].items.push(i);
|
||||
}
|
||||
|
||||
|
@ -121,10 +123,12 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
const features = {
|
||||
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
||||
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: false, dataset: {type: "classfeature"}, isClassfeature: true },
|
||||
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true },
|
||||
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true },
|
||||
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true },
|
||||
lightsaberform: { label: "SW5E.ItemTypeLightsaberForm", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true },
|
||||
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: true },
|
||||
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true },
|
||||
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true },
|
||||
fightingstyles: { label: "SW5E.ItemTypeFightingStylePl", items: [], hasActions: false, dataset: {type: "fightingstyle"}, isFightingstyle: true },
|
||||
fightingmasteries: { label: "SW5E.ItemTypeFightingMasteryPl", items: [], hasActions: false, dataset: {type: "fightingmastery"}, isFightingmastery: true },
|
||||
lightsaberforms: { label: "SW5E.ItemTypeLightsaberFormPl", items: [], hasActions: false, dataset: {type: "lightsaberform"}, isLightsaberform: true },
|
||||
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
||||
};
|
||||
|
@ -134,11 +138,13 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
features.classes.items = classes;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
features.species.items = species;
|
||||
features.background.items = backgrounds;
|
||||
features.lightsaberform.items = lightsaberforms;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
features.species.items = species;
|
||||
features.background.items = backgrounds;
|
||||
features.fightingstyles.items = fightingstyles;
|
||||
features.fightingmasteries.items = fightingmasteries;
|
||||
features.lightsaberforms.items = lightsaberforms;
|
||||
|
||||
// Assign and return
|
||||
data.inventory = Object.values(inventory);
|
||||
|
@ -183,9 +189,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
super.activateListeners(html);
|
||||
if ( !this.options.editable ) return;
|
||||
|
||||
// Inventory Functions
|
||||
html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
|
||||
|
||||
// Item State Toggling
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
|
||||
|
@ -193,8 +196,8 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
html.find('.short-rest').click(this._onShortRest.bind(this));
|
||||
html.find('.long-rest').click(this._onLongRest.bind(this));
|
||||
|
||||
// Death saving throws
|
||||
html.find('.death-save').click(this._onDeathSave.bind(this));
|
||||
// Rollable sheet actions
|
||||
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -204,14 +207,19 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
* @param {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onDeathSave(event) {
|
||||
_onSheetAction(event) {
|
||||
event.preventDefault();
|
||||
return this.actor.rollDeathSave({event: event});
|
||||
const button = event.currentTarget;
|
||||
switch( button.dataset.action ) {
|
||||
case "rollDeathSave":
|
||||
return this.actor.rollDeathSave({event: event});
|
||||
case "rollInitiative":
|
||||
return this.actor.rollInitiative({createCombatants: true});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Handle toggling the state of an Owned Item within the Actor
|
||||
* @param {Event} event The triggering click event
|
||||
|
@ -253,53 +261,39 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle mouse click events to convert currency to the highest possible denomination
|
||||
* @param {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
async _onConvertCurrency(event) {
|
||||
event.preventDefault();
|
||||
return Dialog.confirm({
|
||||
title: `${game.i18n.localize("SW5E.CurrencyConvert")}`,
|
||||
content: `<p>${game.i18n.localize("SW5E.CurrencyConvertHint")}</p>`,
|
||||
yes: () => this.actor.convertCurrency()
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
let addLevel = false;
|
||||
|
||||
// Upgrade the number of class levels a character has and add features
|
||||
if ( itemData.type === "class" ) {
|
||||
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||
const classWasAlreadyPresent = !!cls;
|
||||
let priorLevel = cls?.data.data.levels ?? 0;
|
||||
const hasClass = !!cls;
|
||||
|
||||
// Add new features for class level
|
||||
if ( !classWasAlreadyPresent ) {
|
||||
Actor5e.getClassFeatures(itemData).then(features => {
|
||||
this.actor.createEmbeddedEntity("OwnedItem", features);
|
||||
});
|
||||
// Increment levels instead of creating a new item
|
||||
if ( hasClass ) {
|
||||
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
|
||||
if ( next > priorLevel ) {
|
||||
itemData.levels = next;
|
||||
await cls.update({"data.levels": next});
|
||||
addLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the actor already has the class, increment the level instead of creating a new item
|
||||
// then add new features as long as level increases
|
||||
if ( classWasAlreadyPresent ) {
|
||||
const lvl = cls.data.data.levels;
|
||||
const newLvl = Math.min(lvl + 1, 20 + lvl - this.actor.data.data.details.level);
|
||||
if ( !(lvl === newLvl) ) {
|
||||
cls.update({"data.levels": newLvl});
|
||||
itemData.data.levels = newLvl;
|
||||
Actor5e.getClassFeatures(itemData).then(features => {
|
||||
this.actor.createEmbeddedEntity("OwnedItem", features);
|
||||
});
|
||||
}
|
||||
return
|
||||
// Add class features
|
||||
if ( !hasClass || addLevel ) {
|
||||
const features = await Actor5e.getClassFeatures({
|
||||
className: itemData.name,
|
||||
archetypeName: itemData.data.archetype,
|
||||
level: itemData.levels,
|
||||
priorLevel: priorLevel
|
||||
});
|
||||
await this.actor.createEmbeddedEntity("OwnedItem", features);
|
||||
}
|
||||
}
|
||||
|
||||
super._onDropItemCreate(itemData);
|
||||
// Default drop handling if levels were not added
|
||||
if ( !addLevel ) super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import ActorSheet5e from "../sheets/base.js";
|
||||
import ActorSheet5e from "../base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for NPC type characters in the SW5E system.
|
||||
|
@ -106,7 +106,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
|
||||
html.find(".health .rollable").click(this._onRollHPFormula.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -116,7 +116,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
* @param {Event} event The original click event
|
||||
* @private
|
||||
*/
|
||||
_onRollHealthFormula(event) {
|
||||
_onRollHPFormula(event) {
|
||||
event.preventDefault();
|
||||
const formula = this.actor.data.data.attributes.hp.formula;
|
||||
if ( !formula ) return;
|
|
@ -1,4 +1,4 @@
|
|||
import ActorSheet5e from "./base.js";
|
||||
import ActorSheet5e from "../base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for Vehicle type actors.
|
||||
|
@ -49,12 +49,9 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
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;
|
||||
const max = actorData.data.attributes.capacity.cargo;
|
||||
const pct = Math.clamped((totalWeight * 100) / max, 0, 100);
|
||||
return {value: totalWeight.toNearest(0.1), max, pct};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -89,6 +86,13 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_getMovementSpeed(actorData) {
|
||||
return {primary: "", special: ""};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the Vehicle sheet.
|
||||
* @private
|
|
@ -70,7 +70,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Helpers */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
* An application class which provides advanced configuration for special character flags which modify an Actor
|
||||
* @extends {BaseEntitySheet}
|
||||
* @implements {BaseEntitySheet}
|
||||
*/
|
||||
export default class ActorSheetFlags extends BaseEntitySheet {
|
||||
static get defaultOptions() {
|
||||
static get defaultOptions() {
|
||||
const options = super.defaultOptions;
|
||||
return mergeObject(options, {
|
||||
id: "actor-flags",
|
||||
|
@ -16,22 +16,16 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Configure the title of the special traits selection window to include the Actor name
|
||||
* @type {String}
|
||||
*/
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize('SW5E.FlagsTitle')}: ${this.object.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare data used to render the special Actor traits selection UI
|
||||
* @return {Object}
|
||||
*/
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
const data = {};
|
||||
data.actor = this.object;
|
||||
data.flags = this._getFlags();
|
||||
data.bonuses = this._getBonuses();
|
||||
|
@ -43,17 +37,18 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
/**
|
||||
* Prepare an object of flags data which groups flags by section
|
||||
* Add some additional data for rendering
|
||||
* @return {Object}
|
||||
* @return {object}
|
||||
*/
|
||||
_getFlags() {
|
||||
const flags = {};
|
||||
const baseData = this.entity._data;
|
||||
for ( let [k, v] of Object.entries(CONFIG.SW5E.characterFlags) ) {
|
||||
if ( !flags.hasOwnProperty(v.section) ) flags[v.section] = {};
|
||||
let flag = duplicate(v);
|
||||
flag.type = v.type.name;
|
||||
flag.isCheckbox = v.type === Boolean;
|
||||
flag.isSelect = v.hasOwnProperty('choices');
|
||||
flag.value = this.entity.getFlag("sw5e", k);
|
||||
flag.value = getProperty(baseData.flags, `sw5e.${k}`);
|
||||
flags[v.section][`flags.sw5e.${k}`] = flag;
|
||||
}
|
||||
return flags;
|
||||
|
@ -63,7 +58,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
|
||||
/**
|
||||
* Get the bonuses fields and their localization strings
|
||||
* @return {Array}
|
||||
* @return {Array<object>}
|
||||
* @private
|
||||
*/
|
||||
_getBonuses() {
|
||||
|
@ -82,17 +77,14 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
{name: "data.bonuses.power.dc", label: "SW5E.BonusPowerDC"}
|
||||
];
|
||||
for ( let b of bonuses ) {
|
||||
b.value = getProperty(this.object.data, b.name) || "";
|
||||
b.value = getProperty(this.object._data, b.name) || "";
|
||||
}
|
||||
return bonuses;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Update the Actor using the configured flags
|
||||
* Remove/unset any flags which are no longer configured
|
||||
*/
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const actor = this.object;
|
||||
let updateData = expandObject(formData);
|
||||
|
@ -100,10 +92,12 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
// Unset any flags which are "false"
|
||||
let unset = false;
|
||||
const flags = updateData.flags.sw5e;
|
||||
//clone flags to dnd5e for module compatability
|
||||
updateData.flags.dnd5e = updateData.flags.sw5e
|
||||
for ( let [k, v] of Object.entries(flags) ) {
|
||||
if ( [undefined, null, "", false, 0].includes(v) ) {
|
||||
delete flags[k];
|
||||
if ( hasProperty(actor.data.flags, `sw5e.${k}`) ) {
|
||||
if ( hasProperty(actor._data.flags, `sw5e.${k}`) ) {
|
||||
unset = true;
|
||||
flags[`-=${k}`] = null;
|
||||
}
|
||||
|
@ -118,10 +112,6 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
}
|
||||
|
||||
// Diff the data against any applied overrides and apply
|
||||
// TODO: Remove this logical gate once 0.7.x is release channel
|
||||
if ( !isNewerVersion("0.7.1", game.data.version) ){
|
||||
updateData = diffObject(this.object.data, updateData);
|
||||
}
|
||||
await actor.update(updateData, {diff: false});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* A specialized Dialog subclass for casting a cast item at a certain level
|
||||
* @type {Dialog}
|
||||
*/
|
||||
export class CastDialog extends Dialog {
|
||||
constructor(actor, item, dialogData={}, options={}) {
|
||||
super(dialogData, options);
|
||||
this.options.classes = ["sw5e", "dialog"];
|
||||
|
||||
/**
|
||||
* Store a reference to the Actor entity which is casting the cast
|
||||
* @type {Actor5e}
|
||||
*/
|
||||
this.actor = actor;
|
||||
|
||||
/**
|
||||
* Store a reference to the Item entity which is the cast being cast
|
||||
* @type {Item5e}
|
||||
*/
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Rendering */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A constructor function which displays the Cast Cast Dialog app for a given Actor and Item.
|
||||
* Returns a Promise which resolves to the dialog FormData once the workflow has been completed.
|
||||
* @param {Actor5e} actor
|
||||
* @param {Item5e} item
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async create(actor, item) {
|
||||
const ad = actor.data.data;
|
||||
const id = item.data.data;
|
||||
|
||||
// Determine whether the cast may be upcast
|
||||
const lvl = id.level;
|
||||
const canUpcast = (lvl > 0) && CONFIG.SW5E.castUpcastModes.includes(id.preparation.mode);
|
||||
|
||||
// Determine the levels which are feasible
|
||||
let lmax = 0;
|
||||
const castLevels = Array.fromRange(10).reduce((arr, i) => {
|
||||
if ( i < lvl ) return arr;
|
||||
const l = ad.casts["cast"+i] || {max: 0, override: null};
|
||||
let max = parseInt(l.override || l.max || 0);
|
||||
let slots = Math.clamped(parseInt(l.value || 0), 0, max);
|
||||
if ( max > 0 ) lmax = i;
|
||||
arr.push({
|
||||
level: i,
|
||||
label: i > 0 ? `${CONFIG.SW5E.castLevels[i]} (${slots} Slots)` : CONFIG.SW5E.castLevels[i],
|
||||
canCast: canUpcast && (max > 0),
|
||||
hasSlots: slots > 0
|
||||
});
|
||||
return arr;
|
||||
}, []).filter(sl => sl.level <= lmax);
|
||||
|
||||
const pact = ad.casts.pact;
|
||||
if (pact.level >= lvl) {
|
||||
// If this character has pact slots, present them as an option for
|
||||
// casting the cast.
|
||||
castLevels.push({
|
||||
level: 'pact',
|
||||
label: game.i18n.localize('SW5E.CastLevelPact')
|
||||
+ ` (${game.i18n.localize('SW5E.Level')} ${pact.level}) `
|
||||
+ `(${pact.value} ${game.i18n.localize('SW5E.Slots')})`,
|
||||
canCast: canUpcast,
|
||||
hasSlots: pact.value > 0
|
||||
});
|
||||
}
|
||||
|
||||
const canCast = castLevels.some(l => l.hasSlots);
|
||||
|
||||
// Render the Cast casting template
|
||||
const html = await renderTemplate("systems/sw5e/templates/apps/cast-cast.html", {
|
||||
item: item.data,
|
||||
canCast: canCast,
|
||||
canUpcast: canUpcast,
|
||||
castLevels,
|
||||
hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget
|
||||
});
|
||||
|
||||
// Create the Dialog and return as a Promise
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = new this(actor, item, {
|
||||
title: `${item.name}: Cast Configuration`,
|
||||
content: html,
|
||||
buttons: {
|
||||
cast: {
|
||||
icon: '<i class="fas fa-magic"></i>',
|
||||
label: "Cast",
|
||||
callback: html => resolve(new FormData(html[0].querySelector("#cast-config-form")))
|
||||
}
|
||||
},
|
||||
default: "cast",
|
||||
close: reject
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
}
|
32
module/apps/movement-config.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
*/
|
||||
export default class MovementConfig extends BaseEntitySheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
title: "SW5E.MovementConfig",
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||
width: 240,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
const data = {
|
||||
movement: duplicate(this.entity._data.data.attributes.movement),
|
||||
units: CONFIG.SW5E.movementUnits
|
||||
}
|
||||
for ( let [k, v] of Object.entries(data.movement) ) {
|
||||
if ( ["units", "hover"].includes(k) ) continue;
|
||||
data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -36,7 +36,7 @@ export default 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" ) {
|
||||
|
@ -49,7 +49,7 @@ export default 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* A specialized form used to select from a checklist of attributes, traits, or properties
|
||||
* @extends {FormApplication}
|
||||
* @implements {FormApplication}
|
||||
*/
|
||||
export default class TraitSelector extends FormApplication {
|
||||
|
||||
|
@ -36,7 +36,7 @@ export default class TraitSelector extends FormApplication {
|
|||
getData() {
|
||||
|
||||
// Get current values
|
||||
let attr = getProperty(this.object.data, this.attribute) || {};
|
||||
let attr = getProperty(this.object._data, this.attribute) || {};
|
||||
attr.value = attr.value || [];
|
||||
|
||||
// Populate choices
|
||||
|
|
|
@ -11,14 +11,16 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
|
|||
const d = roll.dice[0];
|
||||
|
||||
// Ensure it is an un-modified d20 roll
|
||||
const isD20 = (d.faces === 20) && ( d.results.length === 1 );
|
||||
const isD20 = (d.faces === 20) && ( d.values.length === 1 );
|
||||
if ( !isD20 ) return;
|
||||
const isModifiedRoll = ("success" in d.results[0]) || d.options.marginSuccess || d.options.marginFailure;
|
||||
if ( isModifiedRoll ) return;
|
||||
|
||||
// Highlight successes and failures
|
||||
if ( d.options.critical && (d.total >= d.options.critical) ) html.find(".dice-total").addClass("critical");
|
||||
else if ( d.options.fumble && (d.total <= d.options.fumble) ) html.find(".dice-total").addClass("fumble");
|
||||
const critical = d.options.critical || 20;
|
||||
const fumble = d.options.fumble || 1;
|
||||
if ( d.total >= critical ) html.find(".dice-total").addClass("critical");
|
||||
else if ( d.total <= fumble ) html.find(".dice-total").addClass("fumble");
|
||||
else if ( d.options.target ) {
|
||||
if ( roll.total >= d.options.target ) html.find(".dice-total").addClass("success");
|
||||
else html.find(".dice-total").addClass("failure");
|
||||
|
@ -33,7 +35,8 @@ export const highlightCriticalSuccessFailure = function(message, html, data) {
|
|||
export const displayChatActionButtons = function(message, html, data) {
|
||||
const chatCard = html.find(".sw5e.chat-card");
|
||||
if ( chatCard.length > 0 ) {
|
||||
html.find(".flavor-text").remove();
|
||||
const flavor = html.find(".flavor-text");
|
||||
if ( flavor.text() === html.find(".item-name").text() ) flavor.remove();
|
||||
|
||||
// If the user is the message author or the actor owner, proceed
|
||||
let actor = game.actors.get(data.message.speaker.actor);
|
||||
|
|
|
@ -1,35 +1,3 @@
|
|||
export const ClassFeatures = {
|
||||
"berserker": {
|
||||
"archetypes": {
|
||||
"addicted-approach": {
|
||||
"label": "Addicted Approach",
|
||||
"source": "PHB",
|
||||
"features": {
|
||||
"3": ["Compendium.sw5e.archetypes.PCwepUZqHYlxr4T3", "Compendium.sw5e.classfeatures.efOA0nrvUqKJOOeP", "Compendium.sw5e.classfeatures.nT6AfpQXSZ4IeChO"],
|
||||
"6": ["Compendium.sw5e.classfeatures.GbJDWzoTKWL7sEpR"],
|
||||
"10": ["Compendium.sw5e.classfeatures.3jqPPd5qJBBnonPw"],
|
||||
"14": ["Compendium.sw5e.classfeatures.xzRNHB2M2HdOZzr7"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"1": ["Compendium.sw5e.classfeatures.IDt6duVrBzL8euRc", "Compendium.sw5e.classfeatures.rPOLy96fW96N2UPg"],
|
||||
"2": ["Compendium.sw5e.classfeatures.DlYiCiG39R0goG9u", "Compendium.sw5e.classfeatures.FbSpxpXm1xONn0na", "Compendium.sw5e.classfeatures.KDiQ8O2evV2Z1YTo", "Compendium.sw5e.classfeatures.Q1JyHnVs9iIEBs91", "Compendium.sw5e.classfeatures.ROdICoWR82v6A2Rf", "Compendium.sw5e.classfeatures.cdCx5Hvq2rYRMzRj", "Compendium.sw5e.classfeatures.dTdbL8dypa6BAdnP", "Compendium.sw5e.classfeatures.h1uDhP1tEOuvjRw6", "Compendium.sw5e.classfeatures.hMiA075EKBBOL2cv", "Compendium.sw5e.classfeatures.sgJdISZMtwv08WPJ", "Compendium.sw5e.classfeatures.v4CZJ8LBMl5PYZCO"],
|
||||
"3": ["Compendium.sw5e.classfeatures.kzwSN9SabKgWZZvU"],
|
||||
"4": ["Compendium.sw5e.classfeatures.9oyy0MMqEws2qoil"],
|
||||
"5": ["Compendium.sw5e.classfeatures.dPWmHiWmpnhHTsgd"],
|
||||
"7": ["Compendium.sw5e.classfeatures.Cid5ujSdnooH0vMm", "Compendium.sw5e.classfeatures.WTBhKJgkArQI3Tgv", "Compendium.sw5e.classfeatures.oiT3TJxzRWPKAX9E", "Compendium.sw5e.classfeatures.pMEmIt3NWThbee8k", "Compendium.sw5e.classfeatures.qWV5YogZcpZ3Y3xj"],
|
||||
"9": ["Compendium.sw5e.classfeatures.bi8G8H5Ur9B3BAyM"],
|
||||
"11": ["Compendium.sw5e.classfeatures.eWbTifdXJvvXT4CV"],
|
||||
"13": ["Compendium.sw5e.classfeatures.Hg8zYh1iXL0DGUVq", "Compendium.sw5e.classfeatures.QRnYiJmRk18ekE9v", "Compendium.sw5e.classfeatures.sfEr8ZBFVddlfLeF", "Compendium.sw5e.classfeatures.yGC9VzT840qQWxca"],
|
||||
"15": ["Compendium.sw5e.classfeatures.YHPUv9lN3nCapAgP"],
|
||||
"18": ["Compendium.sw5e.classfeatures.fFKNqUAWh0ZOhvRc"],
|
||||
"20": ["Compendium.sw5e.classfeatures.IWTDawTUf79eWbEV"]
|
||||
}
|
||||
},
|
||||
"consular": {
|
||||
"features": {
|
||||
"20": ["Compendium.sw5e.classfeatures.gSGeitc98ItAwhfF"]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -27,41 +27,14 @@ export const _getInitiativeFormula = function(combatant) {
|
|||
return parts.filter(p => p !== null).join(" + ");
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* TODO: A temporary shim until 0.7.x becomes stable
|
||||
* @override
|
||||
* When the Combat encounter updates - re-render open Actor sheets for combatants in the encounter.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
Hooks.on("updateCombat", (combat, data, options, userId) => {
|
||||
const updateTurn = ("turn" in data) || ("round" in data);
|
||||
if ( !updateTurn ) return;
|
||||
for ( let t of combat.turns ) {
|
||||
const a = t.actor;
|
||||
if ( t.actor ) t.actor.sheet.render(false);
|
||||
}
|
||||
return attributes;
|
||||
};
|
||||
});
|
||||
|
|
321
module/config.js
|
@ -325,17 +325,31 @@ SW5E.armorPropertiesTypes = {
|
|||
"Versatile": "SW5E.ArmorProperVersatile"
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/**
|
||||
* The valid units of measure for movement distances in the game system.
|
||||
* By default this uses the imperial units of feet and miles.
|
||||
* @type {Object<string,string>}
|
||||
*/
|
||||
SW5E.movementUnits = {
|
||||
"ft": "SW5E.DistFt",
|
||||
"mi": "SW5E.DistMi"
|
||||
}
|
||||
|
||||
/**
|
||||
* The valid units of measure for the range of an action or effect.
|
||||
* This object automatically includes the movement units from SW5E.movementUnits
|
||||
* @type {Object<string,string>}
|
||||
*/
|
||||
SW5E.distanceUnits = {
|
||||
"none": "SW5E.None",
|
||||
"self": "SW5E.DistSelf",
|
||||
"touch": "SW5E.DistTouch",
|
||||
"ft": "SW5E.DistFt",
|
||||
"mi": "SW5E.DistMi",
|
||||
"spec": "SW5E.Special",
|
||||
"any": "SW5E.DistAny"
|
||||
};
|
||||
for ( let [k, v] of Object.entries(SW5E.movementUnits) ) {
|
||||
SW5E.distanceUnits[k] = v;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -413,7 +427,7 @@ SW5E.healingTypes = {
|
|||
* Enumerate the denominations of hit dice which can apply to classes in the SW5E system
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12"];
|
||||
SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12", "d20"];
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -461,15 +475,14 @@ SW5E.skills = {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
SW5E.powerPreparationModes = {
|
||||
"prepared": "SW5E.PowerPrepPrepared",
|
||||
"always": "SW5E.PowerPrepAlways",
|
||||
"atwill": "SW5E.PowerPrepAtWill",
|
||||
"innate": "SW5E.PowerPrepInnate",
|
||||
"prepared": "SW5E.PowerPrepPrepared"
|
||||
"innate": "SW5E.PowerPrepInnate"
|
||||
};
|
||||
|
||||
SW5E.powerUpcastModes = ["always", "pact", "prepared"];
|
||||
|
||||
|
||||
SW5E.powerProgression = {
|
||||
"none": "SW5E.PowerNone",
|
||||
"full": "SW5E.PowerProgFull",
|
||||
|
@ -808,60 +821,294 @@ SW5E.classFeatures = ClassFeatures;
|
|||
|
||||
// Configure Optional Character Flags
|
||||
SW5E.characterFlags = {
|
||||
"adaptiveResilience": {
|
||||
name: "SW5E.FlagsAdaptiveResilience",
|
||||
hint: "Prolongued use of technology allows members of your species to readily adapt to its effects. You have advantage on Strength and Constitution saving throws against tech powers.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"aggressive": {
|
||||
name: "SW5E.FlagsAggressive",
|
||||
hint: "As a bonus action, you can move up to your speed toward an enemy of your choice that you can see or hear. You must end this move closer to the enemy than you started.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"amphibious": {
|
||||
name: "SW5E.FlagsAmphibious",
|
||||
hint: "You can breathe air and water.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"armorIntegration": {
|
||||
name: "SW5E.FlagsArmorIntegration",
|
||||
hint: "You cannot wear armor, but you can have the armor professionally integrated into your chassis over the course of a long rest. This work must be done by someone proficient with astrotech’s implements. You must be proficient in armor in order to have it integrated.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"businessSavvy": {
|
||||
name: "SW5E.FlagsBusinessSavvy",
|
||||
hint: "Whenever you make a Charisma (Persuasion) check involving haggling you are considered to have expertise in the Persuasion skill.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"cannibalize": {
|
||||
name: "SW5E.FlagsCannibalize",
|
||||
hint: "If you spend at least 1 minute devouring the corpse of a beast or humanoid, you gain temporary hit points equal to your Constitution modifier. Once you've used this feature, you must complete a short or long rest before you can use it again.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"closedMind": {
|
||||
name: "SW5E.FlagsClosedMind",
|
||||
hint: "Members of your species have a natural attunement to the Force, which makes them resistant to its powers. You have advantage on Wisdom and Charisma saving throws against force powers.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"crudeWeaponSpecialists": {
|
||||
name: "SW5E.FlagsCrudeWeaponSpecialists",
|
||||
hint: "Members of your species are used to making do with less. You can spend 1 hour, which you can do over the course of a short rest, crafting a weapon out of loose materials. You can craft any simple kinetic weapon, but the weapon’s damage suffers a -1 penalty.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"defiant": {
|
||||
name: "SW5E.FlagsDefiant",
|
||||
hint: "Members of your species are known to be stubborn and often refuse to give up, even against the worst odds. When you or a creature you can see that can see and understand you makes an ability check, attack roll, or saving throw, you can roll a d4 and add it to their roll (no action required). You can use this before or after the roll, but before the GM determines the roll’s outcome. Once you’ve used this feature, you must complete a short or long rest before you can use it again.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"detailOriented": {
|
||||
name: "Detail Oriented",
|
||||
name: "SW5E.FlagsDetailOriented",
|
||||
hint: "You have advantage on Intelligence (Investigation) checks within 5 feet.",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"enthrallingPheromones": {
|
||||
name: "SW5E.FlagsEnthrallingPheromones",
|
||||
hint: "You can use your pheromones to influence individuals of both sexes. Whenever you roll a 1 on a Charisma (Persuasion) check, you can reroll the die and must use the new roll. Additionally, once per short or long rest, you can treat a d20 roll of 9 or lower on a Charisma check as a 10. This feature has no effect on droids or constructs.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"extraArms": {
|
||||
name: "SW5E.FlagsExtraArms",
|
||||
hint: "You possess more than two arms, which you can use independently of one another. You can only gain the benefit of items held by two of your arms at any given time, and once per round you can switch which arms you are benefiting from (no action required).",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"forceContention": {
|
||||
name: "SW5E.FlagsForceContention",
|
||||
hint: "Due to their unique physiology, members of your species exhibit a hardiness that allows them to overcome use of the Force. You have advantage on Strength and Constitution saving throws against force powers.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"forceInsensitive": {
|
||||
name: "SW5E.FlagsForceInsensitive",
|
||||
hint: "While droids can be manipulated by many force powers, they cannot sense the Force. You can not use force powers or take levels in forcecasting classes.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"foreignBiology": {
|
||||
name: "SW5E.FlagsForeignBiology",
|
||||
hint: "You wear a breathing apparatus because many atmospheres in the galaxy differ from that of your species' homeworld. If your apparatus is removed while you are in such an environment, you lose consciousness.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"furyOfTheSmall": {
|
||||
name: "SW5E.FlagsFuryOfTheSmall",
|
||||
hint: "When you damage a creature with an attack or a power and the creature's size is larger than yours, you can cause the attack or power to deal extra damage to the creature. The extra damage equals your level. Once you use this trait, you can't use it again until you finish a short or long rest.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"grovelCowerAndBeg": {
|
||||
name: "SW5E.FlagsGrovelCowerAndBeg",
|
||||
hint: "As an action on your turn, you can cower pathetically to distract nearby foes. Until the end of your next turn, your allies gain advantage on attack rolls against enemies within 10 feet of you that can see you. Once you use this trait, you can’t use it again until you finish a short or long rest.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"inscrutable": {
|
||||
name: "SW5E.FlagsInscrutable",
|
||||
hint: "Your calm demeaner and control make you hard to read. Wisdom (Insight) checks made against you have disadvantage, and you have advantage on any saving throw against an effect that would read your thoughts.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"keenSenses": {
|
||||
name: "Keen Hearing and Smell",
|
||||
hint: "You have advantage on Wisdom (Perception) checks that involve hearing or smell.",
|
||||
section: "Racial Traits",
|
||||
name: "SW5E.FlagsKeenSenses",
|
||||
hint: "You have advantage on Wisdom (Perception) checks that involve using particular senses (see your species' traits for details).",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"longlimbed": {
|
||||
name: "SW5E.FlagsLongLimbed",
|
||||
hint: "When you make a melee attack on your turn, your reach for it is 5 feet greater than normal.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"maintenanceMode": {
|
||||
name: "SW5E.FlagsMaintenanceMode",
|
||||
hint: "Rather than sleep, you enter an inactive state to perform routine maintenance for 4 hours each day. You have disadvantage on Wisdom (Perception) checks while performing maintenance.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"maskOfTheWild": {
|
||||
name: "SW5E.FlagsMaskOfTheWild",
|
||||
hint: "You can attempt to hide even when you are only lightly obscured by foliage, heavy rain, falling snow, mist, and other natural phenomena.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"multipleHearts": {
|
||||
name: "SW5E.FlagsMultipleHearts",
|
||||
hint: "When you are reduced to 0 hit points but not killed outright, you can drop to 1 hit point instead. You can't use this feature again until you finish a long rest.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"naturallyStealthy": {
|
||||
name: "Naturally Stealthy",
|
||||
name: "SW5E.FlagsNaturallyStealthy",
|
||||
hint: "You can attempt to hide even when you are obscured only by a creature that is your size or larger than you.",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"nimbleAgility": {
|
||||
name: "SW5E.FlagsNimbleAgility",
|
||||
hint: "Your reflexes and agility allow you to move with a burst of speed. When you move on your turn in combat, you can double your speed until the end of the turn. Once you use this trait, you can't use it again until you move 0 feet on one of your turns.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"nimbleEscape": {
|
||||
name: "Nimble Escape",
|
||||
name: "SW5E.FlagsNimbleEscape",
|
||||
hint: "You can take the Disengage or Hide action as a bonus action.",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"nimbleness": {
|
||||
name: "SW5E.FlagsNimbleness",
|
||||
hint: "You can move through the space of any creature that is of a size larger than yours.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"pintsized": {
|
||||
name: "SW5E.FlagsPintsized",
|
||||
hint: "Your tiny stature makes it hard for you to wield bigger weapons. You can’t use medium or heavy shields. Additionally, you can’t wield weapons with the two-handed or versatile property, and you can only wield one-handed weapons in two hands unless they have the light property.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"powerfulBuild": {
|
||||
name: "SW5E.FlagsPowerfulBuild",
|
||||
hint: "SW5E.FlagsPowerfulBuildHint",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"precognition": {
|
||||
name: "SW5E.FlagsPrecognition",
|
||||
hint: "You can see brief visions of the future that allow you to turn failures into successes. When you roll a 1 on an attack roll, ability check, or saving throw, you can reroll the die and must use the new roll.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"programmer": {
|
||||
name: "Programmer",
|
||||
name: "SW5E.FlagsProgrammer",
|
||||
hint: "Whenever you make an Intelligence (Technology) check related to computers, you are considered to have expertise in the Technology skill.",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"puny": {
|
||||
name: "SW5E.FlagsPuny",
|
||||
hint: "Members of your species are too small to pack much of a punch. You have disadvantage on Strength saving throws, and when determining your bonus to attack and damage rolls for weapon attacks using Strength, you can’t add more than +3.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"rapidReconstruction": {
|
||||
name: "SW5E.FlagsRapidReconstruction",
|
||||
hint: "You are built with internal repair mechanisms. As a bonus action, you can choose to spend one of your Hit Dice to recover hit points.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"rapidlyRegenerative": {
|
||||
name: "SW5E.FlagsRapidlyRegenerative",
|
||||
hint: "You heal quickly, both at will and in response to danger. As a bonus action, you can choose to spend one of your Hit Dice to recover hit points. Additionally, when you take damage, you can use your reaction and expend a Hit Die to regain hit points as long as the damage would not reduce your hit points to 0.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"regenerative": {
|
||||
name: "SW5E.FlagsRegenerative",
|
||||
hint: "When you take damage, you can use your reaction and expend a Hit Die to regain hit points as long as the damage would not reduce your hit points to 0.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"savageAttacks": {
|
||||
name: "SW5E.FlagsSavageAttacks",
|
||||
hint: "SW5E.FlagsSavageAttacksHint",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"shapechanger": {
|
||||
name: "SW5E.FlagsShapechanger",
|
||||
hint: "As an action, you can change your appearance and your voice. You determine the specifics of the changes, including your coloration, hair length, and sex. You can also adjust your height and weight, but not so much that your size changes. You can make yourself appear as a member of another species, though none of your game statistics change. You can't duplicate the appearance of a creature you've never seen, and you must adopt a form that has the same basic arrangement of limbs that you have. Your clothing and equipment aren't changed by this trait. You stay in the new form until you use an action to revert to your true form or until you die.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"strongLegged": {
|
||||
name: "SW5E.FlagsStrongLegged",
|
||||
hint: "When you make a long jump, you can cover a number of feet up to twice your Strength score. When you make a high jump, you can leap a number of feet up into the air equal to 3 + twice your Strength modifier.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"sunlightSensitivity": {
|
||||
name: "SW5E.FlagsSunlightSensitivity",
|
||||
hint: "You have disadvantage on attack rolls and on Wisdom (Perception) checks that rely on sight when you, the target of your attack, or whatever you are trying to perceive is in direct sunlight.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"surpriseAttack": {
|
||||
name: "SW5E.FlagsSurpriseAttack",
|
||||
hint: "If you surprise a creature and hit it with an attack on your first turn in combat, the attack deals an extra 2d6 damage to it. You can use this trait only once per combat.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"techImpaired": {
|
||||
name: "SW5E.FlagsTechImpaired",
|
||||
hint: "While members of your species can figure out basic technology, they experience difficulty using more complex equipment like wristpads. You cannot use tech powers or take levels in techcasting classes.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"techResistance": {
|
||||
name: "Tech Resistance",
|
||||
name: "SW5E.FlagsTechResistance",
|
||||
hint: "You have advantage on Dexterity and Intelligence saving throws against tech powers.",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"tinker": {
|
||||
name: "SW5E.FlagsTinker",
|
||||
hint: "You have proficiency with tinker’s implements. You can use these and spend 1 hour and 100 cr worth of materials to construct a Tiny Device (AC 5, 1 hp). You can take the Use an Object action to have your device cause one of a variety of minor effects (see your species' traits list). You can maintain a number of these devices up to your proficiency bonus at once, and a device stops functioning after 24 hours away from you. You can dismantle the device to reclaim the materials used to create it.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"toughness": {
|
||||
name: "SW5E.FlagsToughness",
|
||||
hint: "Your hit point maximum increases by 1, and it increases by 1 every time you gain a level.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"trance": {
|
||||
name: "SW5E.FlagsTrance",
|
||||
hint: "Either through meditation or a reduced sleep schedule, you are able to receive the rest you require on a daily basis (see your species' traits for details). After resting in this way, you gain the same benefit that a human does from 8 hours of sleep.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"unarmedCombatant": {
|
||||
name: "Unarmed Combatant",
|
||||
name: "SW5E.FlagsUnarmedCombatant",
|
||||
hint: "Your unarmed strikes deal 1d4 kinetic damage. You can use your choice of your Strength or Dexterity modifier for the attack and damage rolls. You must use the same modifier for both rolls.",
|
||||
section: "Racial Traits",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"undersized": {
|
||||
name: "Undersized",
|
||||
hint: "You can’t use heavy shields, martial weapons with the two-handed property unless it also has the light property, and if a martial weapon has the versatile property, you can only wield it in two hands.",
|
||||
section: "Racial Traits",
|
||||
name: "SW5E.FlagsUndersized",
|
||||
hint: "Your small stature makes it hard for you to wield bigger weapons. You can’t use heavy shields, martial weapons with the two-handed property unless it also has the light property, and if a martial weapon has the versatile property, you can only wield it in two hands.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"unsettlingVisage": {
|
||||
name: "SW5E.FlagsUnsettlingVisage",
|
||||
hint: "When a creature you can see makes an attack roll against you, you can use your reaction to impose disadvantage on the roll. You must use this feature before knowing whether the attack hits or misses. Using this trait reveals your shapeshifting nature to any creature within 30 feet that can see you. Once you use this trait, you can't use it again until you finish a short or long rest.",
|
||||
section: "Species Traits",
|
||||
type: Boolean
|
||||
},
|
||||
"initiativeAdv": {
|
||||
name: "SW5E.FlagsInitiativeAdv",
|
||||
hint: "SW5E.FlagsInitiativeAdvHint",
|
||||
|
@ -901,15 +1148,27 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"weaponCriticalThreshold": {
|
||||
name: "SW5E.FlagsCritThreshold",
|
||||
hint: "SW5E.FlagsCritThresholdHint",
|
||||
name: "SW5E.FlagsWeaponCritThreshold",
|
||||
hint: "SW5E.FlagsWeaponCritThresholdHint",
|
||||
section: "Feats",
|
||||
type: Number,
|
||||
placeholder: 20
|
||||
},
|
||||
"powerCriticalThreshold": {
|
||||
name: "SW5E.FlagsPowerCritThreshold",
|
||||
hint: "SW5E.FlagsPowerCritThresholdHint",
|
||||
section: "Feats",
|
||||
type: Number,
|
||||
placeholder: 20
|
||||
},
|
||||
"meleeCriticalDamageDice": {
|
||||
name: "SW5E.FlagsMeleeCriticalDice",
|
||||
hint: "SW5E.FlagsMeleeCriticalDiceHint",
|
||||
section: "Feats",
|
||||
type: Number,
|
||||
placeholder: 0
|
||||
}
|
||||
};
|
||||
|
||||
// Configure allowed status flags
|
||||
SW5E.allowedActorFlags = [
|
||||
"isPolymorphed", "originalActor"
|
||||
].concat(Object.keys(SW5E.characterFlags));
|
||||
SW5E.allowedActorFlags = ["isPolymorphed", "originalActor"].concat(Object.keys(SW5E.characterFlags));
|
||||
|
|
273
module/dice.js
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
* 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
|
||||
*
|
||||
/**
|
||||
* 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
|
||||
|
@ -27,72 +27,72 @@
|
|||
* @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
|
||||
*/
|
||||
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"]);
|
||||
|
||||
// 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;
|
||||
*/
|
||||
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"]);
|
||||
|
||||
// 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) {
|
||||
nd = elvenAccuracy ? 3 : 2;
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true;
|
||||
mods += "kh";
|
||||
}
|
||||
|
||||
// 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) {
|
||||
nd = elvenAccuracy ? 3 : 2;
|
||||
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) {
|
||||
nd = 2;
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true;
|
||||
mods += "kl";
|
||||
}
|
||||
// Handle disadvantage
|
||||
else if (adv === -1) {
|
||||
nd = 2;
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true;
|
||||
mods += "kl";
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
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) {
|
||||
data.ability = ability.value;
|
||||
const abl = data.abilities[data.ability];
|
||||
if (abl) {
|
||||
data.mod = abl.mod;
|
||||
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
|
||||
}
|
||||
// Optionally include a situational bonus
|
||||
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) {
|
||||
data.ability = ability.value;
|
||||
const abl = data.abilities[data.ability];
|
||||
if (abl) {
|
||||
data.mod = abl.mod;
|
||||
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the roll
|
||||
let roll = new Roll(parts.join(" + "), data);
|
||||
|
@ -139,73 +139,76 @@
|
|||
*/
|
||||
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) {
|
||||
|
||||
// Render modal dialog
|
||||
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
||||
let dialogData = {
|
||||
formula: parts.join(" + "),
|
||||
data: data,
|
||||
rollMode: rollMode,
|
||||
rollModes: CONFIG.Dice.rollModes,
|
||||
config: CONFIG.SW5E
|
||||
};
|
||||
const html = await renderTemplate(template, dialogData);
|
||||
// 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,
|
||||
config: CONFIG.SW5E
|
||||
};
|
||||
const html = await renderTemplate(template, dialogData);
|
||||
|
||||
// Create the Dialog window
|
||||
return new Promise(resolve => {
|
||||
new Dialog({
|
||||
title: title,
|
||||
content: html,
|
||||
buttons: {
|
||||
advantage: {
|
||||
label: game.i18n.localize("SW5E.Advantage"),
|
||||
callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
|
||||
},
|
||||
normal: {
|
||||
label: game.i18n.localize("SW5E.Normal"),
|
||||
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
|
||||
},
|
||||
disadvantage: {
|
||||
label: game.i18n.localize("SW5E.Disadvantage"),
|
||||
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
|
||||
}
|
||||
// Create the Dialog window
|
||||
return new Promise(resolve => {
|
||||
new Dialog({
|
||||
title: title,
|
||||
content: html,
|
||||
buttons: {
|
||||
advantage: {
|
||||
label: game.i18n.localize("SW5E.Advantage"),
|
||||
callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
|
||||
},
|
||||
default: "normal",
|
||||
close: () => resolve(null)
|
||||
}, dialogOptions).render(true);
|
||||
});
|
||||
}
|
||||
normal: {
|
||||
label: game.i18n.localize("SW5E.Normal"),
|
||||
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
|
||||
},
|
||||
disadvantage: {
|
||||
label: game.i18n.localize("SW5E.Disadvantage"),
|
||||
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
|
||||
}
|
||||
},
|
||||
default: "normal",
|
||||
close: () => resolve(null)
|
||||
}, dialogOptions).render(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 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, Critical, or no bonus respectively
|
||||
*
|
||||
* @param {Array} parts The dice roll component parts, excluding the initial d20
|
||||
* @param {Actor} actor The Actor making the damage roll
|
||||
* @param {Object} data Actor or item data against which to parse the roll
|
||||
* @param {Event|object}[event The triggering event which initiated the roll
|
||||
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {String} template The HTML template used to render the roll dialog
|
||||
* @param {String} title The dice roll UI window title
|
||||
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
|
||||
* @param {string} flavor Flavor text to use in the posted chat message
|
||||
* @param {boolean} allowCritical Allow the opportunity for a critical hit to be rolled
|
||||
* @param {Boolean} critical Flag this roll as a critical hit for the purposes of fast-forward rolls
|
||||
* @param {number} criticalBonusDice A number of bonus damage dice that are added for critical hits
|
||||
* @param {number} criticalMultiplier A critical hit multiplier which is applied to critical hits
|
||||
* @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
|
||||
*/
|
||||
export async function damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor,
|
||||
allowCritical=true, critical=false, criticalBonusDice=0, criticalMultiplier=2, fastForward=null,
|
||||
dialogOptions={}, chatMessage=true, messageData={}}={}) {
|
||||
|
||||
/**
|
||||
* 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, Critical, or no bonus respectively
|
||||
*
|
||||
* @param {Array} parts The dice roll component parts, excluding the initial d20
|
||||
* @param {Actor} actor The Actor making the damage roll
|
||||
* @param {Object} data Actor or item data against which to parse the roll
|
||||
* @param {Event|object}[event The triggering event which initiated the roll
|
||||
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {String} template The HTML template used to render the roll dialog
|
||||
* @param {String} title The dice roll UI window title
|
||||
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
|
||||
* @param {string} flavor Flavor text to use in the posted chat message
|
||||
* @param {boolean} allowCritical Allow the opportunity for a critical hit to be rolled
|
||||
* @param {Boolean} critical Flag this roll as a critical hit for the purposes of fast-forward rolls
|
||||
* @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
|
||||
*/
|
||||
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();
|
||||
|
@ -213,8 +216,8 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
|
|||
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) {
|
||||
// Define inner roll function
|
||||
const _roll = function(parts, crit, form) {
|
||||
|
||||
// Optionally include a situational bonus
|
||||
if ( form ) {
|
||||
|
@ -224,17 +227,17 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
|
|||
if (!data["bonus"]) parts.pop();
|
||||
|
||||
// Create the damage roll
|
||||
let roll = new Roll(parts.join("+"), data);
|
||||
let roll = new Roll(parts.join("+"), data);
|
||||
|
||||
// Modify the damage formula for critical hits
|
||||
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;
|
||||
roll.alter(criticalMultiplier, 0); // Multiply all dice
|
||||
if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term
|
||||
roll.terms[0].alter(1, criticalBonusDice);
|
||||
roll._formula = roll.formula;
|
||||
}
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
|
||||
}
|
||||
|
||||
// Execute the roll
|
||||
|
|
63
module/effects.js
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Manage Active Effect instances through the Actor Sheet via effect control buttons.
|
||||
* @param {MouseEvent} event The left-click event on the effect control
|
||||
* @param {Actor|Item} owner The owning entity which manages this effect
|
||||
*/
|
||||
export function onManageActiveEffect(event, owner) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
const li = a.closest("li");
|
||||
const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null;
|
||||
switch ( a.dataset.action ) {
|
||||
case "create":
|
||||
return ActiveEffect.create({
|
||||
label: "New Effect",
|
||||
icon: "icons/svg/aura.svg",
|
||||
origin: owner.uuid,
|
||||
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
|
||||
disabled: li.dataset.effectType === "inactive"
|
||||
}, owner).create();
|
||||
case "edit":
|
||||
return effect.sheet.render(true);
|
||||
case "delete":
|
||||
return effect.delete();
|
||||
case "toggle":
|
||||
return effect.update({disabled: !effect.data.disabled});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data structure for Active Effects which are currently applied to an Actor or Item.
|
||||
* @param {ActiveEffect[]} effects The array of Active Effect instances to prepare sheet data for
|
||||
* @return {object} Data for rendering
|
||||
*/
|
||||
export function prepareActiveEffectCategories(effects) {
|
||||
|
||||
// Define effect header categories
|
||||
const categories = {
|
||||
temporary: {
|
||||
type: "temporary",
|
||||
label: "Temporary Effects",
|
||||
effects: []
|
||||
},
|
||||
passive: {
|
||||
type: "passive",
|
||||
label: "Passive Effects",
|
||||
effects: []
|
||||
},
|
||||
inactive: {
|
||||
type: "inactive",
|
||||
label: "Inactive Effects",
|
||||
effects: []
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over active effects, classifying them into categories
|
||||
for ( let e of effects ) {
|
||||
e._getSourceName(); // Trigger a lookup for the source name
|
||||
if ( e.data.disabled ) categories.inactive.effects.push(e);
|
||||
else if ( e.isTemporary ) categories.temporary.effects.push(e);
|
||||
else categories.passive.effects.push(e);
|
||||
}
|
||||
return categories;
|
||||
}
|
|
@ -161,6 +161,7 @@ export default class Item5e extends Item {
|
|||
|
||||
// Power Level, School, and Components
|
||||
if ( itemData.type === "power" ) {
|
||||
data.preparation.mode = data.preparation.mode || "prepared";
|
||||
labels.level = C.powerLevels[data.level];
|
||||
labels.school = C.powerSchools[data.school];
|
||||
labels.components = Object.entries(data.components).reduce((arr, c) => {
|
||||
|
@ -180,26 +181,34 @@ export default class Item5e extends Item {
|
|||
else labels.featType = game.i18n.localize("SW5E.Passive");
|
||||
}
|
||||
|
||||
// Species Items
|
||||
else if ( itemData.type === "species" ) {
|
||||
// labels.species = C.species[data.species];
|
||||
}
|
||||
// Archetype Items
|
||||
else if ( itemData.type === "archetype" ) {
|
||||
// Species Items
|
||||
else if ( itemData.type === "species" ) {
|
||||
// labels.species = C.species[data.species];
|
||||
}
|
||||
// Archetype Items
|
||||
else if ( itemData.type === "archetype" ) {
|
||||
// labels.archetype = C.archetype[data.archetype];
|
||||
}
|
||||
// Background Items
|
||||
else if ( itemData.type === "background" ) {
|
||||
|
||||
}
|
||||
// Class Feature Items
|
||||
// Background Items
|
||||
else if ( itemData.type === "background" ) {
|
||||
// labels.background = C.background[data.background];
|
||||
}
|
||||
// Class Feature Items
|
||||
else if ( itemData.type === "classfeature" ) {
|
||||
|
||||
}
|
||||
// Lightsaber Form Items
|
||||
else if ( itemData.type === "lightsaberform" ) {
|
||||
|
||||
}
|
||||
// labels.classFeature = C.classFeature[data.classFeature];
|
||||
}
|
||||
// Fighting Style Items
|
||||
else if ( itemData.type === "fightingstyle" ) {
|
||||
// labels.fightingstyle = C.fightingstyle[data.fightingstyle];
|
||||
}
|
||||
// Fighting Mastery Items
|
||||
else if ( itemData.type === "fightingmastery" ) {
|
||||
// labels.fightingmastery = C.fightingmastery[data.fightingmastery];
|
||||
}
|
||||
// Lightsaber Form Items
|
||||
else if ( itemData.type === "lightsaberform" ) {
|
||||
// labels.lightsaberform = C.lightsaberform[data.lightsaberform];
|
||||
}
|
||||
|
||||
// Equipment Items
|
||||
else if ( itemData.type === "equipment" ) {
|
||||
|
@ -314,7 +323,7 @@ export default class Item5e extends Item {
|
|||
user: game.user._id,
|
||||
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
||||
content: html,
|
||||
flavor: this.name,
|
||||
flavor: this.data.data.chatFlavor || this.name,
|
||||
speaker: {
|
||||
actor: this.actor._id,
|
||||
token: this.actor.token,
|
||||
|
@ -340,7 +349,7 @@ export default class Item5e extends Item {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
/**
|
||||
* 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)
|
||||
|
@ -359,7 +368,6 @@ export default class Item5e extends Item {
|
|||
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;
|
||||
|
@ -372,6 +380,7 @@ export default class Item5e extends Item {
|
|||
|
||||
// Identify the consumed resource and it's quantity
|
||||
let consumed = null;
|
||||
let amount = parseInt(consume.amount || 1);
|
||||
let quantity = 0;
|
||||
switch ( consume.type ) {
|
||||
case "attribute":
|
||||
|
@ -385,7 +394,13 @@ export default class Item5e extends Item {
|
|||
break;
|
||||
case "charges":
|
||||
consumed = actor.items.get(consume.target);
|
||||
quantity = consumed ? consumed.data.data.uses.value : 0;
|
||||
if ( !consumed ) break;
|
||||
const uses = consumed.data.data.uses;
|
||||
if ( uses.per && uses.max ) quantity = uses.value;
|
||||
else if ( consumed.data.data.recharge?.value ) {
|
||||
quantity = consumed.data.data.recharge.charged ? 1 : 0;
|
||||
amount = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -410,7 +425,11 @@ export default class Item5e extends Item {
|
|||
await consumed.update({"data.quantity": remaining});
|
||||
break;
|
||||
case "charges":
|
||||
await consumed.update({"data.uses.value": remaining});
|
||||
const uses = consumed.data.data.uses || {};
|
||||
const recharge = consumed.data.data.recharge || {};
|
||||
if ( uses.per && uses.max ) await consumed.update({"data.uses.value": remaining});
|
||||
else if ( recharge.value ) await consumed.update({"data.recharge.charged": false});
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -428,7 +447,7 @@ export default class Item5e extends Item {
|
|||
// Configure whether to consume a limited use or to place a template
|
||||
const charge = this.data.data.recharge;
|
||||
const uses = this.data.data.uses;
|
||||
let usesCharges = !!uses.per && (uses.max > 0);
|
||||
let usesCharges = !!uses.per && !!uses.max;
|
||||
let placeTemplate = false;
|
||||
let consume = charge.value || usesCharges;
|
||||
|
||||
|
@ -629,40 +648,37 @@ export default class Item5e extends Item {
|
|||
}
|
||||
|
||||
// Attack Bonus
|
||||
if ( itemData.attackBonus ) parts.push(itemData.attackBonus);
|
||||
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
|
||||
if ( itemData.attackBonus || actorBonus.attack ) {
|
||||
parts.push("@atk");
|
||||
rollData["atk"] = [itemData.attackBonus, actorBonus.attack].filterJoin(" + ");
|
||||
}
|
||||
if ( actorBonus.attack ) parts.push(actorBonus.attack);
|
||||
|
||||
// Ammunition Bonus
|
||||
delete this._ammo;
|
||||
const consume = itemData.consume;
|
||||
if ( consume?.type === "ammo" ) {
|
||||
const ammo = this.actor.items.get(consume.target);
|
||||
if(ammo?.data){
|
||||
const q = ammo.data.data.quantity;
|
||||
const consumeAmount = consume.amount ?? 0;
|
||||
if ( q && (q - consumeAmount >= 0) ) {
|
||||
let ammoBonus = ammo.data.data.attackBonus;
|
||||
if ( ammoBonus ) {
|
||||
parts.push("@ammo");
|
||||
rollData["ammo"] = ammoBonus;
|
||||
title += ` [${ammo.name}]`;
|
||||
this._ammo = ammo;
|
||||
}
|
||||
// Ammunition Bonus
|
||||
delete this._ammo;
|
||||
const consume = itemData.consume;
|
||||
if ( consume?.type === "ammo" ) {
|
||||
const ammo = this.actor.items.get(consume.target);
|
||||
if(ammo?.data){
|
||||
const q = ammo.data.data.quantity;
|
||||
const consumeAmount = consume.amount ?? 0;
|
||||
if ( q && (q - consumeAmount >= 0) ) {
|
||||
this._ammo = ammo;
|
||||
let ammoBonus = ammo.data.data.attackBonus;
|
||||
if ( ammoBonus ) {
|
||||
parts.push("@ammo");
|
||||
rollData["ammo"] = ammoBonus;
|
||||
title += ` [${ammo.name}]`;
|
||||
}
|
||||
//}else{
|
||||
// ui.notifications.error(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Compose roll options
|
||||
const rollConfig = mergeObject({
|
||||
parts: parts,
|
||||
actor: this.actor,
|
||||
data: rollData,
|
||||
title: title,
|
||||
flavor: title,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
dialogOptions: {
|
||||
width: 400,
|
||||
|
@ -673,9 +689,11 @@ export default class Item5e extends Item {
|
|||
}, options);
|
||||
rollConfig.event = options.event;
|
||||
|
||||
// Expanded weapon critical threshold
|
||||
// Expanded critical hit thresholds
|
||||
if (( this.data.type === "weapon" ) && flags.weaponCriticalThreshold) {
|
||||
rollConfig.critical = parseInt(flags.weaponCriticalThreshold);
|
||||
} else if (( this.data.type === "power" ) && flags.powerCriticalThreshold) {
|
||||
rollConfig.critical = parseInt(flags.powerCriticalThreshold);
|
||||
}
|
||||
|
||||
// Elven Accuracy
|
||||
|
@ -702,28 +720,41 @@ export default class Item5e extends Item {
|
|||
|
||||
/**
|
||||
* Place a damage roll using an item (weapon, feat, power, or equipment)
|
||||
* Rely upon the damageRoll logic for the core implementation
|
||||
*
|
||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||
* Rely upon the damageRoll logic for the core implementation.
|
||||
* @param {MouseEvent} [event] An event which triggered this roll, if any
|
||||
* @param {number} [powerLevel] If the item is a power, override the level for damage scaling
|
||||
* @param {boolean} [versatile] If the item is a weapon, roll damage using the versatile formula
|
||||
* @param {object} [options] Additional options passed to the damageRoll function
|
||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
rollDamage({event, powerLevel=null, versatile=false}={}) {
|
||||
rollDamage({event, powerLevel=null, versatile=false, options={}}={}) {
|
||||
if ( !this.hasDamage ) throw new Error("You may not make a Damage Roll with this Item.");
|
||||
const itemData = this.data.data;
|
||||
const actorData = this.actor.data.data;
|
||||
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 parts = itemData.damage.parts.map(d => d[0]);
|
||||
const rollData = this.getRollData();
|
||||
if ( powerLevel ) rollData.item.level = powerLevel;
|
||||
|
||||
// Get message labels
|
||||
// Configure the damage roll
|
||||
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]);
|
||||
const rollConfig = {
|
||||
event: event,
|
||||
parts: parts,
|
||||
actor: this.actor,
|
||||
data: rollData,
|
||||
title: title,
|
||||
flavor: this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
dialogOptions: {
|
||||
width: 400,
|
||||
top: event ? event.clientY - 80 : null,
|
||||
left: window.innerWidth - 710
|
||||
},
|
||||
messageData: messageData
|
||||
};
|
||||
|
||||
// Adjust damage from versatile usage
|
||||
if ( versatile && itemData.damage.versatile ) {
|
||||
|
@ -743,37 +774,27 @@ export default class Item5e extends Item {
|
|||
}
|
||||
}
|
||||
|
||||
// Define Roll Data
|
||||
// Add damage bonus formula
|
||||
const actorBonus = getProperty(actorData, `bonuses.${itemData.actionType}`) || {};
|
||||
if ( actorBonus.damage && parseInt(actorBonus.damage) !== 0 ) {
|
||||
parts.push("@dmg");
|
||||
rollData["dmg"] = actorBonus.damage;
|
||||
if ( actorBonus.damage && (parseInt(actorBonus.damage) !== 0) ) {
|
||||
parts.push(actorBonus.damage);
|
||||
}
|
||||
|
||||
// Ammunition Damage
|
||||
// Add 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}]`;
|
||||
rollConfig.flavor += ` [${this._ammo.name}]`;
|
||||
delete this._ammo;
|
||||
}
|
||||
|
||||
// Scale melee critical hit damage
|
||||
if ( itemData.actionType === "mwak" ) {
|
||||
rollConfig.criticalBonusDice = this.actor.getFlag("sw5e", "meleeCriticalDamageDice") ?? 0;
|
||||
}
|
||||
|
||||
// Call the roll helper utility
|
||||
return damageRoll({
|
||||
event: event,
|
||||
parts: parts,
|
||||
actor: this.actor,
|
||||
data: rollData,
|
||||
title: title,
|
||||
flavor: flavor,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
dialogOptions: {
|
||||
width: 400,
|
||||
top: event ? event.clientY - 80 : null,
|
||||
left: window.innerWidth - 710
|
||||
},
|
||||
messageData
|
||||
});
|
||||
return damageRoll(mergeObject(rollConfig, options));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -785,22 +806,7 @@ export default class Item5e extends Item {
|
|||
_scaleAtWillDamage(parts, scale, level, rollData) {
|
||||
const add = Math.floor((level + 1) / 6);
|
||||
if ( add === 0 ) return;
|
||||
|
||||
// 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}`);
|
||||
}
|
||||
}
|
||||
this._scaleDamage(parts, scale || parts.join(" + "), add, rollData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -818,20 +824,7 @@ export default class Item5e extends Item {
|
|||
_scalePowerDamage(parts, baseLevel, powerLevel, formula, rollData) {
|
||||
const upcastLevels = Math.max(powerLevel - baseLevel, 0);
|
||||
if ( upcastLevels === 0 ) return parts;
|
||||
|
||||
// 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;
|
||||
this._scaleDamage(parts, formula, upcastLevels, rollData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -874,7 +867,7 @@ export default class Item5e extends Item {
|
|||
/**
|
||||
* Place an attack roll using an item (weapon, feat, power, or equipment)
|
||||
* Rely upon the d20Roll logic for the core implementation
|
||||
*
|
||||
*
|
||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
async rollFormula(options={}) {
|
||||
|
@ -891,7 +884,7 @@ export default class Item5e extends Item {
|
|||
const roll = new Roll(rollData.item.formula, rollData).roll();
|
||||
roll.toMessage({
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
flavor: this.data.data.chatFlavor || title,
|
||||
flavor: title,
|
||||
rollMode: game.settings.get("core", "rollMode"),
|
||||
messageData: {"flags.sw5e.roll": {type: "other", itemId: this.id }}
|
||||
});
|
||||
|
@ -902,7 +895,7 @@ export default class Item5e extends Item {
|
|||
|
||||
/**
|
||||
* Use a consumable item, deducting from the quantity or charges of the item.
|
||||
* @param {boolean} configureDialog Whether to show a configuration dialog
|
||||
* @param {boolean} configureDialog Whether to show a configuration dialog
|
||||
* @return {boolean} Whether further execution should be prevented
|
||||
* @private
|
||||
*/
|
||||
|
@ -964,7 +957,7 @@ export default class Item5e extends Item {
|
|||
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -1013,7 +1006,7 @@ export default class Item5e extends Item {
|
|||
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
|
||||
title: title,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
flavor: `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`,
|
||||
flavor: title,
|
||||
dialogOptions: {
|
||||
width: 400,
|
||||
top: options.event ? options.event.clientY - 80 : null,
|
||||
|
@ -1047,8 +1040,8 @@ export default class Item5e extends Item {
|
|||
}
|
||||
|
||||
// Include a proficiency score
|
||||
const prof = "proficient" in rollData.item ? (rollData.item.proficient || 0) : 1;
|
||||
rollData["prof"] = Math.floor(prof * rollData.attributes.prof);
|
||||
const prof = ("proficient" in rollData.item) ? (rollData.item.proficient || 0) : 1;
|
||||
rollData["prof"] = Math.floor(prof * (rollData.attributes.prof || 0));
|
||||
return rollData;
|
||||
}
|
||||
|
||||
|
@ -1181,7 +1174,7 @@ export default class Item5e extends Item {
|
|||
if ( !targets.length ) ui.notifications.warn(game.i18n.localize("SW5E.ActionWarningNoToken"));
|
||||
return targets;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Factory Methods */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import TraitSelector from "../apps/trait-selector.js";
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../effects.js";
|
||||
|
||||
/**
|
||||
* Override and extend the core ItemSheet implementation to handle specific item types
|
||||
|
@ -7,8 +8,11 @@ import TraitSelector from "../apps/trait-selector.js";
|
|||
export default class ItemSheet5e extends ItemSheet {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
// Expand the default size of the class sheet
|
||||
if ( this.object.data.type === "class" ) {
|
||||
this.options.width = 600;
|
||||
this.options.width = this.position.width = 600;
|
||||
this.options.height = this.position.height = 680;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +22,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
width: 560,
|
||||
height: "auto",
|
||||
height: 400,
|
||||
classes: ["sw5e", "sheet", "item"],
|
||||
resizable: true,
|
||||
scrollY: [".tab.details"],
|
||||
|
@ -37,17 +41,17 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
async getData(options) {
|
||||
const data = super.getData(options);
|
||||
data.labels = this.item.labels;
|
||||
data.config = CONFIG.SW5E;
|
||||
|
||||
// Item Type, Status, and Details
|
||||
data.itemType = data.item.type.titleCase();
|
||||
data.itemType = game.i18n.localize(`ITEM.Type${data.item.type.titleCase()}`);
|
||||
data.itemStatus = this._getItemStatus(data.item);
|
||||
data.itemProperties = this._getItemProperties(data.item);
|
||||
data.isPhysical = data.item.data.hasOwnProperty("quantity");
|
||||
|
||||
|
||||
// Potential consumption targets
|
||||
data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item);
|
||||
|
||||
|
@ -55,17 +59,20 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
data.hasAttackRoll = this.item.hasAttack;
|
||||
data.isHealing = data.item.data.actionType === "heal";
|
||||
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
|
||||
data.isLine = ["line", "wall"].includes(data.item.data.target?.type);
|
||||
data.isLine = ["line", "wall"].includes(data.item.data.target?.type);
|
||||
|
||||
// Vehicles
|
||||
data.isCrewed = data.item.data.activation?.type === 'crew';
|
||||
data.isMountable = this._isItemMountable(data.item);
|
||||
|
||||
// Prepare Active Effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
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
|
||||
|
@ -109,6 +116,8 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
// Charges
|
||||
else if ( consume.type === "charges" ) {
|
||||
return actor.items.reduce((obj, i) => {
|
||||
|
||||
// Limited-use items
|
||||
const uses = i.data.data.uses || {};
|
||||
if ( uses.per && uses.max ) {
|
||||
const label = uses.per === "charges" ?
|
||||
|
@ -116,6 +125,10 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
` (${game.i18n.format("SW5E.AbilityUseConsumableLabel", {max: uses.max, per: uses.per})})`;
|
||||
obj[i.id] = i.name + label;
|
||||
}
|
||||
|
||||
// Recharging items
|
||||
const recharge = i.data.data.recharge || {};
|
||||
if ( recharge.value ) obj[i.id] = `${i.name} (${game.i18n.format("SW5E.Recharge")})`;
|
||||
return obj;
|
||||
}, {})
|
||||
}
|
||||
|
@ -177,23 +190,26 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
}
|
||||
|
||||
else if ( item.type === "species" ) {
|
||||
|
||||
//props.push(labels.species);
|
||||
}
|
||||
else if ( item.type === "archetype" ) {
|
||||
|
||||
//props.push(labels.archetype);
|
||||
}
|
||||
else if ( item.type === "background" ) {
|
||||
//props.push(labels.background);
|
||||
}
|
||||
else if ( item.type === "classfeature" ) {
|
||||
//props.push(labels.classfeature);
|
||||
}
|
||||
else if ( item.type === "fightingmastery" ) {
|
||||
//props.push(labels.fightingmastery);
|
||||
}
|
||||
else if ( item.type === "fightingstyle" ) {
|
||||
//props.push(labels.fightingstyle);
|
||||
}
|
||||
else if ( item.type === "lightsaberform" ) {
|
||||
//props.push(labels.lightsaberform);
|
||||
}
|
||||
|
||||
else if ( item.type === "background" ) {
|
||||
|
||||
}
|
||||
|
||||
else if ( item.type === "classfeature" ) {
|
||||
|
||||
}
|
||||
|
||||
else if ( item.type === "lightsaberform" ) {
|
||||
|
||||
}
|
||||
|
||||
// Action type
|
||||
if ( item.data.actionType ) {
|
||||
|
@ -232,8 +248,8 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/** @override */
|
||||
setPosition(position={}) {
|
||||
if ( !this._minimized ) {
|
||||
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
|
||||
if ( !(this._minimized || position.height) ) {
|
||||
position.height = (this._tabs[0].active === "details") ? "auto" : this.options.height;
|
||||
}
|
||||
return super.setPosition(position);
|
||||
}
|
||||
|
@ -243,17 +259,20 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
_getSubmitData(updateData={}) {
|
||||
|
||||
// TODO: This can be removed once 0.7.x is release channel
|
||||
if ( !formData.data ) formData = expandObject(formData);
|
||||
// Create the expanded update data object
|
||||
const fd = new FormDataExtended(this.form, {editors: this.editors});
|
||||
let data = fd.toObject();
|
||||
if ( updateData ) data = mergeObject(data, updateData);
|
||||
else data = expandObject(data);
|
||||
|
||||
// Handle Damage Array
|
||||
const damage = formData.data?.damage;
|
||||
// Handle Damage array
|
||||
const damage = data.data?.damage;
|
||||
if ( damage ) damage.parts = Object.values(damage?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
|
||||
|
||||
// Update the Item
|
||||
super._updateObject(event, formData);
|
||||
// Return the flattened submission data
|
||||
return flattenObject(data);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -261,8 +280,14 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.find(".damage-control").click(this._onDamageControl.bind(this));
|
||||
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this));
|
||||
if ( this.isEditable ) {
|
||||
html.find(".damage-control").click(this._onDamageControl.bind(this));
|
||||
html.find('.trait-selector.class-skills').click(this._onConfigureClassSkills.bind(this));
|
||||
html.find(".effect-control").click(ev => {
|
||||
if ( this.item.isOwned ) return ui.notifications.warn("Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update.")
|
||||
onManageActiveEffect(ev, this.item)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -320,4 +345,12 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
maximum: skills.number
|
||||
}).render(true)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onSubmit(...args) {
|
||||
if ( this._tabs[0].active === "details" ) this.position.height = "auto";
|
||||
await super._onSubmit(...args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export const migrateWorld = async function() {
|
|||
await a.update(updateData, {enforceTypes: false});
|
||||
}
|
||||
} catch(err) {
|
||||
err.message = `Failed sw5e system migration for Actor ${a.name}: ${err.message}`;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +28,7 @@ export const migrateWorld = async function() {
|
|||
await i.update(updateData, {enforceTypes: false});
|
||||
}
|
||||
} catch(err) {
|
||||
err.message = `Failed sw5e system migration for Item ${i.name}: ${err.message}`;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
@ -40,15 +42,15 @@ export const migrateWorld = async function() {
|
|||
await s.update(updateData, {enforceTypes: false});
|
||||
}
|
||||
} catch(err) {
|
||||
err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate World Compendium Packs
|
||||
const packs = game.packs.filter(p => {
|
||||
return (p.metadata.package === "world") && ["Actor", "Item", "Scene"].includes(p.metadata.entity)
|
||||
});
|
||||
for ( let p of packs ) {
|
||||
for ( let p of game.packs ) {
|
||||
if ( p.metadata.package !== "world" ) continue;
|
||||
if ( !["Actor", "Item", "Scene"].includes(p.metadata.entity) ) continue;
|
||||
await migrateCompendium(p);
|
||||
}
|
||||
|
||||
|
@ -68,27 +70,46 @@ export const migrateCompendium = async function(pack) {
|
|||
const entity = pack.metadata.entity;
|
||||
if ( !["Actor", "Item", "Scene"].includes(entity) ) return;
|
||||
|
||||
// Unlock the pack for editing
|
||||
const wasLocked = pack.locked;
|
||||
await pack.configure({locked: false});
|
||||
|
||||
// Begin by requesting server-side data model migration and get the migrated content
|
||||
await pack.migrate();
|
||||
const content = await pack.getContent();
|
||||
|
||||
// Iterate over compendium entries - applying fine-tuned migration functions
|
||||
for ( let ent of content ) {
|
||||
let updateData = {};
|
||||
try {
|
||||
let updateData = null;
|
||||
if (entity === "Item") updateData = migrateItemData(ent.data);
|
||||
else if (entity === "Actor") updateData = migrateActorData(ent.data);
|
||||
else if ( entity === "Scene" ) updateData = migrateSceneData(ent.data);
|
||||
if (!isObjectEmpty(updateData)) {
|
||||
expandObject(updateData);
|
||||
updateData["_id"] = ent._id;
|
||||
await pack.updateEntity(updateData);
|
||||
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
|
||||
switch (entity) {
|
||||
case "Actor":
|
||||
updateData = migrateActorData(ent.data);
|
||||
break;
|
||||
case "Item":
|
||||
updateData = migrateItemData(ent.data);
|
||||
break;
|
||||
case "Scene":
|
||||
updateData = migrateSceneData(ent.data);
|
||||
break;
|
||||
}
|
||||
} catch(err) {
|
||||
if ( isObjectEmpty(updateData) ) continue;
|
||||
|
||||
// Save the entry, if data was changed
|
||||
updateData["_id"] = ent._id;
|
||||
await pack.updateEntity(updateData);
|
||||
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
|
||||
}
|
||||
|
||||
// Handle migration failures
|
||||
catch(err) {
|
||||
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the original locked status for the pack
|
||||
pack.configure({locked: wasLocked});
|
||||
console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
|
||||
};
|
||||
|
||||
|
@ -107,9 +128,7 @@ export const migrateActorData = function(actor) {
|
|||
|
||||
// Actor Data Updates
|
||||
_migrateActorBonuses(actor, updateData);
|
||||
|
||||
// Remove deprecated fields
|
||||
_migrateRemoveDeprecated(actor, updateData);
|
||||
_migrateActorMovement(actor, updateData);
|
||||
|
||||
// Migrate Owned Items
|
||||
if ( !actor.items ) return updateData;
|
||||
|
@ -172,11 +191,6 @@ function cleanActorData(actorData) {
|
|||
*/
|
||||
export const migrateItemData = function(item) {
|
||||
const updateData = {};
|
||||
|
||||
// Remove deprecated fields
|
||||
_migrateRemoveDeprecated(item, updateData);
|
||||
|
||||
// Return the migrated update data
|
||||
return updateData;
|
||||
};
|
||||
|
||||
|
@ -225,31 +239,17 @@ function _migrateActorBonuses(actor, updateData) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* A general migration to remove all fields from the data model which are flagged with a _deprecated tag
|
||||
* Migrate the actor bonuses object
|
||||
* @private
|
||||
*/
|
||||
const _migrateRemoveDeprecated = function(ent, updateData) {
|
||||
const flat = flattenObject(ent.data);
|
||||
|
||||
// Identify objects to deprecate
|
||||
const toDeprecate = Object.entries(flat).filter(e => e[0].endsWith("_deprecated") && (e[1] === true)).map(e => {
|
||||
let parent = e[0].split(".");
|
||||
parent.pop();
|
||||
return parent.join(".");
|
||||
});
|
||||
|
||||
// Remove them
|
||||
for ( let k of toDeprecate ) {
|
||||
let parts = k.split(".");
|
||||
parts[parts.length-1] = "-=" + parts[parts.length-1];
|
||||
updateData[`data.${parts.join(".")}`] = null;
|
||||
}
|
||||
};
|
||||
function _migrateActorMovement(actor, updateData) {
|
||||
if ( actor.data.attributes?.movement?.walk !== undefined ) return;
|
||||
const s = (actor.data.attributes?.speed?.value || "").split(" ");
|
||||
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -280,3 +280,24 @@ export async function purgeFlags(pack) {
|
|||
}
|
||||
await pack.configure({locked: true});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Purge the data model of any inner objects which have been flagged as _deprecated.
|
||||
* @param {object} data The data to clean
|
||||
* @private
|
||||
*/
|
||||
export function removeDeprecatedObjects(data) {
|
||||
for ( let [k, v] of Object.entries(data) ) {
|
||||
if ( getType(v) === "Object" ) {
|
||||
if (v._deprecated === true) {
|
||||
console.log(`Deleting deprecated object key ${k}`);
|
||||
delete data[k];
|
||||
}
|
||||
else removeDeprecatedObjects(v);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
templateData.width = target.value;
|
||||
templateData.direction = 45;
|
||||
break;
|
||||
case "ray": // 5e rays are most commonly 1 square (5 ft) in width
|
||||
case "ray": // 5e rays are most commonly 1 square (5 ft) in width
|
||||
templateData.width = target.width ?? canvas.dimensions.distance;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -7,11 +7,11 @@ export const registerSystemSettings = function() {
|
|||
name: "System Migration Version",
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: Number,
|
||||
default: 0
|
||||
type: String,
|
||||
default: ""
|
||||
});
|
||||
|
||||
/**
|
||||
/**
|
||||
* Register resting variants
|
||||
*/
|
||||
game.settings.register("sw5e", "restVariant", {
|
||||
|
@ -82,18 +82,6 @@ export const registerSystemSettings = function() {
|
|||
type: Boolean,
|
||||
});
|
||||
|
||||
/**
|
||||
* Option to automatically create Power Measured Template on roll
|
||||
*/
|
||||
game.settings.register("sw5e", "alwaysPlacePowerTemplate", {
|
||||
name: "SETTINGS.5eAutoPowerTemplateN",
|
||||
hint: "SETTINGS.5eAutoPowerTemplateL",
|
||||
scope: "client",
|
||||
config: true,
|
||||
default: false,
|
||||
type: Boolean
|
||||
});
|
||||
|
||||
/**
|
||||
* Option to automatically collapse Item Card descriptions
|
||||
*/
|
||||
|
@ -142,4 +130,16 @@ export const registerSystemSettings = function() {
|
|||
transformTokens: true
|
||||
}
|
||||
});
|
||||
game.settings.register("sw5e", "colorTheme", {
|
||||
name: "SETTINGS.SWColorN",
|
||||
hint: "SETTINGS.SWColorL",
|
||||
scope: "world",
|
||||
config: true,
|
||||
default: "light",
|
||||
type: String,
|
||||
choices: {
|
||||
"light": "SETTINGS.SWColorLight",
|
||||
"dark": "SETTINGS.SWColorDark"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,24 +4,30 @@
|
|||
* @return {Promise}
|
||||
*/
|
||||
export const preloadHandlebarsTemplates = async function() {
|
||||
return loadTemplates([
|
||||
|
||||
// Define template paths to load
|
||||
const templatePaths = [
|
||||
// Shared Partials
|
||||
"systems/sw5e/templates/actors/parts/active-effects.html",
|
||||
|
||||
// Actor Sheet Partials
|
||||
"systems/sw5e/templates/actors/parts/actor-traits.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-inventory.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-features.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-powerbook.html",
|
||||
"systems/sw5e/templates/actors/parts/actor-effects.html",
|
||||
"systems/sw5e/templates/actors/oldActor/parts/actor-traits.html",
|
||||
"systems/sw5e/templates/actors/oldActor/parts/actor-inventory.html",
|
||||
"systems/sw5e/templates/actors/oldActor/parts/actor-features.html",
|
||||
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
|
||||
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-core.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-features.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-notes.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-powerbook.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
|
||||
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",
|
||||
|
||||
// Item Sheet Partials
|
||||
"systems/sw5e/templates/items/parts/item-action.html",
|
||||
"systems/sw5e/templates/items/parts/item-activation.html",
|
||||
"systems/sw5e/templates/items/parts/item-description.html",
|
||||
"systems/sw5e/templates/items/parts/item-mountable.html"
|
||||
];
|
||||
|
||||
// Load the template parts
|
||||
return loadTemplates(templatePaths);
|
||||
]);
|
||||
};
|
||||
|
|
61
package-lock.json
generated
|
@ -89,6 +89,7 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"micromatch": "^3.1.4",
|
||||
"normalize-path": "^2.1.1"
|
||||
|
@ -98,6 +99,7 @@
|
|||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"remove-trailing-separator": "^1.0.1"
|
||||
}
|
||||
|
@ -270,6 +272,7 @@
|
|||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
|
||||
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cache-base": "^1.0.1",
|
||||
"class-utils": "^0.3.5",
|
||||
|
@ -284,6 +287,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^1.0.0"
|
||||
}
|
||||
|
@ -292,6 +296,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
||||
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^6.0.0"
|
||||
}
|
||||
|
@ -300,6 +305,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
||||
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^6.0.0"
|
||||
}
|
||||
|
@ -308,6 +314,7 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
||||
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-accessor-descriptor": "^1.0.0",
|
||||
"is-data-descriptor": "^1.0.0",
|
||||
|
@ -342,6 +349,7 @@
|
|||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-flatten": "^1.1.0",
|
||||
"array-unique": "^0.3.2",
|
||||
|
@ -359,6 +367,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -379,6 +388,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"collection-visit": "^1.0.0",
|
||||
"component-emitter": "^1.2.1",
|
||||
|
@ -409,6 +419,7 @@
|
|||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^2.0.0",
|
||||
"async-each": "^1.0.1",
|
||||
|
@ -686,7 +697,6 @@
|
|||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
|
||||
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"prr": "~1.0.1"
|
||||
}
|
||||
|
@ -772,6 +782,7 @@
|
|||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^2.3.3",
|
||||
"define-property": "^0.2.5",
|
||||
|
@ -786,6 +797,7 @@
|
|||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
||||
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^0.1.0"
|
||||
}
|
||||
|
@ -794,6 +806,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -851,6 +864,7 @@
|
|||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
|
||||
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-unique": "^0.3.2",
|
||||
"define-property": "^1.0.0",
|
||||
|
@ -866,6 +880,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^1.0.0"
|
||||
}
|
||||
|
@ -874,6 +889,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -882,6 +898,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
||||
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^6.0.0"
|
||||
}
|
||||
|
@ -890,6 +907,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
||||
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^6.0.0"
|
||||
}
|
||||
|
@ -898,6 +916,7 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
||||
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-accessor-descriptor": "^1.0.0",
|
||||
"is-data-descriptor": "^1.0.0",
|
||||
|
@ -931,6 +950,7 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"is-number": "^3.0.0",
|
||||
|
@ -942,6 +962,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -961,6 +982,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
|
||||
"integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"detect-file": "^1.0.0",
|
||||
"is-glob": "^4.0.0",
|
||||
|
@ -1033,6 +1055,7 @@
|
|||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
|
@ -1107,6 +1130,7 @@
|
|||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz",
|
||||
"integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^2.0.0",
|
||||
"async-done": "^1.2.0",
|
||||
|
@ -1156,6 +1180,7 @@
|
|||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
|
||||
"integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob-watcher": "^5.0.3",
|
||||
"gulp-cli": "^2.2.0",
|
||||
|
@ -1167,6 +1192,7 @@
|
|||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz",
|
||||
"integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-colors": "^1.0.1",
|
||||
"archy": "^1.0.0",
|
||||
|
@ -1194,6 +1220,7 @@
|
|||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz",
|
||||
"integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"accord": "^0.29.0",
|
||||
"less": "2.6.x || ^3.7.1",
|
||||
|
@ -1229,6 +1256,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
|
||||
"integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"get-value": "^2.0.6",
|
||||
"has-values": "^1.0.0",
|
||||
|
@ -1239,6 +1267,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
|
||||
"integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^3.0.0",
|
||||
"kind-of": "^4.0.0"
|
||||
|
@ -1248,6 +1277,7 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
|
||||
"integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
|
@ -1270,8 +1300,7 @@
|
|||
"image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
||||
"optional": true
|
||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w="
|
||||
},
|
||||
"indx": {
|
||||
"version": "0.2.3",
|
||||
|
@ -1445,6 +1474,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.0.2"
|
||||
},
|
||||
|
@ -1453,6 +1483,7 @@
|
|||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
|
@ -1609,6 +1640,7 @@
|
|||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz",
|
||||
"integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"extend": "^3.0.0",
|
||||
"findup-sync": "^3.0.0",
|
||||
|
@ -1676,7 +1708,6 @@
|
|||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
|
@ -1685,8 +1716,7 @@
|
|||
"pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"optional": true
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1715,6 +1745,7 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
|
||||
"integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"findup-sync": "^2.0.0",
|
||||
"micromatch": "^3.0.4",
|
||||
|
@ -1726,6 +1757,7 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
|
||||
"integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"detect-file": "^1.0.0",
|
||||
"is-glob": "^3.1.0",
|
||||
|
@ -1737,6 +1769,7 @@
|
|||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
||||
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.0"
|
||||
}
|
||||
|
@ -1747,6 +1780,7 @@
|
|||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-diff": "^4.0.0",
|
||||
"array-unique": "^0.3.2",
|
||||
|
@ -1766,8 +1800,7 @@
|
|||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"optional": true
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
|
@ -1815,6 +1848,7 @@
|
|||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
"integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-diff": "^4.0.0",
|
||||
"array-unique": "^0.3.2",
|
||||
|
@ -2153,8 +2187,7 @@
|
|||
"prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
|
||||
"optional": true
|
||||
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
|
||||
},
|
||||
"pump": {
|
||||
"version": "2.0.1",
|
||||
|
@ -2212,6 +2245,7 @@
|
|||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
|
||||
"integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.11",
|
||||
"micromatch": "^3.1.10",
|
||||
|
@ -2394,6 +2428,7 @@
|
|||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||
"integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base": "^0.11.1",
|
||||
"debug": "^2.2.0",
|
||||
|
@ -2409,6 +2444,7 @@
|
|||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
||||
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-descriptor": "^0.1.0"
|
||||
}
|
||||
|
@ -2417,6 +2453,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -2750,6 +2787,7 @@
|
|||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
||||
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^3.0.0",
|
||||
"repeat-string": "^1.6.1"
|
||||
|
@ -2819,8 +2857,7 @@
|
|||
"uglify-to-browserify": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
|
||||
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
|
||||
"optional": true
|
||||
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc="
|
||||
},
|
||||
"unc-path-regex": {
|
||||
"version": "0.1.2",
|
||||
|
|
BIN
packs/Icons/Archetypes/Biotech Engineering.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Archetypes/Way of Technology.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Akimbo Mastery.webp
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Akimbo Style.webp
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Berserk Mastery.webp
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Berserk Style.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Brawler Mastery.webp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Brawler Style.webp
Normal file
After Width: | Height: | Size: 7 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Covert Mastery.webp
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Covert Style.webp
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Defense Mastery.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Defense Style.webp
Normal file
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 8.9 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Disruption Style.webp
Normal file
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 11 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Dual Wield Style.webp
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Duelist Mastery.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Duelist Style.webp
Normal file
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 8.8 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Equilibrium Style.webp
Normal file
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Explosives Style.webp
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Formation Mastery.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Formation Style.webp
Normal file
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 7.7 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Guerrilla Mastery.webp
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Guerrilla Style.webp
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Gunning Mastery.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Gunning Style.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Mounted Mastery.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Mounted Style.webp
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Onslaught Mastery.webp
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Onslaught Style.webp
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Sentinel Mastery.webp
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Sentinel Style.webp
Normal file
After Width: | Height: | Size: 7 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 7.7 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Shield Mastery.webp
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Shield Style.webp
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
packs/Icons/Fighting Styles and Masteries/Snapshot Mastery.webp
Normal file
After Width: | Height: | Size: 8.4 KiB |