Merging with upsteam/master

This commit is contained in:
Mike Magarino 2020-12-09 09:30:54 -05:00
commit 852309c973
183 changed files with 16890 additions and 3516 deletions

BIN
fonts/Aurebesh.ttf Normal file

Binary file not shown.

BIN
fonts/OpenSans-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
fonts/OpenSans-Italic.ttf Normal file

Binary file not shown.

View file

@ -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

View file

@ -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 weapons 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"
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View 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;

View 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;

View 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);
}

File diff suppressed because it is too large Load diff

View 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;
}
}
}
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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";
}

View 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);
}
}

View 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";
}

View 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";

View file

@ -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;

View file

@ -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);
}
}

View 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);
});

View 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});
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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

View file

@ -70,7 +70,7 @@ export default class AbilityUseDialog extends Dialog {
dlg.render(true);
});
}
/* -------------------------------------------- */
/* Helpers */
/* -------------------------------------------- */

View file

@ -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});
}
}

View file

@ -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);
});
}
}

View 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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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"]
}
}
};
};

View file

@ -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;
};
});

View file

@ -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 astrotechs 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 weapons 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 rolls outcome. Once youve 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 cant 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 cant use medium or heavy shields. Additionally, you cant 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 cant 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 tinkers 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 cant 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 cant 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));

View file

@ -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
View 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;
}

View file

@ -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 */
/* -------------------------------------------- */

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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:

View file

@ -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"
}
});
};

View file

@ -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
View file

@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Some files were not shown because too many files have changed in this diff Show more