Merge branch 'master' into character-sheet-importer
24
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
name: Gulp build and commit updated stylesheets
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, Develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [master, Develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
gulp-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
|
||||||
|
- name: Compile with Gulp
|
||||||
|
uses: elstudio/actions-js-build/build@v2
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
uses: elstudio/actions-js-build/commit@v3
|
||||||
|
with:
|
||||||
|
commitMessage: Regenerate css
|
|
@ -1,5 +1,7 @@
|
||||||
# Foundry Virtual Tabletop - SW5e Game System
|
# Foundry Virtual Tabletop - SW5e Game System
|
||||||
|
|
||||||
|
This unofficial implementation of the SW5e system for Foundry VTT is made by fans for fans and is not associated with SW5e, Disney, Wizards of the Coast, or their partners in any way.
|
||||||
|
|
||||||
This game system for [Foundry Virtual Tabletop](http://foundryvtt.com) provides character sheet and game system
|
This game system for [Foundry Virtual Tabletop](http://foundryvtt.com) provides character sheet and game system
|
||||||
support for the SW5E roleplaying game.
|
support for the SW5E roleplaying game.
|
||||||
|
|
||||||
|
|
BIN
fonts/EngliBesh-KG3W.ttf
Normal file
32
gulpfile.js
|
@ -1,31 +1,28 @@
|
||||||
const gulp = require('gulp');
|
const gulp = require("gulp");
|
||||||
const less = require('gulp-less');
|
const less = require("gulp-less");
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Compile LESS
|
/* Compile LESS
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
const SW5E_LESS = ["less/**/*.less"];
|
const SW5E_LESS = ["less/**/*.less"];
|
||||||
|
|
||||||
function compileLESS() {
|
function compileLESS() {
|
||||||
return gulp.src("less/original/sw5e.less")
|
return gulp.src("less/original/sw5e.less").pipe(less()).pipe(gulp.dest("./"));
|
||||||
.pipe(less())
|
|
||||||
.pipe(gulp.dest("./"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileGlobalLess() {
|
function compileGlobalLess() {
|
||||||
return gulp.src("less/update/sw5e-global.less")
|
return gulp.src("less/update/sw5e-global.less").pipe(less()).pipe(gulp.dest("./"));
|
||||||
.pipe(less())
|
|
||||||
.pipe(gulp.dest("./"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileLightLess() {
|
function compileLightLess() {
|
||||||
return gulp.src("less/update/sw5e-light.less")
|
return gulp.src("less/update/sw5e-light.less").pipe(less()).pipe(gulp.dest("./"));
|
||||||
.pipe(less())
|
|
||||||
.pipe(gulp.dest("./"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function compileDarkLess() {
|
function compileDarkLess() {
|
||||||
return gulp.src("less/update/sw5e-dark.less")
|
return gulp.src("less/update/sw5e-dark.less").pipe(less()).pipe(gulp.dest("./"));
|
||||||
.pipe(less())
|
|
||||||
.pipe(gulp.dest("./"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const css = gulp.series(compileLESS, compileGlobalLess, compileLightLess, compileDarkLess);
|
const css = gulp.series(compileLESS, compileGlobalLess, compileLightLess, compileDarkLess);
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
@ -40,8 +37,5 @@ function watchUpdates() {
|
||||||
/* Export Tasks
|
/* Export Tasks
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
exports.default = gulp.series(
|
exports.default = css;
|
||||||
gulp.parallel(css),
|
gulp.parallel(css), (exports.watch = gulp.series(gulp.parallel(css), watchUpdates));
|
||||||
watchUpdates
|
|
||||||
);
|
|
||||||
exports.css = css;
|
|
||||||
|
|
35
lang/en.json
|
@ -6,14 +6,14 @@
|
||||||
"ITEM.TypeClass": "Class",
|
"ITEM.TypeClass": "Class",
|
||||||
"ITEM.TypeConsumable": "Consumable",
|
"ITEM.TypeConsumable": "Consumable",
|
||||||
"ITEM.TypeEquipment": "Equipment",
|
"ITEM.TypeEquipment": "Equipment",
|
||||||
"ITEM.TypeFeat": "Feature",
|
"ITEM.TypeFeat": "Feat",
|
||||||
"ITEM.TypeLoot": "Loot",
|
"ITEM.TypeLoot": "Loot",
|
||||||
"ITEM.TypePower": "Power",
|
"ITEM.TypePower": "Power",
|
||||||
"ITEM.TypeTool": "Tool",
|
"ITEM.TypeTool": "Tool",
|
||||||
"ITEM.TypeWeapon": "Weapon",
|
"ITEM.TypeWeapon": "Weapon",
|
||||||
"ITEM.TypeArchetype": "Archetype",
|
"ITEM.TypeArchetype": "Archetype",
|
||||||
"ITEM.TypeBackground": "Background",
|
"ITEM.TypeBackground": "Background",
|
||||||
"ITEM.TypeLightsaberForm": "Lightsaber Form",
|
"ITEM.TypeLightsaberform": "Lightsaber Form",
|
||||||
"ITEM.TypeClassfeature": "Class Feature",
|
"ITEM.TypeClassfeature": "Class Feature",
|
||||||
"ITEM.TypeFightingmastery": "FightingMastery",
|
"ITEM.TypeFightingmastery": "FightingMastery",
|
||||||
"ITEM.TypeFightingstyle": "Fighting Style",
|
"ITEM.TypeFightingstyle": "Fighting Style",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"SW5E.AbilityUseHint": "Configure how you would like to use the {name} {type}.",
|
"SW5E.AbilityUseHint": "Configure how you would like to use the {name} {type}.",
|
||||||
"SW5E.AbilityUseUnavailableHint": "There are no uses of this item remaining!",
|
"SW5E.AbilityUseUnavailableHint": "There are no uses of this item remaining!",
|
||||||
"SW5E.AbilityUseChargedHint": "This {type} is charged and ready to use!",
|
"SW5E.AbilityUseChargedHint": "This {type} is charged and ready to use!",
|
||||||
"SW5E.AbilityUseRechargedHint": "This {type} is depleted and must be recharged!",
|
"SW5E.AbilityUseRechargeHint": "This {type} is depleted and must be recharged!",
|
||||||
"SW5E.AbilityUseNormalHint": "This {type} has {value} of {max} uses per {per} remaining.",
|
"SW5E.AbilityUseNormalHint": "This {type} has {value} of {max} uses per {per} remaining.",
|
||||||
"SW5E.AbilityUseConsumableChargeHint": "Using this {type} will consume 1 charge of {value} remaining.",
|
"SW5E.AbilityUseConsumableChargeHint": "Using this {type} will consume 1 charge of {value} remaining.",
|
||||||
"SW5E.AbilityUseConsumableQuantityHint": "Using this {type} will consume 1 quantity of {quantity} remaining",
|
"SW5E.AbilityUseConsumableQuantityHint": "Using this {type} will consume 1 quantity of {quantity} remaining",
|
||||||
|
@ -85,6 +85,11 @@
|
||||||
"SW5E.AlignmentBN": "Balanced Neutral",
|
"SW5E.AlignmentBN": "Balanced Neutral",
|
||||||
"SW5E.Archetypes": "Archetypes",
|
"SW5E.Archetypes": "Archetypes",
|
||||||
"SW5E.Appearance": "Appearance",
|
"SW5E.Appearance": "Appearance",
|
||||||
|
"SW5E.Attunement": "Attunement",
|
||||||
|
"SW5E.AttunementNone": "Attunement Not Required",
|
||||||
|
"SW5E.AttunementRequired": "Attunement Required",
|
||||||
|
"SW5E.AttunementAttuned": "Attuned",
|
||||||
|
"SW5E.Attuned": "Attuned",
|
||||||
"SW5E.ArmorClass": "Armor Class",
|
"SW5E.ArmorClass": "Armor Class",
|
||||||
"SW5E.AC": "AC",
|
"SW5E.AC": "AC",
|
||||||
"SW5E.ArmorProperties": "Armor Properties",
|
"SW5E.ArmorProperties": "Armor Properties",
|
||||||
|
@ -122,7 +127,6 @@
|
||||||
"SW5E.AttackPl": "Attacks",
|
"SW5E.AttackPl": "Attacks",
|
||||||
"SW5E.AttackRoll": "Attack Roll",
|
"SW5E.AttackRoll": "Attack Roll",
|
||||||
"SW5E.Attributes": "Attributes",
|
"SW5E.Attributes": "Attributes",
|
||||||
"SW5E.Attuned": "Attuned",
|
|
||||||
"SW5E.Background": "Background",
|
"SW5E.Background": "Background",
|
||||||
"SW5E.Biography": "Biography",
|
"SW5E.Biography": "Biography",
|
||||||
"SW5E.Bonds": "Bonds",
|
"SW5E.Bonds": "Bonds",
|
||||||
|
@ -189,7 +193,8 @@
|
||||||
"SW5E.ConsumeWarningNoSource": "The designated {type} source that {name} consumes no longer exists!",
|
"SW5E.ConsumeWarningNoSource": "The designated {type} source that {name} consumes no longer exists!",
|
||||||
"SW5E.ConsumeWarningNoQuantity": "{name} has run out of its designated {type}!",
|
"SW5E.ConsumeWarningNoQuantity": "{name} has run out of its designated {type}!",
|
||||||
"SW5E.ConsumeWarningZeroAttribute": "{name} has run out of its designated attribute resource pool!",
|
"SW5E.ConsumeWarningZeroAttribute": "{name} has run out of its designated attribute resource pool!",
|
||||||
|
"SW5E.ConsumeResource": "Consume Resource?",
|
||||||
|
"SW5E.ConsumeRecharge": "Consume Recharge?",
|
||||||
"SW5E.ConsumableAmmunition": "Ammunition",
|
"SW5E.ConsumableAmmunition": "Ammunition",
|
||||||
"SW5E.ConsumableFood": "Food",
|
"SW5E.ConsumableFood": "Food",
|
||||||
"SW5E.ConsumablePoison": "Poison",
|
"SW5E.ConsumablePoison": "Poison",
|
||||||
|
@ -288,6 +293,8 @@
|
||||||
"SW5E.ItemTypeContainerPl": "Containers",
|
"SW5E.ItemTypeContainerPl": "Containers",
|
||||||
"SW5E.ItemTypeEquipment": "Equipment",
|
"SW5E.ItemTypeEquipment": "Equipment",
|
||||||
"SW5E.ItemTypeEquipmentPl": "Equipment",
|
"SW5E.ItemTypeEquipmentPl": "Equipment",
|
||||||
|
"SW5E.ItemTypeFeat": "Feat",
|
||||||
|
"SW5E.ItemTypeFeatPl": "Feats",
|
||||||
"SW5E.ItemTypeFightingMastery": "Fighting Mastery",
|
"SW5E.ItemTypeFightingMastery": "Fighting Mastery",
|
||||||
"SW5E.ItemTypeFightingMasteryPl": "Fighting Masteries",
|
"SW5E.ItemTypeFightingMasteryPl": "Fighting Masteries",
|
||||||
"SW5E.ItemTypeFightingStyle": "Fighting Style",
|
"SW5E.ItemTypeFightingStyle": "Fighting Style",
|
||||||
|
@ -388,8 +395,6 @@
|
||||||
"SW5E.FlagsReliableTalentHint": "Rogues Reliable Talent Feature.",
|
"SW5E.FlagsReliableTalentHint": "Rogues Reliable Talent Feature.",
|
||||||
"SW5E.FlagsRemarkableAthlete": "Remarkable Athlete.",
|
"SW5E.FlagsRemarkableAthlete": "Remarkable Athlete.",
|
||||||
"SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
|
"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.FlagsWeaponCritThreshold": "Weapon Critical Hit Threshold",
|
||||||
"SW5E.FlagsWeaponCritThresholdHint": "An expanded critical hit threshold for weapon attacks.",
|
"SW5E.FlagsWeaponCritThresholdHint": "An expanded critical hit threshold for weapon attacks.",
|
||||||
"SW5E.FlagsPowerCritThreshold": "Power Critical Hit Threshold",
|
"SW5E.FlagsPowerCritThreshold": "Power Critical Hit Threshold",
|
||||||
|
@ -593,6 +598,7 @@
|
||||||
"SW5E.NoCharges": "No Charges",
|
"SW5E.NoCharges": "No Charges",
|
||||||
"SW5E.None": "None",
|
"SW5E.None": "None",
|
||||||
"SW5E.Normal": "Normal",
|
"SW5E.Normal": "Normal",
|
||||||
|
"SW5E.Notes": "Notes",
|
||||||
"SW5E.NotProficient": "Not Proficient",
|
"SW5E.NotProficient": "Not Proficient",
|
||||||
"SW5E.OtherFormula": "Other Formula",
|
"SW5E.OtherFormula": "Other Formula",
|
||||||
"SW5E.PactMagic": "Pact Magic",
|
"SW5E.PactMagic": "Pact Magic",
|
||||||
|
@ -646,7 +652,7 @@
|
||||||
"SW5E.RollMode": "Roll Mode",
|
"SW5E.RollMode": "Roll Mode",
|
||||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||||
"SW5E.Save": "Save",
|
"SW5E.Save": "Save",
|
||||||
|
"SW5E.Movement": "Movement",
|
||||||
"SW5E.MovementConfig": "Configure Movement Speed",
|
"SW5E.MovementConfig": "Configure Movement Speed",
|
||||||
"SW5E.MovementConfigHint": "Configure the movement speed and special movement attributes of this creature.",
|
"SW5E.MovementConfigHint": "Configure the movement speed and special movement attributes of this creature.",
|
||||||
"SW5E.MovementWalk": "Walk",
|
"SW5E.MovementWalk": "Walk",
|
||||||
|
@ -656,7 +662,14 @@
|
||||||
"SW5E.MovementFly": "Fly",
|
"SW5E.MovementFly": "Fly",
|
||||||
"SW5E.MovementSwim": "Swim",
|
"SW5E.MovementSwim": "Swim",
|
||||||
"SW5E.MovementUnits": "Units",
|
"SW5E.MovementUnits": "Units",
|
||||||
|
"SW5E.Senses": "Senses",
|
||||||
|
"SW5E.SensesConfig": "Configure Senses",
|
||||||
|
"SW5E.SensesConfigHint": "Configure any special sensory perception abilities that this actor possesses.",
|
||||||
|
"SW5E.SenseDarkvision": "Darkvision",
|
||||||
|
"SW5E.SenseBlindsight": "Blindsight",
|
||||||
|
"SW5E.SenseTremorsense": "Tremorsense",
|
||||||
|
"SW5E.SenseTruesight": "Truesight",
|
||||||
|
"SW5E.SenseSpecial": "Special Senses",
|
||||||
"SW5E.SheetClassCharacter": "Default Character Sheet",
|
"SW5E.SheetClassCharacter": "Default Character Sheet",
|
||||||
"SW5E.SheetClassCharacterOld": "Old Character Sheet",
|
"SW5E.SheetClassCharacterOld": "Old Character Sheet",
|
||||||
"SW5E.SheetClassNPC": "Default NPC Sheet",
|
"SW5E.SheetClassNPC": "Default NPC Sheet",
|
||||||
|
@ -725,7 +738,7 @@
|
||||||
"SW5E.PowerAtWill": "At-Will",
|
"SW5E.PowerAtWill": "At-Will",
|
||||||
"SW5E.PowerCastConsume": "Consume Power Slot?",
|
"SW5E.PowerCastConsume": "Consume Power Slot?",
|
||||||
"SW5E.PowerCastHint": "Configure how you would like to cast the",
|
"SW5E.PowerCastHint": "Configure how you would like to cast the",
|
||||||
"SW5E.PowerCastNoSlots": "You have no available power slots to cast this power",
|
"SW5E.PowerCastNoSlots": "You have no available {level} power slots with which to cast {name}",
|
||||||
"SW5E.PowerCastUpcast": "Cast at Level",
|
"SW5E.PowerCastUpcast": "Cast at Level",
|
||||||
"SW5E.PowercasterLevel": "Powercaster Level",
|
"SW5E.PowercasterLevel": "Powercaster Level",
|
||||||
"SW5E.PowerCastingHeader": "Power Casting",
|
"SW5E.PowerCastingHeader": "Power Casting",
|
||||||
|
@ -941,4 +954,4 @@
|
||||||
"SETTINGS.SWColorN": "Display Theme",
|
"SETTINGS.SWColorN": "Display Theme",
|
||||||
"SETTINGS.SWColorLight": "Light Theme",
|
"SETTINGS.SWColorLight": "Light Theme",
|
||||||
"SETTINGS.SWColorDark": "Dark Theme"
|
"SETTINGS.SWColorDark": "Dark Theme"
|
||||||
}
|
}
|
|
@ -69,12 +69,29 @@
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.attributes {
|
// Movement Configuration
|
||||||
input.temphp {
|
.movement {
|
||||||
width: 48%;
|
h4.attribute-name {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.config-button {
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
right: 0;
|
||||||
|
top: 1px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
&:hover .config-button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary HP
|
||||||
|
input.temphp {
|
||||||
|
width: 48%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +255,7 @@
|
||||||
|
|
||||||
li.skill {
|
li.skill {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
width: 225px;
|
||||||
padding: 3px 2px;
|
padding: 3px 2px;
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
|
@ -340,7 +358,7 @@
|
||||||
margin: 0 0 3px 0;
|
margin: 0 0 3px 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.configure-flags {
|
.config-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,9 +445,8 @@
|
||||||
&.rollable .item-image:hover {
|
&.rollable .item-image:hover {
|
||||||
background-image: url("../../icons/svg/d20-black.svg") !important;
|
background-image: url("../../icons/svg/d20-black.svg") !important;
|
||||||
}
|
}
|
||||||
i.attuned {
|
i.attuned { color: @colorTan; }
|
||||||
color: @colorTan;
|
i.not-attuned { color: @colorCrimson; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item uses
|
// Item uses
|
||||||
|
@ -470,8 +487,8 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
&:last-child { border-right: none; }
|
&:last-child { border-right: none; }
|
||||||
&.item-action {flex: 0 0 100px}
|
&.item-action {flex: 0 0 100px}
|
||||||
|
&.attunement {flex: 0 0 24px}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-weight {
|
.item-weight {
|
||||||
flex: 0 0 60px;
|
flex: 0 0 60px;
|
||||||
border-left: 1px solid @colorFaint;
|
border-left: 1px solid @colorFaint;
|
||||||
|
@ -491,6 +508,15 @@
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
color: @colorDark;
|
color: @colorDark;
|
||||||
border-top: 1px solid @colorFaint;
|
border-top: 1px solid @colorFaint;
|
||||||
|
h2 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 2px solid rgb(13, 153, 204);
|
||||||
|
color: #c40f0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,26 +590,23 @@
|
||||||
.powercasting-ability {
|
.powercasting-ability {
|
||||||
flex: 0 0 240px;
|
flex: 0 0 240px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
label, span {
|
||||||
input, span {
|
flex: none;
|
||||||
flex: 0 0 32px;
|
}
|
||||||
|
input {
|
||||||
|
flex: 0 0 28px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
flex: 0 0 150px;
|
flex: 0 0 120px;
|
||||||
}
|
|
||||||
h3.power-dc {
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.power-slots,
|
.power-slots,
|
||||||
.power-comps {
|
.power-comps {
|
||||||
flex: 0 0 75px;
|
flex: none;
|
||||||
padding-right: 5px;
|
padding: 0 5px;
|
||||||
text-align: right;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
border-right: 1px solid @colorFaint;
|
border-right: 1px solid @colorFaint;
|
||||||
|
@ -600,9 +623,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.power-uses {
|
.powerbook .power-uses {
|
||||||
padding-right: 8px;
|
padding-right: 5px;
|
||||||
text-align: right !important;
|
text-align: right;
|
||||||
|
color: @colorTan;
|
||||||
}
|
}
|
||||||
|
|
||||||
.power-school, .power-action, .power-target {
|
.power-school, .power-action, .power-target {
|
||||||
|
@ -651,5 +675,6 @@
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -178,11 +178,14 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rollable Links
|
// Rollable Titles
|
||||||
.editable .rollable:hover {
|
.editable .rollable:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.editable h4.rollable:hover,
|
||||||
|
.editable .rollable:hover > h4 {
|
||||||
color: #000;
|
color: #000;
|
||||||
text-shadow: 0 0 10px red;
|
text-shadow: 0 0 10px red;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separators
|
// Separators
|
||||||
|
@ -306,6 +309,7 @@
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.filter-list {
|
.filter-list {
|
||||||
|
align-items: center;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -382,6 +386,30 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Item Name
|
||||||
|
.item-name {
|
||||||
|
flex: 2;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
align-items: center;
|
||||||
|
h3, h4 {
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control Buttons
|
||||||
|
.item-controls {
|
||||||
|
flex: 0 0 60px;
|
||||||
|
justify-content: space-between;
|
||||||
|
a {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Individual Item
|
// Individual Item
|
||||||
.item {
|
.item {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -398,11 +426,6 @@
|
||||||
border: none;
|
border: none;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,32 +442,13 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.item-name {
|
h3 {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
//.modesto();
|
//.modesto();
|
||||||
|
text-align: left;
|
||||||
font-size: 16px;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
@ -482,4 +486,41 @@
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
/* HUD
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
|
.placeable-hud .control-icon {
|
||||||
|
box-sizing: content-box;
|
||||||
|
width: 40px;
|
||||||
|
flex: 0 0 40px;
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #FBF4F4;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
box-shadow: 0 0 15px #000;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 8px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
#token-hud .status-effects {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
left: 50px;
|
||||||
|
top: 0;
|
||||||
|
display: grid;
|
||||||
|
padding: 3px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
width: 100px;
|
||||||
|
color: #FBF4F4;
|
||||||
|
grid-template-columns: 25px 25px 25px 25px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
box-shadow: 0 0 15px #000;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
/* Basic Structure */
|
/* Basic Structure */
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
.sw5e.sheet.actor.character {
|
.sw5e.sheet.actor.character {
|
||||||
min-width: 720px;
|
min-width: 800px;
|
||||||
min-height: 680px;
|
min-height: 680px;
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
@ -160,7 +160,7 @@
|
||||||
padding: 0 3px 3px;
|
padding: 0 3px 3px;
|
||||||
label {
|
label {
|
||||||
flex: 0 0 20px;
|
flex: 0 0 20px;
|
||||||
.bungeeInline();
|
.russoOne();
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
/* Chat Cards
|
/* Chat Cards
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
.sw5e.chat-card {
|
.sw5e.chat-card,
|
||||||
|
.midi-qol-item-card {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
.bungeeInline();
|
.engli-Besh();
|
||||||
color: @colorOlive;
|
color: @colorOlive;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #111;
|
color: #111;
|
||||||
|
@ -72,7 +73,7 @@
|
||||||
|
|
||||||
span {
|
span {
|
||||||
border-right: 2px groove #FFF;
|
border-right: 2px groove #FFF;
|
||||||
padding: 0 5px 0 0;
|
padding: 0 3px 0 0;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@import "./variables.less";
|
@import "./variables.less";
|
||||||
.sw5e.sheet.item {
|
.sw5e.sheet.item {
|
||||||
min-height: 420px;
|
min-height: 660px;
|
||||||
|
min-width: 680px;
|
||||||
|
|
||||||
/* ----------------------------------------- */
|
/* ----------------------------------------- */
|
||||||
/* Sheet Header */
|
/* Sheet Header */
|
||||||
|
@ -10,7 +11,19 @@
|
||||||
img.profile {
|
img.profile {
|
||||||
border: 2px solid #000;
|
border: 2px solid #000;
|
||||||
}
|
}
|
||||||
|
h1 {
|
||||||
|
input {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.header-details.flexrow {
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
.charname {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.item-subtitle {
|
.item-subtitle {
|
||||||
flex: 0 0 80px;
|
flex: 0 0 80px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
@ -20,8 +33,8 @@
|
||||||
color: @colorTan;
|
color: @colorTan;
|
||||||
|
|
||||||
.item-type {
|
.item-type {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
line-height: 26px;
|
line-height: 24px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,19 +42,298 @@
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
li {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-navigation {
|
.sheet-navigation {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
.item {
|
.item {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-body {
|
.sheet-body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: none;
|
||||||
|
color: #c40f0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 2px solid rgb(13, 153, 204);
|
||||||
|
color: #c40f0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: none;
|
||||||
|
color: #c40f0f;
|
||||||
|
}
|
||||||
|
.smalltable {
|
||||||
|
table {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
&:nth-child(odd) {
|
||||||
|
width: 50px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&:nth-child(even) {
|
||||||
|
width: 150px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
padding: 0px 10px 0px 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
color: #000000;
|
||||||
|
text-shadow: none;
|
||||||
|
border-bottom: 0px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
text-transform: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
&:nth-child(odd) {
|
||||||
|
width: 50px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&:nth-child(even) {
|
||||||
|
width: 150px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
padding: 0px 10px 0px 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.medtable {
|
||||||
|
table {
|
||||||
|
width: 500px;
|
||||||
|
border: 0px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
&:nth-child(odd) {
|
||||||
|
width: 50px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&:nth-child(even) {
|
||||||
|
width: 450px;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
padding: 0px 10px 0px 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
color: #000000;
|
||||||
|
text-shadow: none;
|
||||||
|
border-bottom: 0px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
text-transform: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
&:nth-child(odd) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&:nth-child(even) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.classtable {
|
||||||
|
blockquote {
|
||||||
|
border-left: 0px;
|
||||||
|
border-right: 0px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
width: 600px;
|
||||||
|
h3 {
|
||||||
|
color: #000000;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-left: 0px;
|
||||||
|
border-right: 0px;
|
||||||
|
border-top: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
font-style: normal;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
color: #000000;
|
||||||
|
text-shadow: none;
|
||||||
|
border-bottom: 0px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
text-transform: none;
|
||||||
|
font-style: normal;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
color: #000000;
|
||||||
|
text-shadow: none;
|
||||||
|
border-bottom: 0px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
text-transform: none;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.speciestable {
|
||||||
|
blockquote {
|
||||||
|
width: 620px;
|
||||||
|
padding: 15px 10px;
|
||||||
|
margin: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
border-top: 2px solid #0d99cc !important;
|
||||||
|
border-bottom: 2px solid #0d99cc !important;
|
||||||
|
border-left: 0 !important;
|
||||||
|
border-right: 0 !important;
|
||||||
|
-webkit-box-shadow: 0 0 1.5em rgba(13, 153, 204, .5) !important;
|
||||||
|
box-shadow: 0 0 1.5em rgba(13, 153, 204, .5) !important;
|
||||||
|
overflow-x: auto;
|
||||||
|
h3 {
|
||||||
|
color: #000000;
|
||||||
|
font-size: 22px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 18px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 0 0 0 0;
|
||||||
|
border-bottom: none;
|
||||||
|
overflow-x: auto;
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background-color: #c9d6db;
|
||||||
|
}
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
&:nth-child(1) {
|
||||||
|
padding-right: 5px;
|
||||||
|
width: 100px;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
color: #000000;
|
||||||
|
font-size: 15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: #bdc8cc;
|
||||||
|
text-shadow: none;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 20px;
|
||||||
|
border-top: 5px solid #0d99cc;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
&:before {
|
||||||
|
display: inline-block;
|
||||||
|
font-style: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.entity-link {
|
||||||
|
background: #DDD;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border: 1px solid #4b4a44;
|
||||||
|
border-radius: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-break: break-all;
|
||||||
|
i {
|
||||||
|
&::before {
|
||||||
|
content: url("ui/jedi-order.svg") !important;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#species-description {
|
||||||
|
h2 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 2px solid rgb(13, 153, 204);
|
||||||
|
color: #c40f0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#Traits {
|
||||||
|
h2 {
|
||||||
|
font-family: 'Russo One';
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 2px solid rgb(13, 153, 204);
|
||||||
|
color: #c40f0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
|
|
|
@ -31,11 +31,4 @@
|
||||||
.summary {
|
.summary {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.powercasting-ability {
|
|
||||||
label {
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,33 +6,3 @@
|
||||||
@import "character.less";
|
@import "character.less";
|
||||||
@import "npc.less";
|
@import "npc.less";
|
||||||
@import "vehicle.less";
|
@import "vehicle.less";
|
||||||
|
|
||||||
// TODO: Remove number styling after 0.7.x
|
|
||||||
input[type="number"] {
|
|
||||||
width: calc(100% - 2px);
|
|
||||||
min-width: 20px;
|
|
||||||
height: 26px;
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
padding: 1px 3px;
|
|
||||||
margin: 0;
|
|
||||||
color: #191813;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
text-align: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
border: 1px solid #7a7971;
|
|
||||||
border-radius: 3px;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
-moz-user-select: text;
|
|
||||||
-ms-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 5px red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,15 +19,15 @@
|
||||||
font-size: @font-size;
|
font-size: @font-size;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
/* bungee-inline-regular - latin */
|
/* engli-besh */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Bungee Inline';
|
font-family: 'Engli-Besh';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('./fonts/BungeeInline.ttf');
|
src: url('./fonts/EngliBesh-KG3W.ttf');
|
||||||
}
|
}
|
||||||
.bungeeInline {
|
.engli-Besh {
|
||||||
font-family: 'Bungee Inline';
|
font-family: 'Engli-Besh';
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
@blockquoteShadow: 0 0 20px rgba(@colorBlue, 0.8);
|
@blockquoteShadow: 0 0 20px rgba(@colorBlue, 0.8);
|
||||||
|
|
||||||
//forms
|
//forms
|
||||||
@inputBackgroundColor: white;
|
@inputBackgroundColor: @colorGray;
|
||||||
@inputBorderNormal: @colorLightGray;
|
@inputBorderNormal: @colorLightGray;
|
||||||
@inputBorderHover: @colorGray;
|
@inputBorderHover: @colorGray;
|
||||||
@inputBorderFocus: @colorRed;
|
@inputBorderFocus: @colorRed;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
.dropShadow1();
|
.dropShadow1();
|
||||||
}
|
}
|
||||||
.sw5e.sheet.actor.character {
|
.sw5e.sheet.actor.character {
|
||||||
min-width: 780px;
|
min-width: 850px;
|
||||||
min-height: 720px;
|
min-height: 720px;
|
||||||
}
|
}
|
||||||
.sw5e.sheet .window-content {
|
.sw5e.sheet .window-content {
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
grid-template-rows: 1fr 26px auto;
|
grid-template-rows: 1fr 26px auto;
|
||||||
grid-template-columns: 128px 1fr;
|
grid-template-columns: 128px 1fr;
|
||||||
column-gap: 8px;
|
column-gap: 8px;
|
||||||
row-gap: 8px;
|
grid-row-gap: 8px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
|
@ -683,7 +683,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-gap: 16px;
|
grid-gap: 16px;
|
||||||
row-gap: 8px;
|
grid-row-gap: 8px;
|
||||||
|
|
||||||
input,
|
input,
|
||||||
select {
|
select {
|
||||||
|
@ -715,7 +715,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.languages {
|
.languages {
|
||||||
grid-column-end: span 2;
|
grid-column-end: span 1;
|
||||||
label {
|
label {
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -745,7 +745,7 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-gap: 4px;
|
grid-gap: 4px;
|
||||||
row-gap: 4px;
|
grid-row-gap: 4px;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
@ -994,7 +994,7 @@
|
||||||
}
|
}
|
||||||
&.limited {
|
&.limited {
|
||||||
grid-template-rows: 144px auto;
|
grid-template-rows: 144px auto;
|
||||||
row-gap: 8px;
|
grid-row-gap: 8px;
|
||||||
header {
|
header {
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
color: @colorBlue;
|
color: @colorBlue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw5e.chat-card {
|
.sw5e.chat-card,
|
||||||
|
.midi-qol-item-card {
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
color: @chatNotificationColor;
|
color: @chatNotificationColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw5e.chat-card {
|
.sw5e.chat-card,
|
||||||
|
.midi-qol-item-card {
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
h3 {
|
h3 {
|
||||||
|
@ -94,7 +95,8 @@
|
||||||
}
|
}
|
||||||
#chat-controls {
|
#chat-controls {
|
||||||
.roll-type-select {
|
.roll-type-select {
|
||||||
background: @inputBackgroundColor;
|
background: #4f4f4f;
|
||||||
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
color: @bodyFontColor;
|
color: @bodyFontColor;
|
||||||
|
@ -102,7 +104,7 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
#chat-form textarea {
|
#chat-form textarea {
|
||||||
background: @inputBackgroundColor;
|
background: #4f4f4f;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,8 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw5e.chat-card {
|
.sw5e.chat-card,
|
||||||
|
.midi-qol-item-card {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
|
@ -168,6 +169,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#chat-controls {
|
#chat-controls {
|
||||||
|
&.roll-type-select {
|
||||||
|
background: #4f4f4f;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
label {
|
label {
|
||||||
color: @colorBlack;
|
color: @colorBlack;
|
||||||
|
@ -175,7 +180,7 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
#chat-form textarea {
|
#chat-form textarea {
|
||||||
background: white;
|
background: #4f4f4f;
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -48,6 +48,12 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('./fonts/Aurebesh.ttf');
|
src: url('./fonts/Aurebesh.ttf');
|
||||||
}
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Engli-Besh';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./fonts/EngliBesh-KG3W.ttf');
|
||||||
|
}
|
||||||
@import "_variables.less";
|
@import "_variables.less";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
@ -77,6 +83,9 @@ html {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
.openSans(13px, 400);
|
.openSans(13px, 400);
|
||||||
|
background-image: url('./ui/SW5e-logo.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { d20Roll, damageRoll } from "../dice.js";
|
import { d20Roll, damageRoll } from "../dice.js";
|
||||||
import ShortRestDialog from "../apps/short-rest.js";
|
import ShortRestDialog from "../apps/short-rest.js";
|
||||||
import LongRestDialog from "../apps/long-rest.js";
|
import LongRestDialog from "../apps/long-rest.js";
|
||||||
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
|
||||||
import AbilityTemplate from "../pixi/ability-template.js";
|
|
||||||
import {SW5E} from '../config.js';
|
import {SW5E} from '../config.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,8 +92,14 @@ export default class Actor5e extends Actor {
|
||||||
init.total = init.mod + init.prof + init.bonus;
|
init.total = init.mod + init.prof + init.bonus;
|
||||||
|
|
||||||
// Prepare power-casting data
|
// Prepare power-casting data
|
||||||
this._computePowercastingDC(this.data);
|
data.attributes.powerdc = data.attributes.powercasting ? data.abilities[data.attributes.powercasting].dc : 10;
|
||||||
this._computePowercastingProgression(this.data);
|
this._computePowercastingProgression(this.data);
|
||||||
|
|
||||||
|
// Compute owned item attributes which depend on prepared Actor data
|
||||||
|
this.items.forEach(item => {
|
||||||
|
item.getSaveDC();
|
||||||
|
item.getAttackToHit();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -163,14 +167,17 @@ export default class Actor5e extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acquire archetype features
|
// Acquire archetype features
|
||||||
const subConfig = clsConfig.archetypes[archetypeName] || {};
|
const archConfig = clsConfig.archetypes[archetypeName] || {};
|
||||||
for ( let [l, f] of Object.entries(subConfig.features || {}) ) {
|
for ( let [l, f] of Object.entries(archConfig.features || {}) ) {
|
||||||
l = parseInt(l);
|
l = parseInt(l);
|
||||||
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
|
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load item data for all identified features
|
// Load item data for all identified features
|
||||||
const features = await Promise.all(ids.map(id => fromUuid(id)));
|
const features = [];
|
||||||
|
for ( let id of ids ) {
|
||||||
|
features.push(await fromUuid(id));
|
||||||
|
}
|
||||||
|
|
||||||
// Class powers should always be prepared
|
// Class powers should always be prepared
|
||||||
for ( const feature of features ) {
|
for ( const feature of features ) {
|
||||||
|
@ -207,7 +214,7 @@ export default class Actor5e extends Actor {
|
||||||
const updateData = expandObject(u);
|
const updateData = expandObject(u);
|
||||||
const config = {
|
const config = {
|
||||||
className: updateData.name || item.data.name,
|
className: updateData.name || item.data.name,
|
||||||
archetypeName: updateData.data.archetype || item.data.data.archetype,
|
archetypeName: getProperty(updateData, "data.archetype") || item.data.data.archetype,
|
||||||
level: getProperty(updateData, "data.levels"),
|
level: getProperty(updateData, "data.levels"),
|
||||||
priorLevel: item ? item.data.data.levels : 0
|
priorLevel: item ? item.data.data.levels : 0
|
||||||
}
|
}
|
||||||
|
@ -314,19 +321,22 @@ export default class Actor5e extends Actor {
|
||||||
const joat = flags.jackOfAllTrades;
|
const joat = flags.jackOfAllTrades;
|
||||||
const observant = flags.observantFeat;
|
const observant = flags.observantFeat;
|
||||||
const skillBonus = Number.isNumeric(bonuses.skill) ? parseInt(bonuses.skill) : 0;
|
const skillBonus = Number.isNumeric(bonuses.skill) ? parseInt(bonuses.skill) : 0;
|
||||||
let round = Math.floor;
|
|
||||||
for (let [id, skl] of Object.entries(data.skills)) {
|
for (let [id, skl] of Object.entries(data.skills)) {
|
||||||
skl.value = parseFloat(skl.value || 0);
|
skl.value = Math.clamped(Number(skl.value).toNearest(0.5), 0, 2) ?? 0;
|
||||||
|
let round = Math.floor;
|
||||||
|
|
||||||
// Apply Remarkable Athlete or Jack of all Trades
|
// Remarkable
|
||||||
let multi = skl.value;
|
if ( athlete && (skl.value < 0.5) && feats.remarkableAthlete.abilities.includes(skl.ability) ) {
|
||||||
if ( athlete && (skl.value === 0) && feats.remarkableAthlete.abilities.includes(skl.ability) ) {
|
skl.value = 0.5;
|
||||||
multi = 0.5;
|
|
||||||
round = Math.ceil;
|
round = Math.ceil;
|
||||||
}
|
}
|
||||||
if ( joat && (skl.value === 0 ) ) multi = 0.5;
|
|
||||||
|
|
||||||
// Retain the maximum skill proficiency when skill proficiencies are merged
|
// Jack of All Trades
|
||||||
|
if ( joat && (skl.value < 0.5) ) {
|
||||||
|
skl.value = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polymorph Skill Proficiencies
|
||||||
if ( originalSkills ) {
|
if ( originalSkills ) {
|
||||||
skl.value = Math.max(skl.value, originalSkills[id].value);
|
skl.value = Math.max(skl.value, originalSkills[id].value);
|
||||||
}
|
}
|
||||||
|
@ -334,7 +344,7 @@ export default class Actor5e extends Actor {
|
||||||
// Compute modifier
|
// Compute modifier
|
||||||
skl.bonus = checkBonus + skillBonus;
|
skl.bonus = checkBonus + skillBonus;
|
||||||
skl.mod = data.abilities[skl.ability].mod;
|
skl.mod = data.abilities[skl.ability].mod;
|
||||||
skl.prof = round(multi * data.attributes.prof);
|
skl.prof = round(skl.value * data.attributes.prof);
|
||||||
skl.total = skl.mod + skl.prof + skl.bonus;
|
skl.total = skl.mod + skl.prof + skl.bonus;
|
||||||
|
|
||||||
// Compute passive bonus
|
// Compute passive bonus
|
||||||
|
@ -345,31 +355,6 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the powercasting DC for all item abilities which use power DC scaling
|
|
||||||
* @param {object} actorData The actor data being prepared
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_computePowercastingDC(actorData) {
|
|
||||||
|
|
||||||
// Compute the powercasting DC
|
|
||||||
const data = actorData.data;
|
|
||||||
data.attributes.powerdc = data.attributes.powercasting ? data.abilities[data.attributes.powercasting].dc : 10;
|
|
||||||
|
|
||||||
// Apply powercasting DC to any power items which use it
|
|
||||||
for ( let i of this.items ) {
|
|
||||||
const save = i.data.data.save;
|
|
||||||
if ( save?.ability ) {
|
|
||||||
if ( save.scaling === "power" ) save.dc = data.attributes.powerdc;
|
|
||||||
else if ( save.scaling !== "flat" ) save.dc = data.abilities[save.scaling]?.dc ?? 10;
|
|
||||||
const ability = CONFIG.SW5E.abilities[save.ability];
|
|
||||||
i.labels.save = game.i18n.format("SW5E.SaveDC", {dc: save.dc || "", ability});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare data related to the power-casting capabilities of the Actor
|
* Prepare data related to the power-casting capabilities of the Actor
|
||||||
* @private
|
* @private
|
||||||
|
@ -418,7 +403,7 @@ export default class Actor5e extends Actor {
|
||||||
progression.slot = Math.ceil(caster.data.levels / denom);
|
progression.slot = Math.ceil(caster.data.levels / denom);
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXCEPTION: NPC with an explicit powercaster level
|
// EXCEPTION: NPC with an explicit power-caster level
|
||||||
if (isNPC && actorData.data.details.powerLevel) {
|
if (isNPC && actorData.data.details.powerLevel) {
|
||||||
progression.slot = actorData.data.details.powerLevel;
|
progression.slot = actorData.data.details.powerLevel;
|
||||||
}
|
}
|
||||||
|
@ -429,9 +414,9 @@ export default class Actor5e extends Actor {
|
||||||
for ( let [n, lvl] of Object.entries(powers) ) {
|
for ( let [n, lvl] of Object.entries(powers) ) {
|
||||||
let i = parseInt(n.slice(-1));
|
let i = parseInt(n.slice(-1));
|
||||||
if ( Number.isNaN(i) ) continue;
|
if ( Number.isNaN(i) ) continue;
|
||||||
if ( Number.isNumeric(lvl.override) ) lvl.max = Math.max(parseInt(lvl.override), 1);
|
if ( Number.isNumeric(lvl.override) ) lvl.max = Math.max(parseInt(lvl.override), 0);
|
||||||
else lvl.max = slots[i-1] || 0;
|
else lvl.max = slots[i-1] || 0;
|
||||||
lvl.value = Math.min(parseInt(lvl.value), lvl.max);
|
lvl.value = parseInt(lvl.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the Actor's pact magic level (if any)
|
// Determine the Actor's pact magic level (if any)
|
||||||
|
@ -473,8 +458,8 @@ export default class Actor5e extends Actor {
|
||||||
return weight + (q * w);
|
return weight + (q * w);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// [Optional] add Currency Weight
|
// [Optional] add Currency Weight (for non-transformed actors)
|
||||||
if ( game.settings.get("sw5e", "currencyWeight") ) {
|
if ( game.settings.get("sw5e", "currencyWeight") && actorData.data.currency ) {
|
||||||
const currency = actorData.data.currency;
|
const currency = actorData.data.currency;
|
||||||
const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0);
|
const numCoins = Object.values(currency).reduce((val, denom) => val += Math.max(denom, 0), 0);
|
||||||
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
weight += numCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||||
|
@ -546,20 +531,82 @@ export default class Actor5e extends Actor {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async createOwnedItem(itemData, options) {
|
async createEmbeddedEntity(embeddedName, itemData, options={}) {
|
||||||
|
|
||||||
// Assume NPCs are always proficient with weapons and always have powers prepared
|
// Pre-creation steps for owned items
|
||||||
if ( !this.hasPlayerOwner ) {
|
if ( embeddedName === "OwnedItem" ) this._preCreateOwnedItem(itemData, options);
|
||||||
let t = itemData.type;
|
|
||||||
let initial = {};
|
// Standard embedded entity creation
|
||||||
if ( t === "weapon" ) initial["data.proficient"] = true;
|
return super.createEmbeddedEntity(embeddedName, itemData, options);
|
||||||
if ( ["weapon", "equipment"].includes(t) ) initial["data.equipped"] = true;
|
|
||||||
if ( t === "power" ) initial["data.prepared"] = true;
|
|
||||||
mergeObject(itemData, initial);
|
|
||||||
}
|
|
||||||
return super.createOwnedItem(itemData, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A temporary shim function which will eventually (in core fvtt version 0.8.0+) be migrated to the new abstraction layer
|
||||||
|
* @param itemData
|
||||||
|
* @param options
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_preCreateOwnedItem(itemData, options) {
|
||||||
|
if ( this.data.type === "vehicle" ) return;
|
||||||
|
const isNPC = this.data.type === 'npc';
|
||||||
|
let initial = {};
|
||||||
|
switch ( itemData.type ) {
|
||||||
|
|
||||||
|
case "weapon":
|
||||||
|
if ( getProperty(itemData, "data.equipped") === undefined ) {
|
||||||
|
initial["data.equipped"] = isNPC; // NPCs automatically equip weapons
|
||||||
|
}
|
||||||
|
if ( getProperty(itemData, "data.proficient") === undefined ) {
|
||||||
|
if ( isNPC ) {
|
||||||
|
initial["data.proficient"] = true; // NPCs automatically have equipment proficiency
|
||||||
|
} else {
|
||||||
|
const weaponProf = {
|
||||||
|
"natural": true,
|
||||||
|
"simpleVW": "sim",
|
||||||
|
"simpleB": "sim",
|
||||||
|
"simpleLW": "sim",
|
||||||
|
"martialVW": "mar",
|
||||||
|
"martialB": "mar",
|
||||||
|
"martialLW": "mar"
|
||||||
|
}[itemData.data?.weaponType]; // Player characters check proficiency
|
||||||
|
const actorWeaponProfs = this.data.data.traits?.weaponProf?.value || [];
|
||||||
|
const hasWeaponProf = (weaponProf === true) || actorWeaponProfs.includes(weaponProf);
|
||||||
|
initial["data.proficient"] = hasWeaponProf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "equipment":
|
||||||
|
if ( getProperty(itemData, "data.equipped") === undefined ) {
|
||||||
|
initial["data.equipped"] = isNPC; // NPCs automatically equip equipment
|
||||||
|
}
|
||||||
|
if ( getProperty(itemData, "data.proficient") === undefined ) {
|
||||||
|
if ( isNPC ) {
|
||||||
|
initial["data.proficient"] = true; // NPCs automatically have equipment proficiency
|
||||||
|
} else {
|
||||||
|
const armorProf = {
|
||||||
|
"natural": true,
|
||||||
|
"clothing": true,
|
||||||
|
"light": "lgt",
|
||||||
|
"medium": "med",
|
||||||
|
"heavy": "hvy",
|
||||||
|
"shield": "shl"
|
||||||
|
}[itemData.data?.armor?.type]; // Player characters check proficiency
|
||||||
|
const actorArmorProfs = this.data.data.traits?.armorProf?.value || [];
|
||||||
|
const hasEquipmentProf = (armorProf === true) || actorArmorProfs.includes(armorProf);
|
||||||
|
initial["data.proficient"] = hasEquipmentProf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "power":
|
||||||
|
initial["data.prepared"] = true; // automatically prepare powers for everyone
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mergeObject(itemData, initial);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Gameplay Mechanics */
|
/* Gameplay Mechanics */
|
||||||
|
@ -600,77 +647,16 @@ export default class Actor5e extends Actor {
|
||||||
"data.attributes.hp.temp": tmp - dt,
|
"data.attributes.hp.temp": tmp - dt,
|
||||||
"data.attributes.hp.value": dh
|
"data.attributes.hp.value": dh
|
||||||
};
|
};
|
||||||
return this.update(updates);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
// Delegate damage application to a hook
|
||||||
|
// TODO replace this in the future with a better modifyTokenAttribute function in the core
|
||||||
/**
|
const allowed = Hooks.call("modifyTokenAttribute", {
|
||||||
* Cast a Power, consuming a power slot of a certain level
|
attribute: "attributes.hp",
|
||||||
* @param {Item5e} item The power being cast by the actor
|
value: amount,
|
||||||
* @param {Event} event The originating user interaction which triggered the cast
|
isDelta: false,
|
||||||
*/
|
isBar: true
|
||||||
async usePower(item, {configureDialog=true}={}) {
|
}, updates);
|
||||||
if ( item.data.type !== "power" ) throw new Error("Wrong Item type");
|
return allowed !== false ? this.update(updates) : this;
|
||||||
const itemData = item.data.data;
|
|
||||||
|
|
||||||
// Configure powercasting data
|
|
||||||
let lvl = itemData.level;
|
|
||||||
const usesSlots = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
|
||||||
const limitedUses = !!itemData.uses.per;
|
|
||||||
let consumeSlot = `power${lvl}`;
|
|
||||||
let consumeUse = false;
|
|
||||||
let placeTemplate = false;
|
|
||||||
|
|
||||||
// Configure power slot consumption and measured template placement from the form
|
|
||||||
if ( configureDialog && (usesSlots || item.hasAreaTarget || limitedUses) ) {
|
|
||||||
const usage = await AbilityUseDialog.create(item);
|
|
||||||
if ( usage === null ) return;
|
|
||||||
|
|
||||||
// Determine consumption preferences
|
|
||||||
consumeSlot = Boolean(usage.get("consumeSlot"));
|
|
||||||
consumeUse = Boolean(usage.get("consumeUse"));
|
|
||||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
|
||||||
|
|
||||||
// Determine the cast power level
|
|
||||||
const isPact = usage.get('level') === 'pact';
|
|
||||||
const lvl = isPact ? this.data.data.powers.pact.level : parseInt(usage.get("level"));
|
|
||||||
if ( lvl !== item.data.data.level ) {
|
|
||||||
const upcastData = mergeObject(item.data, {"data.level": lvl}, {inplace: false});
|
|
||||||
item = item.constructor.createOwned(upcastData, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Denote the power slot being consumed
|
|
||||||
if ( consumeSlot ) consumeSlot = isPact ? "pact" : `power${lvl}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Actor data
|
|
||||||
if ( usesSlots && consumeSlot && (lvl > 0) ) {
|
|
||||||
const slots = parseInt(this.data.data.powers[consumeSlot]?.value);
|
|
||||||
if ( slots === 0 || Number.isNaN(slots) ) {
|
|
||||||
return ui.notifications.error(game.i18n.localize("SW5E.PowerCastNoSlots"));
|
|
||||||
}
|
|
||||||
await this.update({
|
|
||||||
[`data.powers.${consumeSlot}.value`]: Math.max(slots - 1, 0)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Item data
|
|
||||||
if ( limitedUses && consumeUse ) {
|
|
||||||
const uses = parseInt(itemData.uses.value || 0);
|
|
||||||
if ( uses <= 0 ) ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: item.name}));
|
|
||||||
await item.update({"data.uses.value": Math.max(parseInt(item.data.data.uses.value || 0) - 1, 0)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate ability template placement workflow if selected
|
|
||||||
if ( placeTemplate && item.hasAreaTarget ) {
|
|
||||||
const template = AbilityTemplate.fromItem(item);
|
|
||||||
if ( template ) template.drawPreview();
|
|
||||||
if ( this.sheet.rendered ) this.sheet.minimize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the Item roll
|
|
||||||
return item.roll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -989,7 +975,7 @@ export default class Actor5e extends Actor {
|
||||||
// Adjust actor data
|
// Adjust actor data
|
||||||
await cls.update({"data.hitDiceUsed": cls.data.data.hitDiceUsed + 1});
|
await cls.update({"data.hitDiceUsed": cls.data.data.hitDiceUsed + 1});
|
||||||
const hp = this.data.data.attributes.hp;
|
const hp = this.data.data.attributes.hp;
|
||||||
const dhp = Math.min(hp.max - hp.value, roll.total);
|
const dhp = Math.min(hp.max + (hp.tempmax ?? 0) - hp.value, roll.total);
|
||||||
await this.update({"data.attributes.hp.value": hp.value + dhp});
|
await this.update({"data.attributes.hp.value": hp.value + dhp});
|
||||||
return roll;
|
return roll;
|
||||||
}
|
}
|
||||||
|
@ -1130,8 +1116,7 @@ export default class Actor5e extends Actor {
|
||||||
|
|
||||||
// Recover power slots
|
// Recover power slots
|
||||||
for ( let [k, v] of Object.entries(data.powers) ) {
|
for ( let [k, v] of Object.entries(data.powers) ) {
|
||||||
if ( !v.max && !v.override ) continue;
|
updateData[`data.powers.${k}.value`] = Number.isNumeric(v.override) ? v.override : (v.max ?? 0);
|
||||||
updateData[`data.powers.${k}.value`] = v.override || v.max;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recover pact slots.
|
// Recover pact slots.
|
||||||
|
@ -1238,10 +1223,10 @@ export default class Actor5e extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the original Actor data and the new source data
|
// Get the original Actor data and the new source data
|
||||||
const o = duplicate(this.data);
|
const o = duplicate(this.toJSON());
|
||||||
o.flags.sw5e = o.flags.sw5e || {};
|
o.flags.sw5e = o.flags.sw5e || {};
|
||||||
o.flags.sw5e.transformOptions = {mergeSkills, mergeSaves};
|
o.flags.sw5e.transformOptions = {mergeSkills, mergeSaves};
|
||||||
const source = duplicate(target.data);
|
const source = duplicate(target.toJSON());
|
||||||
|
|
||||||
// Prepare new data to merge from the source
|
// Prepare new data to merge from the source
|
||||||
const d = {
|
const d = {
|
||||||
|
@ -1249,6 +1234,7 @@ export default class Actor5e extends Actor {
|
||||||
name: `${o.name} (${source.name})`, // Append the new shape to your old name
|
name: `${o.name} (${source.name})`, // Append the new shape to your old name
|
||||||
data: source.data, // Get the data model of your new form
|
data: source.data, // Get the data model of your new form
|
||||||
items: source.items, // Get the items of your new form
|
items: source.items, // Get the items of your new form
|
||||||
|
effects: o.effects.concat(source.effects), // Combine active effects from both forms
|
||||||
token: source.token, // New token configuration
|
token: source.token, // New token configuration
|
||||||
img: source.img, // New appearance
|
img: source.img, // New appearance
|
||||||
permission: o.permission, // Use the original actor permissions
|
permission: o.permission, // Use the original actor permissions
|
||||||
|
@ -1271,7 +1257,7 @@ export default class Actor5e extends Actor {
|
||||||
// Handle wildcard
|
// Handle wildcard
|
||||||
if ( source.token.randomImg ) {
|
if ( source.token.randomImg ) {
|
||||||
const images = await target.getTokenImages();
|
const images = await target.getTokenImages();
|
||||||
d.token.img = images[0];
|
d.token.img = images[Math.floor(Math.random() * images.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep Token configurations
|
// Keep Token configurations
|
||||||
|
@ -1355,7 +1341,7 @@ export default class Actor5e extends Actor {
|
||||||
newTokenData.actorId = newActor.id;
|
newTokenData.actorId = newActor.id;
|
||||||
return newTokenData;
|
return newTokenData;
|
||||||
});
|
});
|
||||||
return canvas.scene.updateEmbeddedEntity("Token", updates);
|
return canvas.scene?.updateEmbeddedEntity("Token", updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -1437,4 +1423,18 @@ export default class Actor5e extends Actor {
|
||||||
console.warn(`The Actor5e#getPowerDC(ability) method has been deprecated in favor of Actor5e#data.data.abilities[ability].dc`);
|
console.warn(`The Actor5e#getPowerDC(ability) method has been deprecated in favor of Actor5e#data.data.abilities[ability].dc`);
|
||||||
return this.data.data.abilities[ability]?.dc;
|
return this.data.data.abilities[ability]?.dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast a Power, consuming a power slot of a certain level
|
||||||
|
* @param {Item5e} item The power being cast by the actor
|
||||||
|
* @param {Event} event The originating user interaction which triggered the cast
|
||||||
|
* @deprecated since sw5e 1.2.0
|
||||||
|
*/
|
||||||
|
async usePower(item, {configureDialog=true}={}) {
|
||||||
|
console.warn(`The Actor5e#usePower method has been deprecated in favor of Item5e#roll`);
|
||||||
|
if ( item.data.type !== "power" ) throw new Error("Wrong Item type");
|
||||||
|
return item.roll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
860
module/actor/sheets/newSheet/base.js
Normal file
|
@ -0,0 +1,860 @@
|
||||||
|
import Item5e from "../../../item/entity.js";
|
||||||
|
import TraitSelector from "../../../apps/trait-selector.js";
|
||||||
|
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||||
|
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||||
|
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||||
|
import {SW5E} from '../../../config.js';
|
||||||
|
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||||
|
* This sheet is an Abstract layer which is not used.
|
||||||
|
* @extends {ActorSheet}
|
||||||
|
*/
|
||||||
|
export default class ActorSheet5e extends ActorSheet {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track the set of item filters which are applied
|
||||||
|
* @type {Set}
|
||||||
|
*/
|
||||||
|
this._filters = {
|
||||||
|
inventory: new Set(),
|
||||||
|
powerbook: new Set(),
|
||||||
|
features: new Set(),
|
||||||
|
effects: new Set()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
scrollY: [
|
||||||
|
".inventory .group-list",
|
||||||
|
".features .group-list",
|
||||||
|
".powerbook .group-list",
|
||||||
|
".effects .effects-list"
|
||||||
|
],
|
||||||
|
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
get template() {
|
||||||
|
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html";
|
||||||
|
return `systems/sw5e/templates/actors/newActor/${this.actor.data.type}-sheet.html`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getData() {
|
||||||
|
|
||||||
|
// Basic data
|
||||||
|
let isOwner = this.entity.owner;
|
||||||
|
const data = {
|
||||||
|
owner: isOwner,
|
||||||
|
limited: this.entity.limited,
|
||||||
|
options: this.options,
|
||||||
|
editable: this.isEditable,
|
||||||
|
cssClass: isOwner ? "editable" : "locked",
|
||||||
|
isCharacter: this.entity.data.type === "character",
|
||||||
|
isNPC: this.entity.data.type === "npc",
|
||||||
|
isVehicle: this.entity.data.type === 'vehicle',
|
||||||
|
config: CONFIG.SW5E,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Actor and its Items
|
||||||
|
data.actor = duplicate(this.actor.data);
|
||||||
|
data.items = this.actor.items.map(i => {
|
||||||
|
i.data.labels = i.labels;
|
||||||
|
return i.data;
|
||||||
|
});
|
||||||
|
data.items.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||||
|
data.data = data.actor.data;
|
||||||
|
data.labels = this.actor.labels || {};
|
||||||
|
data.filters = this._filters;
|
||||||
|
|
||||||
|
// Ability Scores
|
||||||
|
for ( let [a, abl] of Object.entries(data.actor.data.abilities)) {
|
||||||
|
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||||
|
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||||
|
abl.label = CONFIG.SW5E.abilities[a];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skills
|
||||||
|
if (data.actor.data.skills) {
|
||||||
|
for ( let [s, skl] of Object.entries(data.actor.data.skills)) {
|
||||||
|
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||||
|
skl.icon = this._getProficiencyIcon(skl.value);
|
||||||
|
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||||
|
skl.label = CONFIG.SW5E.skills[s];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement speeds
|
||||||
|
data.movement = this._getMovementSpeed(data.actor);
|
||||||
|
|
||||||
|
// Senses
|
||||||
|
data.senses = this._getSenses(data.actor);
|
||||||
|
|
||||||
|
// Update traits
|
||||||
|
this._prepareTraits(data.actor.data.traits);
|
||||||
|
|
||||||
|
// Prepare owned items
|
||||||
|
this._prepareItems(data);
|
||||||
|
|
||||||
|
// Prepare active effects
|
||||||
|
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||||
|
|
||||||
|
// Return data to the sheet
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the display of movement speed data for the Actor*
|
||||||
|
* @param {object} actorData The Actor data being prepared.
|
||||||
|
* @param {boolean} [largestPrimary=false] Show the largest movement speed as "primary", otherwise show "walk"
|
||||||
|
* @returns {{primary: string, special: string}}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getMovementSpeed(actorData, largestPrimary=false) {
|
||||||
|
const movement = actorData.data.attributes.movement || {};
|
||||||
|
|
||||||
|
// Prepare an array of available movement speeds
|
||||||
|
let 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}`]
|
||||||
|
]
|
||||||
|
if ( largestPrimary ) {
|
||||||
|
speeds.push([movement.walk, `${game.i18n.localize("SW5E.MovementWalk")} ${movement.walk}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter and sort speeds on their values
|
||||||
|
speeds = speeds.filter(s => !!s[0]).sort((a, b) => b[0] - a[0]);
|
||||||
|
|
||||||
|
// Case 1: Largest as primary
|
||||||
|
if ( largestPrimary ) {
|
||||||
|
let primary = speeds.shift();
|
||||||
|
return {
|
||||||
|
primary: `${primary ? primary[1] : "0"} ${movement.units}`,
|
||||||
|
special: speeds.map(s => s[1]).join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Walk as primary
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
primary: `${movement.walk || 0} ${movement.units}`,
|
||||||
|
special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
_getSenses(actorData) {
|
||||||
|
const senses = actorData.data.attributes.senses || {};
|
||||||
|
const tags = {};
|
||||||
|
for ( let [k, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||||
|
const v = senses[k] ?? 0
|
||||||
|
if ( v === 0 ) continue;
|
||||||
|
tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`;
|
||||||
|
}
|
||||||
|
if ( !!senses.special ) tags["special"] = senses.special;
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
||||||
|
* @param {object} traits The raw traits data object from the actor data
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareTraits(traits) {
|
||||||
|
const map = {
|
||||||
|
"dr": CONFIG.SW5E.damageResistanceTypes,
|
||||||
|
"di": CONFIG.SW5E.damageResistanceTypes,
|
||||||
|
"dv": CONFIG.SW5E.damageResistanceTypes,
|
||||||
|
"ci": CONFIG.SW5E.conditionTypes,
|
||||||
|
"languages": CONFIG.SW5E.languages,
|
||||||
|
"armorProf": CONFIG.SW5E.armorProficiencies,
|
||||||
|
"weaponProf": CONFIG.SW5E.weaponProficiencies,
|
||||||
|
"toolProf": CONFIG.SW5E.toolProficiencies
|
||||||
|
};
|
||||||
|
for ( let [t, choices] of Object.entries(map) ) {
|
||||||
|
const trait = traits[t];
|
||||||
|
if ( !trait ) continue;
|
||||||
|
let values = [];
|
||||||
|
if ( trait.value ) {
|
||||||
|
values = trait.value instanceof Array ? trait.value : [trait.value];
|
||||||
|
}
|
||||||
|
trait.selected = values.reduce((obj, t) => {
|
||||||
|
obj[t] = choices[t];
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Add custom entry
|
||||||
|
if ( trait.custom ) {
|
||||||
|
trait.custom.split(";").forEach((c, i) => trait.selected[`custom${i+1}`] = c.trim());
|
||||||
|
}
|
||||||
|
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a power into the powerbook object when rendering the character sheet
|
||||||
|
* @param {Object} data The Actor data being prepared
|
||||||
|
* @param {Array} powers The power data being prepared
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_preparePowerbook(data, powers) {
|
||||||
|
const owner = this.actor.owner;
|
||||||
|
const levels = data.data.powers;
|
||||||
|
const powerbook = {};
|
||||||
|
|
||||||
|
// Define some mappings
|
||||||
|
const sections = {
|
||||||
|
"atwill": -20,
|
||||||
|
"innate": -10,
|
||||||
|
"pact": 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
// Label power slot uses headers
|
||||||
|
const useLabels = {
|
||||||
|
"-20": "-",
|
||||||
|
"-10": "-",
|
||||||
|
"0": "∞"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format a powerbook entry for a certain indexed level
|
||||||
|
const registerSection = (sl, i, label, {prepMode="prepared", value, max, override}={}) => {
|
||||||
|
powerbook[i] = {
|
||||||
|
order: i,
|
||||||
|
label: label,
|
||||||
|
usesSlots: i > 0,
|
||||||
|
canCreate: owner,
|
||||||
|
canPrepare: (data.actor.type === "character") && (i >= 1),
|
||||||
|
powers: [],
|
||||||
|
uses: useLabels[i] || value || 0,
|
||||||
|
slots: useLabels[i] || max || 0,
|
||||||
|
override: override || 0,
|
||||||
|
dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode},
|
||||||
|
prop: sl
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine the maximum power level which has a slot
|
||||||
|
const maxLevel = Array.fromRange(10).reduce((max, i) => {
|
||||||
|
if ( i === 0 ) return max;
|
||||||
|
const level = levels[`power${i}`];
|
||||||
|
if ( (level.max || level.override ) && ( i > max ) ) max = i;
|
||||||
|
return max;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Level-based powercasters have cantrips and leveled slots
|
||||||
|
if ( maxLevel > 0 ) {
|
||||||
|
registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
|
||||||
|
for (let lvl = 1; lvl <= maxLevel; lvl++) {
|
||||||
|
const sl = `power${lvl}`;
|
||||||
|
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]);
|
||||||
|
const l = levels.pact;
|
||||||
|
const config = CONFIG.SW5E.powerPreparationModes.pact;
|
||||||
|
registerSection("pact", sections.pact, config, {
|
||||||
|
prepMode: "pact",
|
||||||
|
value: l.value,
|
||||||
|
max: l.max,
|
||||||
|
override: l.override
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over every power item, adding powers to the powerbook by section
|
||||||
|
powers.forEach(power => {
|
||||||
|
const mode = power.data.preparation.mode || "prepared";
|
||||||
|
let s = power.data.level || 0;
|
||||||
|
const sl = `power${s}`;
|
||||||
|
|
||||||
|
// Specialized powercasting modes (if they exist)
|
||||||
|
if ( mode in sections ) {
|
||||||
|
s = sections[mode];
|
||||||
|
if ( !powerbook[s] ){
|
||||||
|
const l = levels[mode] || {};
|
||||||
|
const config = CONFIG.SW5E.powerPreparationModes[mode];
|
||||||
|
registerSection(mode, s, config, {
|
||||||
|
prepMode: mode,
|
||||||
|
value: l.value,
|
||||||
|
max: l.max,
|
||||||
|
override: l.override
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sections for higher-level powers which the caster "should not" have, but power items exist for
|
||||||
|
else if ( !powerbook[s] ) {
|
||||||
|
registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the power to the relevant heading
|
||||||
|
powerbook[s].powers.push(power);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort the powerbook by section level
|
||||||
|
const sorted = Object.values(powerbook);
|
||||||
|
sorted.sort((a, b) => a.order - b.order);
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether an Owned Item will be shown based on the current set of filters
|
||||||
|
* @return {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_filterItems(items, filters) {
|
||||||
|
return items.filter(item => {
|
||||||
|
const data = item.data;
|
||||||
|
|
||||||
|
// Action usage
|
||||||
|
for ( let f of ["action", "bonus", "reaction"] ) {
|
||||||
|
if ( filters.has(f) ) {
|
||||||
|
if ((data.activation && (data.activation.type !== f))) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power-specific filters
|
||||||
|
if ( filters.has("ritual") ) {
|
||||||
|
if (data.components.ritual !== true) return false;
|
||||||
|
}
|
||||||
|
if ( filters.has("concentration") ) {
|
||||||
|
if (data.components.concentration !== true) return false;
|
||||||
|
}
|
||||||
|
if ( filters.has("prepared") ) {
|
||||||
|
if ( data.level === 0 || ["innate", "always"].includes(data.preparation.mode) ) return true;
|
||||||
|
if ( this.actor.data.type === "npc" ) return true;
|
||||||
|
return data.preparation.prepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equipment-specific filters
|
||||||
|
if ( filters.has("equipped") ) {
|
||||||
|
if ( data.equipped !== true ) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font-awesome icon used to display a certain level of skill proficiency
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getProficiencyIcon(level) {
|
||||||
|
const icons = {
|
||||||
|
0: '<i class="far fa-circle"></i>',
|
||||||
|
0.5: '<i class="fas fa-adjust"></i>',
|
||||||
|
1: '<i class="fas fa-check"></i>',
|
||||||
|
2: '<i class="fas fa-check-double"></i>'
|
||||||
|
};
|
||||||
|
return icons[level] || icons[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* 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) {
|
||||||
|
|
||||||
|
// Activate Item Filters
|
||||||
|
const filterLists = html.find(".filter-list");
|
||||||
|
filterLists.each(this._initializeFilterItemList.bind(this));
|
||||||
|
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
|
||||||
|
|
||||||
|
// Item summaries
|
||||||
|
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
|
||||||
|
|
||||||
|
// Editable Only Listeners
|
||||||
|
if ( this.isEditable ) {
|
||||||
|
|
||||||
|
// Input focus and update
|
||||||
|
const inputs = html.find("input");
|
||||||
|
inputs.focus(ev => ev.currentTarget.select());
|
||||||
|
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
|
||||||
|
|
||||||
|
// Ability Proficiency
|
||||||
|
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this));
|
||||||
|
|
||||||
|
// Toggle Skill Proficiency
|
||||||
|
html.find('.skill-proficiency').on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
||||||
|
|
||||||
|
// Trait Selector
|
||||||
|
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
|
||||||
|
|
||||||
|
// Configure Special Flags
|
||||||
|
html.find('.config-button').click(this._onConfigMenu.bind(this));
|
||||||
|
|
||||||
|
// Owned Item management
|
||||||
|
html.find('.item-create').click(this._onItemCreate.bind(this));
|
||||||
|
html.find('.item-edit').click(this._onItemEdit.bind(this));
|
||||||
|
html.find('.item-delete').click(this._onItemDelete.bind(this));
|
||||||
|
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this));
|
||||||
|
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
|
||||||
|
|
||||||
|
// Active Effect management
|
||||||
|
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner Only Listeners
|
||||||
|
if ( this.actor.owner ) {
|
||||||
|
|
||||||
|
// Ability Checks
|
||||||
|
html.find('.ability-name').click(this._onRollAbilityTest.bind(this));
|
||||||
|
|
||||||
|
|
||||||
|
// Roll Skill Checks
|
||||||
|
html.find('.skill-name').click(this._onRollSkillCheck.bind(this));
|
||||||
|
|
||||||
|
// Item Rolling
|
||||||
|
html.find('.item .item-image').click(event => this._onItemRoll(event));
|
||||||
|
html.find('.item .item-recharge').click(event => this._onItemRecharge(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise remove rollable classes
|
||||||
|
else {
|
||||||
|
html.find(".rollable").each((i, el) => el.classList.remove("rollable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle default listeners last so system listeners are triggered first
|
||||||
|
super.activateListeners(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iinitialize Item list filters by activating the set of filters which are currently applied
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initializeFilterItemList(i, ul) {
|
||||||
|
const set = this._filters[ul.dataset.filter];
|
||||||
|
const filters = ul.querySelectorAll(".filter-item");
|
||||||
|
for ( let li of filters ) {
|
||||||
|
if ( set.has(li.dataset.filter) ) li.classList.add("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Event Listeners and Handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle input changes to numeric form fields, allowing them to accept delta-typed inputs
|
||||||
|
* @param event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onChangeInputDelta(event) {
|
||||||
|
const input = event.target;
|
||||||
|
const value = input.value;
|
||||||
|
if ( ["+", "-"].includes(value[0]) ) {
|
||||||
|
let delta = parseFloat(value);
|
||||||
|
input.value = getProperty(this.actor.data, input.name) + delta;
|
||||||
|
} else if ( value[0] === "=" ) {
|
||||||
|
input.value = value.slice(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||||
|
* @param {Event} event The click event which originated the selection
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onConfigMenu(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const button = event.currentTarget;
|
||||||
|
switch ( button.dataset.action ) {
|
||||||
|
case "movement":
|
||||||
|
new ActorMovementConfig(this.object).render(true);
|
||||||
|
break;
|
||||||
|
case "flags":
|
||||||
|
new ActorSheetFlags(this.object).render(true);
|
||||||
|
break;
|
||||||
|
case "senses":
|
||||||
|
new ActorSensesConfig(this.object).render(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle cycling proficiency in a Skill
|
||||||
|
* @param {Event} event A click or contextmenu event which triggered the handler
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onCycleSkillProficiency(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const field = $(event.currentTarget).siblings('input[type="hidden"]');
|
||||||
|
|
||||||
|
// Get the current level and the array of levels
|
||||||
|
const level = parseFloat(field.val());
|
||||||
|
const levels = [0, 1, 0.5, 2];
|
||||||
|
let idx = levels.indexOf(level);
|
||||||
|
|
||||||
|
// Toggle next level - forward on click, backwards on right
|
||||||
|
if ( event.type === "click" ) {
|
||||||
|
field.val(levels[(idx === levels.length - 1) ? 0 : idx + 1]);
|
||||||
|
} else if ( event.type === "contextmenu" ) {
|
||||||
|
field.val(levels[(idx === 0) ? levels.length - 1 : idx - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the field value and save the form
|
||||||
|
this._onSubmit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _onDropActor(event, data) {
|
||||||
|
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||||
|
if ( !canPolymorph ) return false;
|
||||||
|
|
||||||
|
// Get the target actor
|
||||||
|
let sourceActor = null;
|
||||||
|
if (data.pack) {
|
||||||
|
const pack = game.packs.find(p => p.collection === data.pack);
|
||||||
|
sourceActor = await pack.getEntity(data.id);
|
||||||
|
} else {
|
||||||
|
sourceActor = game.actors.get(data.id);
|
||||||
|
}
|
||||||
|
if ( !sourceActor ) return;
|
||||||
|
|
||||||
|
// Define a function to record polymorph settings for future use
|
||||||
|
const rememberOptions = html => {
|
||||||
|
const options = {};
|
||||||
|
html.find('input').each((i, el) => {
|
||||||
|
options[el.name] = el.checked;
|
||||||
|
});
|
||||||
|
const settings = mergeObject(game.settings.get('sw5e', 'polymorphSettings') || {}, options);
|
||||||
|
game.settings.set('sw5e', 'polymorphSettings', settings);
|
||||||
|
return settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and render the Dialog
|
||||||
|
return new Dialog({
|
||||||
|
title: game.i18n.localize('SW5E.PolymorphPromptTitle'),
|
||||||
|
content: {
|
||||||
|
options: game.settings.get('sw5e', 'polymorphSettings'),
|
||||||
|
i18n: SW5E.polymorphSettings,
|
||||||
|
isToken: this.actor.isToken
|
||||||
|
},
|
||||||
|
default: 'accept',
|
||||||
|
buttons: {
|
||||||
|
accept: {
|
||||||
|
icon: '<i class="fas fa-check"></i>',
|
||||||
|
label: game.i18n.localize('SW5E.PolymorphAcceptSettings'),
|
||||||
|
callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
|
||||||
|
},
|
||||||
|
wildshape: {
|
||||||
|
icon: '<i class="fas fa-paw"></i>',
|
||||||
|
label: game.i18n.localize('SW5E.PolymorphWildShape'),
|
||||||
|
callback: html => this.actor.transformInto(sourceActor, {
|
||||||
|
keepBio: true,
|
||||||
|
keepClass: true,
|
||||||
|
keepMental: true,
|
||||||
|
mergeSaves: true,
|
||||||
|
mergeSkills: true,
|
||||||
|
transformTokens: rememberOptions(html).transformTokens
|
||||||
|
})
|
||||||
|
},
|
||||||
|
polymorph: {
|
||||||
|
icon: '<i class="fas fa-pastafarianism"></i>',
|
||||||
|
label: game.i18n.localize('SW5E.Polymorph'),
|
||||||
|
callback: html => this.actor.transformInto(sourceActor, {
|
||||||
|
transformTokens: rememberOptions(html).transformTokens
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
icon: '<i class="fas fa-times"></i>',
|
||||||
|
label: game.i18n.localize('Cancel')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
classes: ['dialog', 'sw5e'],
|
||||||
|
width: 600,
|
||||||
|
template: 'systems/sw5e/templates/apps/polymorph-prompt.html'
|
||||||
|
}).render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _onDropItemCreate(itemData) {
|
||||||
|
|
||||||
|
// Create a Consumable power scroll on the Inventory tab
|
||||||
|
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) {
|
||||||
|
const scroll = await Item5e.createScrollFromPower(itemData);
|
||||||
|
itemData = scroll.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore certain statuses
|
||||||
|
if ( itemData.data ) {
|
||||||
|
["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the owned item as normal
|
||||||
|
return super._onDropItemCreate(itemData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle enabling editing for a power slot override value
|
||||||
|
* @param {MouseEvent} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onPowerSlotOverride (event) {
|
||||||
|
const span = event.currentTarget.parentElement;
|
||||||
|
const level = span.dataset.level;
|
||||||
|
const override = this.actor.data.data.powers[level].override || span.dataset.slots;
|
||||||
|
const input = document.createElement("INPUT");
|
||||||
|
input.type = "text";
|
||||||
|
input.name = `data.powers.${level}.override`;
|
||||||
|
input.value = override;
|
||||||
|
input.placeholder = span.dataset.slots;
|
||||||
|
input.dataset.dtype = "Number";
|
||||||
|
|
||||||
|
// Replace the HTML
|
||||||
|
const parent = span.parentElement;
|
||||||
|
parent.removeChild(span);
|
||||||
|
parent.appendChild(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the uses amount of an Owned Item within the Actor
|
||||||
|
* @param {Event} event The triggering click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onUsesChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||||
|
const item = this.actor.getOwnedItem(itemId);
|
||||||
|
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
|
||||||
|
event.target.value = uses;
|
||||||
|
return item.update({ 'data.uses.value': uses });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle rolling of an item from the Actor sheet, obtaining the Item instance and dispatching to it's roll method
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemRoll(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||||
|
const item = this.actor.getOwnedItem(itemId);
|
||||||
|
return item.roll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle attempting to recharge an item usage by rolling a recharge check
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemRecharge(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||||
|
const item = this.actor.getOwnedItem(itemId);
|
||||||
|
return item.rollRecharge();
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle rolling of an item from the Actor sheet, obtaining the Item instance and dispatching to it's roll method
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemSummary(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
let li = $(event.currentTarget).parents(".item"),
|
||||||
|
item = this.actor.getOwnedItem(li.data("item-id")),
|
||||||
|
chatData = item.getChatData({secrets: this.actor.owner});
|
||||||
|
|
||||||
|
// Toggle summary
|
||||||
|
if ( li.hasClass("expanded") ) {
|
||||||
|
let summary = li.children(".item-summary");
|
||||||
|
summary.slideUp(200, () => summary.remove());
|
||||||
|
} else {
|
||||||
|
let div = $(`<div class="item-summary">${chatData.description.value}</div>`);
|
||||||
|
let props = $(`<div class="item-properties"></div>`);
|
||||||
|
chatData.properties.forEach(p => props.append(`<span class="tag">${p}</span>`));
|
||||||
|
div.append(props);
|
||||||
|
li.append(div.hide());
|
||||||
|
div.slideDown(200);
|
||||||
|
}
|
||||||
|
li.toggleClass("expanded");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemCreate(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const header = event.currentTarget;
|
||||||
|
const type = header.dataset.type;
|
||||||
|
const itemData = {
|
||||||
|
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||||
|
type: type,
|
||||||
|
data: duplicate(header.dataset)
|
||||||
|
};
|
||||||
|
delete itemData.data["type"];
|
||||||
|
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle editing an existing Owned Item for the Actor
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemEdit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const li = event.currentTarget.closest(".item");
|
||||||
|
const item = this.actor.getOwnedItem(li.dataset.itemId);
|
||||||
|
item.sheet.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle deleting an existing Owned Item for the Actor
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemDelete(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const li = event.currentTarget.closest(".item");
|
||||||
|
this.actor.deleteOwnedItem(li.dataset.itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle rolling an Ability check, either a test or a saving throw
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onRollAbilityTest(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
let ability = event.currentTarget.parentElement.dataset.ability;
|
||||||
|
this.actor.rollAbility(ability, {event: event});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle rolling a Skill check
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onRollSkillCheck(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const skill = event.currentTarget.parentElement.dataset.skill;
|
||||||
|
this.actor.rollSkill(skill, {event: event});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggling Ability score proficiency level
|
||||||
|
* @param {Event} event The originating click event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onToggleAbilityProficiency(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const field = event.currentTarget.previousElementSibling;
|
||||||
|
this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggling of filters to display a different set of owned items
|
||||||
|
* @param {Event} event The click event which triggered the toggle
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onToggleFilter(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const li = event.currentTarget;
|
||||||
|
const set = this._filters[li.parentElement.dataset.filter];
|
||||||
|
const filter = li.dataset.filter;
|
||||||
|
if ( set.has(filter) ) set.delete(filter);
|
||||||
|
else set.add(filter);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||||
|
* @param {Event} event The click event which originated the selection
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onTraitSelector(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const a = event.currentTarget;
|
||||||
|
const label = a.parentElement.querySelector("label");
|
||||||
|
const choices = CONFIG.SW5E[a.dataset.options];
|
||||||
|
const options = { name: a.dataset.target, title: label.innerText, choices };
|
||||||
|
new TraitSelector(this.actor, options).render(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_getHeaderButtons() {
|
||||||
|
let buttons = super._getHeaderButtons();
|
||||||
|
|
||||||
|
// Add button to revert polymorph
|
||||||
|
if ( !this.actor.isPolymorphed || this.actor.isToken ) return buttons;
|
||||||
|
buttons.unshift({
|
||||||
|
label: 'SW5E.PolymorphRestoreTransformation',
|
||||||
|
class: "restore-transformation",
|
||||||
|
icon: "fas fa-backward",
|
||||||
|
onclick: ev => this.actor.revertOriginalForm()
|
||||||
|
});
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import ActorSheet5e from "../base.js";
|
import ActorSheet5e from "./base.js";
|
||||||
import Actor5e from "../../entity.js";
|
import Actor5e from "../../entity.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,6 +57,9 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
// Experience Tracking
|
// Experience Tracking
|
||||||
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
||||||
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
|
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
|
||||||
|
sheetData["multiclassLabels"] = this.actor.itemTypes.class.map(c => {
|
||||||
|
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(' ')
|
||||||
|
}).join(', ');
|
||||||
|
|
||||||
// Return data for rendering
|
// Return data for rendering
|
||||||
return sheetData;
|
return sheetData;
|
||||||
|
@ -79,13 +82,25 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} },
|
backpack: { label: "SW5E.ItemTypeContainerPl", items: [], dataset: {type: "backpack"} },
|
||||||
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
loot: { label: "SW5E.ItemTypeLootPl", items: [], dataset: {type: "loot"} }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Partition items by category
|
// Partition items by category
|
||||||
let [items, powers, feats, classes, species, 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 details
|
||||||
item.img = item.img || DEFAULT_TOKEN;
|
item.img = item.img || DEFAULT_TOKEN;
|
||||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||||
|
item.attunement = {
|
||||||
|
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
|
||||||
|
icon: "fa-sun",
|
||||||
|
cls: "not-attuned",
|
||||||
|
title: "SW5E.AttunementRequired"
|
||||||
|
},
|
||||||
|
[CONFIG.SW5E.attunementTypes.ATTUNED]: {
|
||||||
|
icon: "fa-sun",
|
||||||
|
cls: "attuned",
|
||||||
|
title: "SW5E.AttunementAttuned"
|
||||||
|
}
|
||||||
|
}[item.data.attunement];
|
||||||
|
|
||||||
// Item usage
|
// Item usage
|
||||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||||
|
@ -104,10 +119,12 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
else if ( item.type === "archetype" ) arr[5].push(item);
|
else if ( item.type === "archetype" ) arr[5].push(item);
|
||||||
else if ( item.type === "classfeature" ) arr[6].push(item);
|
else if ( item.type === "classfeature" ) arr[6].push(item);
|
||||||
else if ( item.type === "background" ) arr[7].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);
|
else if ( Object.keys(inventory).includes(item.type ) ) arr[0].push(item);
|
||||||
return arr;
|
return arr;
|
||||||
}, [[], [], [], [], [], [], [], [], []]);
|
}, [[], [], [], [], [], [], [], [], [], [], []]);
|
||||||
|
|
||||||
// Apply active item filters
|
// Apply active item filters
|
||||||
items = this._filterItems(items, this._filters.inventory);
|
items = this._filterItems(items, this._filters.inventory);
|
||||||
|
@ -131,11 +148,13 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
// Organize Features
|
// Organize Features
|
||||||
const features = {
|
const features = {
|
||||||
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
||||||
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: false, dataset: {type: "classfeature"}, isClassfeature: true },
|
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true },
|
||||||
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: 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 },
|
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true },
|
||||||
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: 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 },
|
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"} },
|
active: { label: "SW5E.FeatureActive", items: [], hasActions: true, dataset: {type: "feat", "activation.type": "action"} },
|
||||||
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
passive: { label: "SW5E.FeaturePassive", items: [], hasActions: false, dataset: {type: "feat"} }
|
||||||
};
|
};
|
||||||
|
@ -145,11 +164,13 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
}
|
}
|
||||||
classes.sort((a, b) => b.levels - a.levels);
|
classes.sort((a, b) => b.levels - a.levels);
|
||||||
features.classes.items = classes;
|
features.classes.items = classes;
|
||||||
features.classfeatures.items = classfeatures;
|
features.classfeatures.items = classfeatures;
|
||||||
features.archetype.items = archetypes;
|
features.archetype.items = archetypes;
|
||||||
features.species.items = species;
|
features.species.items = species;
|
||||||
features.background.items = backgrounds;
|
features.background.items = backgrounds;
|
||||||
features.lightsaberform.items = lightsaberforms;
|
features.fightingstyles.items = fightingstyles;
|
||||||
|
features.fightingmasteries.items = fightingmasteries;
|
||||||
|
features.lightsaberforms.items = lightsaberforms;
|
||||||
|
|
||||||
// Assign and return
|
// Assign and return
|
||||||
data.inventory = Object.values(inventory);
|
data.inventory = Object.values(inventory);
|
||||||
|
@ -195,7 +216,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
if ( !this.options.editable ) return;
|
if ( !this.options.editable ) return;
|
||||||
|
|
||||||
// Inventory Functions
|
// Inventory Functions
|
||||||
html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
|
// html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
|
||||||
|
|
||||||
// Item State Toggling
|
// Item State Toggling
|
||||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||||
|
@ -204,8 +225,8 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
html.find('.short-rest').click(this._onShortRest.bind(this));
|
html.find('.short-rest').click(this._onShortRest.bind(this));
|
||||||
html.find('.long-rest').click(this._onLongRest.bind(this));
|
html.find('.long-rest').click(this._onLongRest.bind(this));
|
||||||
|
|
||||||
// Death saving throws
|
// Rollable sheet actions
|
||||||
html.find('.death-save').click(this._onDeathSave.bind(this));
|
html.find(".rollable[data-action]").click(this._onSheetAction.bind(this));
|
||||||
|
|
||||||
// Send Languages to Chat onClick
|
// Send Languages to Chat onClick
|
||||||
html.find('[data-options="share-languages"]').click(event => {
|
html.find('[data-options="share-languages"]').click(event => {
|
||||||
|
@ -271,13 +292,19 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle rolling a death saving throw for the Character
|
* Handle mouse click events for character sheet actions
|
||||||
* @param {MouseEvent} event The originating click event
|
* @param {MouseEvent} event The originating click event
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onDeathSave(event) {
|
_onSheetAction(event) {
|
||||||
event.preventDefault();
|
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});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -324,57 +351,26 @@ export default class ActorSheet5eCharacterNew 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 */
|
/** @override */
|
||||||
async _onDropItemCreate(itemData) {
|
async _onDropItemCreate(itemData) {
|
||||||
|
|
||||||
// Upgrade the number of class levels a character has and add features
|
// Increment the number of class levels a character instead of creating a new item
|
||||||
if ( itemData.type === "class" ) {
|
if ( itemData.type === "class" ) {
|
||||||
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||||
const classWasAlreadyPresent = !!cls;
|
let priorLevel = cls?.data.data.levels ?? 0;
|
||||||
|
if ( !!cls ) {
|
||||||
// Add new features for class level
|
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
|
||||||
if ( !classWasAlreadyPresent ) {
|
if ( next > priorLevel ) {
|
||||||
Actor5e.getClassFeatures(itemData).then(features => {
|
itemData.levels = next;
|
||||||
this.actor.createEmbeddedEntity("OwnedItem", features);
|
return cls.update({"data.levels": next});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default drop handling if levels were not added
|
||||||
super._onDropItemCreate(itemData);
|
super._onDropItemCreate(itemData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addFavorites(app, html, data) {
|
async function addFavorites(app, html, data) {
|
||||||
// Thisfunction is adapted for the SwaltSheet from the Favorites Item
|
// Thisfunction is adapted for the SwaltSheet from the Favorites Item
|
||||||
// Tab Module created for Foundry VTT - by Felix Müller (Felix#6196 on Discord).
|
// Tab Module created for Foundry VTT - by Felix Müller (Felix#6196 on Discord).
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ActorSheet5e from "../base.js";
|
import ActorSheet5e from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Actor sheet for NPC type characters in the SW5E system.
|
* An Actor sheet for NPC type characters in the SW5E system.
|
||||||
|
@ -16,7 +16,6 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
return mergeObject(super.defaultOptions, {
|
return mergeObject(super.defaultOptions, {
|
||||||
classes: ["sw5e", "sheet", "actor", "npc"],
|
classes: ["sw5e", "sheet", "actor", "npc"],
|
||||||
width: 600,
|
|
||||||
width: 800,
|
width: 800,
|
||||||
tabs: [{
|
tabs: [{
|
||||||
navSelector: ".root-tabs",
|
navSelector: ".root-tabs",
|
||||||
|
@ -116,7 +115,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
||||||
/** @override */
|
/** @override */
|
||||||
activateListeners(html) {
|
activateListeners(html) {
|
||||||
super.activateListeners(html);
|
super.activateListeners(html);
|
||||||
html.find(".health .rollable").click(this._onRollHealthFormula.bind(this));
|
html.find(".health .rollable").click(this._onRollHPFormula.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -126,7 +125,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
||||||
* @param {Event} event The original click event
|
* @param {Event} event The original click event
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onRollHealthFormula(event) {
|
_onRollHPFormula(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const formula = this.actor.data.data.attributes.hp.formula;
|
const formula = this.actor.data.data.attributes.hp.formula;
|
||||||
if ( !formula ) return;
|
if ( !formula ) return;
|
||||||
|
@ -135,3 +134,4 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
||||||
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
385
module/actor/sheets/newSheet/vehicle.js
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
import ActorSheet5e from "./base.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Actor sheet for Vehicle type actors.
|
||||||
|
* Extends the base ActorSheet5e class.
|
||||||
|
* @type {ActorSheet5e}
|
||||||
|
*/
|
||||||
|
export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
/**
|
||||||
|
* Define default rendering options for the Vehicle sheet.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||||
|
width: 605,
|
||||||
|
height: 680
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new cargo entry for a vehicle Actor.
|
||||||
|
*/
|
||||||
|
static get newCargo() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
quantity: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the total weight of the vehicle's cargo.
|
||||||
|
* @param {Number} totalWeight The cumulative item weight from inventory items
|
||||||
|
* @param {Object} actorData The data object for the Actor being rendered
|
||||||
|
* @returns {{max: number, value: number, pct: number}}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_computeEncumbrance(totalWeight, actorData) {
|
||||||
|
|
||||||
|
// Compute currency weight
|
||||||
|
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
||||||
|
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||||
|
|
||||||
|
// Vehicle weights are an order of magnitude greater.
|
||||||
|
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
||||||
|
|
||||||
|
// Compute overall encumbrance
|
||||||
|
const max = actorData.data.attributes.capacity.cargo;
|
||||||
|
const pct = Math.clamped((totalWeight * 100) / max, 0, 100);
|
||||||
|
return {value: totalWeight.toNearest(0.1), max, pct};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_getMovementSpeed(actorData, largestPrimary=true) {
|
||||||
|
return super._getMovementSpeed(actorData, largestPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare items that are mounted to a vehicle and require one or more crew
|
||||||
|
* to operate.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareCrewedItem(item) {
|
||||||
|
|
||||||
|
// Determine crewed status
|
||||||
|
const isCrewed = item.data.crewed;
|
||||||
|
item.toggleClass = isCrewed ? 'active' : '';
|
||||||
|
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`);
|
||||||
|
|
||||||
|
// Handle crew actions
|
||||||
|
if (item.type === 'feat' && item.data.activation.type === 'crew') {
|
||||||
|
item.crew = item.data.activation.cost;
|
||||||
|
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`);
|
||||||
|
if (item.data.cover === .5) item.cover = '½';
|
||||||
|
else if (item.data.cover === .75) item.cover = '¾';
|
||||||
|
else if (item.data.cover === null) item.cover = '—';
|
||||||
|
if (item.crew < 1 || item.crew === null) item.crew = '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare vehicle weapons
|
||||||
|
if (item.type === 'equipment' || item.type === 'weapon') {
|
||||||
|
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organize Owned Items for rendering the Vehicle sheet.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_prepareItems(data) {
|
||||||
|
const cargoColumns = [{
|
||||||
|
label: game.i18n.localize('SW5E.Quantity'),
|
||||||
|
css: 'item-qty',
|
||||||
|
property: 'quantity',
|
||||||
|
editable: 'Number'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const equipmentColumns = [{
|
||||||
|
label: game.i18n.localize('SW5E.Quantity'),
|
||||||
|
css: 'item-qty',
|
||||||
|
property: 'data.quantity'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.AC'),
|
||||||
|
css: 'item-ac',
|
||||||
|
property: 'data.armor.value'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.HP'),
|
||||||
|
css: 'item-hp',
|
||||||
|
property: 'data.hp.value',
|
||||||
|
editable: 'Number'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Threshold'),
|
||||||
|
css: 'item-threshold',
|
||||||
|
property: 'threshold'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const features = {
|
||||||
|
actions: {
|
||||||
|
label: game.i18n.localize('SW5E.ActionPl'),
|
||||||
|
items: [],
|
||||||
|
crewable: true,
|
||||||
|
dataset: {type: 'feat', 'activation.type': 'crew'},
|
||||||
|
columns: [{
|
||||||
|
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||||
|
css: 'item-crew',
|
||||||
|
property: 'crew'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Cover'),
|
||||||
|
css: 'item-cover',
|
||||||
|
property: 'cover'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
equipment: {
|
||||||
|
label: game.i18n.localize('SW5E.ItemTypeEquipment'),
|
||||||
|
items: [],
|
||||||
|
crewable: true,
|
||||||
|
dataset: {type: 'equipment', 'armor.type': 'vehicle'},
|
||||||
|
columns: equipmentColumns
|
||||||
|
},
|
||||||
|
passive: {
|
||||||
|
label: game.i18n.localize('SW5E.Features'),
|
||||||
|
items: [],
|
||||||
|
dataset: {type: 'feat'}
|
||||||
|
},
|
||||||
|
reactions: {
|
||||||
|
label: game.i18n.localize('SW5E.ReactionPl'),
|
||||||
|
items: [],
|
||||||
|
dataset: {type: 'feat', 'activation.type': 'reaction'}
|
||||||
|
},
|
||||||
|
weapons: {
|
||||||
|
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'),
|
||||||
|
items: [],
|
||||||
|
crewable: true,
|
||||||
|
dataset: {type: 'weapon', 'weapon-type': 'siege'},
|
||||||
|
columns: equipmentColumns
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cargo = {
|
||||||
|
crew: {
|
||||||
|
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||||
|
items: data.data.cargo.crew,
|
||||||
|
css: 'cargo-row crew',
|
||||||
|
editableName: true,
|
||||||
|
dataset: {type: 'crew'},
|
||||||
|
columns: cargoColumns
|
||||||
|
},
|
||||||
|
passengers: {
|
||||||
|
label: game.i18n.localize('SW5E.VehiclePassengers'),
|
||||||
|
items: data.data.cargo.passengers,
|
||||||
|
css: 'cargo-row passengers',
|
||||||
|
editableName: true,
|
||||||
|
dataset: {type: 'passengers'},
|
||||||
|
columns: cargoColumns
|
||||||
|
},
|
||||||
|
cargo: {
|
||||||
|
label: game.i18n.localize('SW5E.VehicleCargo'),
|
||||||
|
items: [],
|
||||||
|
dataset: {type: 'loot'},
|
||||||
|
columns: [{
|
||||||
|
label: game.i18n.localize('SW5E.Quantity'),
|
||||||
|
css: 'item-qty',
|
||||||
|
property: 'data.quantity',
|
||||||
|
editable: 'Number'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Price'),
|
||||||
|
css: 'item-price',
|
||||||
|
property: 'data.price',
|
||||||
|
editable: 'Number'
|
||||||
|
}, {
|
||||||
|
label: game.i18n.localize('SW5E.Weight'),
|
||||||
|
css: 'item-weight',
|
||||||
|
property: 'data.weight',
|
||||||
|
editable: 'Number'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let totalWeight = 0;
|
||||||
|
for (const item of data.items) {
|
||||||
|
this._prepareCrewedItem(item);
|
||||||
|
if (item.type === 'weapon') features.weapons.items.push(item);
|
||||||
|
else if (item.type === 'equipment') features.equipment.items.push(item);
|
||||||
|
else if (item.type === 'loot') {
|
||||||
|
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||||
|
cargo.cargo.items.push(item);
|
||||||
|
}
|
||||||
|
else if (item.type === 'feat') {
|
||||||
|
if (!item.data.activation.type || item.data.activation.type === 'none') {
|
||||||
|
features.passive.items.push(item);
|
||||||
|
}
|
||||||
|
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
|
||||||
|
else features.actions.items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.features = Object.values(features);
|
||||||
|
data.cargo = Object.values(cargo);
|
||||||
|
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Event Listeners and Handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
if (!this.options.editable) return;
|
||||||
|
|
||||||
|
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||||
|
html.find('.item-hp input')
|
||||||
|
.click(evt => evt.target.select())
|
||||||
|
.change(this._onHPChange.bind(this));
|
||||||
|
|
||||||
|
html.find('.item:not(.cargo-row) input[data-property]')
|
||||||
|
.click(evt => evt.target.select())
|
||||||
|
.change(this._onEditInSheet.bind(this));
|
||||||
|
|
||||||
|
html.find('.cargo-row input')
|
||||||
|
.click(evt => evt.target.select())
|
||||||
|
.change(this._onCargoRowChange.bind(this));
|
||||||
|
|
||||||
|
if (this.actor.data.data.attributes.actions.stations) {
|
||||||
|
html.find('.counter.actions, .counter.action-thresholds').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle saving a cargo row (i.e. crew or passenger) in-sheet.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Actor>|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onCargoRowChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const row = target.closest('.item');
|
||||||
|
const idx = Number(row.dataset.itemId);
|
||||||
|
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||||
|
|
||||||
|
// Get the cargo entry
|
||||||
|
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||||
|
const entry = cargo[idx];
|
||||||
|
if (!entry) return null;
|
||||||
|
|
||||||
|
// Update the cargo value
|
||||||
|
const key = target.dataset.property || 'name';
|
||||||
|
const type = target.dataset.dtype;
|
||||||
|
let value = target.value;
|
||||||
|
if (type === 'Number') value = Number(value);
|
||||||
|
entry[key] = value;
|
||||||
|
|
||||||
|
// Perform the Actor update
|
||||||
|
return this.actor.update({[`data.cargo.${property}`]: cargo});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle editing certain values like quantity, price, and weight in-sheet.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onEditInSheet(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const property = event.currentTarget.dataset.property;
|
||||||
|
const type = event.currentTarget.dataset.dtype;
|
||||||
|
let value = event.currentTarget.value;
|
||||||
|
switch (type) {
|
||||||
|
case 'Number': value = parseInt(value); break;
|
||||||
|
case 'Boolean': value = value === 'true'; break;
|
||||||
|
}
|
||||||
|
return item.update({[`${property}`]: value});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle creating a new crew or passenger row.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Actor|Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemCreate(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const type = target.dataset.type;
|
||||||
|
if (type === 'crew' || type === 'passengers') {
|
||||||
|
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||||
|
cargo.push(this.constructor.newCargo);
|
||||||
|
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||||
|
}
|
||||||
|
return super._onItemCreate(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle deleting a crew or passenger row.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Actor|Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onItemDelete(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const row = event.currentTarget.closest('.item');
|
||||||
|
if (row.classList.contains('cargo-row')) {
|
||||||
|
const idx = Number(row.dataset.itemId);
|
||||||
|
const type = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||||
|
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||||
|
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._onItemDelete(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special handling for editing HP to clamp it within appropriate range.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onHPChange(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
|
||||||
|
event.currentTarget.value = hp;
|
||||||
|
return item.update({'data.hp.value': hp});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggling an item's crewed status.
|
||||||
|
* @param event {Event}
|
||||||
|
* @returns {Promise<Item>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onToggleItem(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemID);
|
||||||
|
const crewed = !!item.data.data.crewed;
|
||||||
|
return item.update({'data.crewed': !crewed});
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,9 +1,10 @@
|
||||||
import Item5e from "../../item/entity.js";
|
import Item5e from "../../../item/entity.js";
|
||||||
import TraitSelector from "../../apps/trait-selector.js";
|
import TraitSelector from "../../../apps/trait-selector.js";
|
||||||
import ActorSheetFlags from "../../apps/actor-flags.js";
|
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||||
import MovementConfig from "../../apps/movement-config.js";
|
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||||
import {SW5E} from '../../config.js';
|
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../effects.js";
|
import {SW5E} from '../../../config.js';
|
||||||
|
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||||
|
@ -99,6 +100,9 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
// Movement speeds
|
// Movement speeds
|
||||||
data.movement = this._getMovementSpeed(data.actor);
|
data.movement = this._getMovementSpeed(data.actor);
|
||||||
|
|
||||||
|
// Senses
|
||||||
|
data.senses = this._getSenses(data.actor);
|
||||||
|
|
||||||
// Update traits
|
// Update traits
|
||||||
this._prepareTraits(data.actor.data.traits);
|
this._prepareTraits(data.actor.data.traits);
|
||||||
|
|
||||||
|
@ -115,23 +119,59 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the display of movement speed data for the Actor
|
* Prepare the display of movement speed data for the Actor*
|
||||||
* @param {object} actorData
|
* @param {object} actorData The Actor data being prepared.
|
||||||
|
* @param {boolean} [largestPrimary=false] Show the largest movement speed as "primary", otherwise show "walk"
|
||||||
* @returns {{primary: string, special: string}}
|
* @returns {{primary: string, special: string}}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_getMovementSpeed(actorData) {
|
_getMovementSpeed(actorData, largestPrimary=false) {
|
||||||
const movement = actorData.data.attributes.movement;
|
const movement = actorData.data.attributes.movement || {};
|
||||||
const speeds = [
|
|
||||||
|
// Prepare an array of available movement speeds
|
||||||
|
let speeds = [
|
||||||
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
|
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
|
||||||
[movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`],
|
[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.fly, `${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` + (movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")],
|
||||||
[movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
|
[movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
|
||||||
].filter(s => !!s[0]).sort((a, b) => b[0] - a[0]);
|
]
|
||||||
return {
|
if ( largestPrimary ) {
|
||||||
primary: `${movement.walk || 0} ${movement.units}`,
|
speeds.push([movement.walk, `${game.i18n.localize("SW5E.MovementWalk")} ${movement.walk}`]);
|
||||||
special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter and sort speeds on their values
|
||||||
|
speeds = speeds.filter(s => !!s[0]).sort((a, b) => b[0] - a[0]);
|
||||||
|
|
||||||
|
// Case 1: Largest as primary
|
||||||
|
if ( largestPrimary ) {
|
||||||
|
let primary = speeds.shift();
|
||||||
|
return {
|
||||||
|
primary: `${primary ? primary[1] : "0"} ${movement.units}`,
|
||||||
|
special: speeds.map(s => s[1]).join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Walk as primary
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
primary: `${movement.walk || 0} ${movement.units}`,
|
||||||
|
special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
_getSenses(actorData) {
|
||||||
|
const senses = actorData.data.attributes.senses || {};
|
||||||
|
const tags = {};
|
||||||
|
for ( let [k, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||||
|
const v = senses[k] ?? 0
|
||||||
|
if ( v === 0 ) continue;
|
||||||
|
tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`;
|
||||||
|
}
|
||||||
|
if ( !!senses.special ) tags["special"] = senses.special;
|
||||||
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -334,7 +374,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
1: '<i class="fas fa-check"></i>',
|
1: '<i class="fas fa-check"></i>',
|
||||||
2: '<i class="fas fa-check-double"></i>'
|
2: '<i class="fas fa-check-double"></i>'
|
||||||
};
|
};
|
||||||
return icons[level];
|
return icons[level] || icons[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -373,8 +413,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
|
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
|
||||||
|
|
||||||
// Configure Special Flags
|
// Configure Special Flags
|
||||||
html.find('.configure-movement').click(this._onMovementConfig.bind(this));
|
html.find('.config-button').click(this._onConfigMenu.bind(this));
|
||||||
html.find('.configure-flags').click(this._onConfigureFlags.bind(this));
|
|
||||||
|
|
||||||
// Owned Item management
|
// Owned Item management
|
||||||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
html.find('.item-create').click(this._onItemCreate.bind(this));
|
||||||
|
@ -448,11 +487,24 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle click events for the Traits tab button to configure special Character Flags
|
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||||
|
* @param {Event} event The click event which originated the selection
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
_onConfigureFlags(event) {
|
_onConfigMenu(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
new ActorSheetFlags(this.actor).render(true);
|
const button = event.currentTarget;
|
||||||
|
switch ( button.dataset.action ) {
|
||||||
|
case "movement":
|
||||||
|
new ActorMovementConfig(this.object).render(true);
|
||||||
|
break;
|
||||||
|
case "flags":
|
||||||
|
new ActorSheetFlags(this.object).render(true);
|
||||||
|
break;
|
||||||
|
case "senses":
|
||||||
|
new ActorSensesConfig(this.object).render(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -529,6 +581,8 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
icon: '<i class="fas fa-paw"></i>',
|
icon: '<i class="fas fa-paw"></i>',
|
||||||
label: game.i18n.localize('SW5E.PolymorphWildShape'),
|
label: game.i18n.localize('SW5E.PolymorphWildShape'),
|
||||||
callback: html => this.actor.transformInto(sourceActor, {
|
callback: html => this.actor.transformInto(sourceActor, {
|
||||||
|
keepBio: true,
|
||||||
|
keepClass: true,
|
||||||
keepMental: true,
|
keepMental: true,
|
||||||
mergeSaves: true,
|
mergeSaves: true,
|
||||||
mergeSkills: true,
|
mergeSkills: true,
|
||||||
|
@ -565,6 +619,11 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
itemData = scroll.data;
|
itemData = scroll.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore certain statuses
|
||||||
|
if ( itemData.data ) {
|
||||||
|
["attunement", "equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||||
|
}
|
||||||
|
|
||||||
// Create the owned item as normal
|
// Create the owned item as normal
|
||||||
return super._onDropItemCreate(itemData);
|
return super._onDropItemCreate(itemData);
|
||||||
}
|
}
|
||||||
|
@ -619,14 +678,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||||
const item = this.actor.getOwnedItem(itemId);
|
const item = this.actor.getOwnedItem(itemId);
|
||||||
|
return item.roll();
|
||||||
// Roll powers through the actor
|
|
||||||
if ( item.data.type === "power" ) {
|
|
||||||
return this.actor.usePower(item, {configureDialog: !event.shiftKey});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise roll the Item directly
|
|
||||||
else return item.roll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -687,7 +739,7 @@ export default class ActorSheet5e extends ActorSheet {
|
||||||
data: duplicate(header.dataset)
|
data: duplicate(header.dataset)
|
||||||
};
|
};
|
||||||
delete itemData.data["type"];
|
delete itemData.data["type"];
|
||||||
return this.actor.createOwnedItem(itemData);
|
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -791,18 +843,6 @@ 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 */
|
/** @override */
|
||||||
_getHeaderButtons() {
|
_getHeaderButtons() {
|
||||||
let buttons = super._getHeaderButtons();
|
let buttons = super._getHeaderButtons();
|
|
@ -1,4 +1,4 @@
|
||||||
import ActorSheet5e from "../base.js";
|
import ActorSheet5e from "./base.js";
|
||||||
import Actor5e from "../../entity.js";
|
import Actor5e from "../../entity.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,6 +46,9 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
// Experience Tracking
|
// Experience Tracking
|
||||||
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
sheetData["disableExperience"] = game.settings.get("sw5e", "disableExperienceTracking");
|
||||||
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
|
sheetData["classLabels"] = this.actor.itemTypes.class.map(c => c.name).join(", ");
|
||||||
|
sheetData["multiclassLabels"] = this.actor.itemTypes.class.map(c => {
|
||||||
|
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(' ')
|
||||||
|
}).join(', ');
|
||||||
|
|
||||||
// Return data for rendering
|
// Return data for rendering
|
||||||
return sheetData;
|
return sheetData;
|
||||||
|
@ -75,6 +78,18 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
// Item details
|
// Item details
|
||||||
item.img = item.img || DEFAULT_TOKEN;
|
item.img = item.img || DEFAULT_TOKEN;
|
||||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||||
|
item.attunement = {
|
||||||
|
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
|
||||||
|
icon: "fa-sun",
|
||||||
|
cls: "not-attuned",
|
||||||
|
title: "SW5E.AttunementRequired"
|
||||||
|
},
|
||||||
|
[CONFIG.SW5E.attunementTypes.ATTUNED]: {
|
||||||
|
icon: "fa-sun",
|
||||||
|
cls: "attuned",
|
||||||
|
title: "SW5E.AttunementAttuned"
|
||||||
|
}
|
||||||
|
}[item.data.attunement];
|
||||||
|
|
||||||
// Item usage
|
// Item usage
|
||||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||||
|
@ -122,7 +137,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
// Organize Features
|
// Organize Features
|
||||||
const features = {
|
const features = {
|
||||||
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
classes: { label: "SW5E.ItemTypeClassPl", items: [], hasActions: false, dataset: {type: "class"}, isClass: true },
|
||||||
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: false, dataset: {type: "classfeature"}, isClassfeature: true },
|
classfeatures: { label: "SW5E.ItemTypeClassFeats", items: [], hasActions: true, dataset: {type: "classfeature"}, isClassfeature: true },
|
||||||
archetype: { label: "SW5E.ItemTypeArchetype", items: [], hasActions: false, dataset: {type: "archetype"}, isArchetype: 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 },
|
species: { label: "SW5E.ItemTypeSpecies", items: [], hasActions: false, dataset: {type: "species"}, isSpecies: true },
|
||||||
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true },
|
background: { label: "SW5E.ItemTypeBackground", items: [], hasActions: false, dataset: {type: "background"}, isBackground: true },
|
||||||
|
@ -203,7 +218,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle rolling a death saving throw for the Character
|
* Handle mouse click events for character sheet actions
|
||||||
* @param {MouseEvent} event The originating click event
|
* @param {MouseEvent} event The originating click event
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -263,37 +278,21 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _onDropItemCreate(itemData) {
|
async _onDropItemCreate(itemData) {
|
||||||
let addLevel = false;
|
|
||||||
|
|
||||||
// Upgrade the number of class levels a character has and add features
|
// Increment the number of class levels a character instead of creating a new item
|
||||||
if ( itemData.type === "class" ) {
|
if ( itemData.type === "class" ) {
|
||||||
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||||
let priorLevel = cls?.data.data.levels ?? 0;
|
let priorLevel = cls?.data.data.levels ?? 0;
|
||||||
const hasClass = !!cls;
|
if ( !!cls ) {
|
||||||
|
|
||||||
// Increment levels instead of creating a new item
|
|
||||||
if ( hasClass ) {
|
|
||||||
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
|
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
|
||||||
if ( next > priorLevel ) {
|
if ( next > priorLevel ) {
|
||||||
itemData.levels = next;
|
itemData.levels = next;
|
||||||
await cls.update({"data.levels": next});
|
return cls.update({"data.levels": next});
|
||||||
addLevel = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default drop handling if levels were not added
|
// Default drop handling if levels were not added
|
||||||
if ( !addLevel ) super._onDropItemCreate(itemData);
|
super._onDropItemCreate(itemData);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import ActorSheet5e from "../base.js";
|
import ActorSheet5e from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Actor sheet for NPC type characters in the SW5E system.
|
* An Actor sheet for NPC type characters in the SW5E system.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ActorSheet5e from "../base.js";
|
import ActorSheet5e from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Actor sheet for Vehicle type actors.
|
* An Actor sheet for Vehicle type actors.
|
||||||
|
@ -56,6 +56,13 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_getMovementSpeed(actorData, largestPrimary=true) {
|
||||||
|
return super._getMovementSpeed(actorData, largestPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare items that are mounted to a vehicle and require one or more crew
|
* Prepare items that are mounted to a vehicle and require one or more crew
|
||||||
* to operate.
|
* to operate.
|
||||||
|
@ -86,13 +93,6 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
|
||||||
_getMovementSpeed(actorData) {
|
|
||||||
return {primary: "", special: ""};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organize Owned Items for rendering the Vehicle sheet.
|
* Organize Owned Items for rendering the Vehicle sheet.
|
||||||
* @private
|
* @private
|
||||||
|
|
|
@ -34,15 +34,19 @@ export default class AbilityUseDialog extends Dialog {
|
||||||
const quantity = itemData.quantity || 0;
|
const quantity = itemData.quantity || 0;
|
||||||
const recharge = itemData.recharge || {};
|
const recharge = itemData.recharge || {};
|
||||||
const recharges = !!recharge.value;
|
const recharges = !!recharge.value;
|
||||||
|
const sufficientUses = (quantity > 0 && !uses.value) || uses.value > 0;
|
||||||
|
|
||||||
// Prepare dialog form data
|
// Prepare dialog form data
|
||||||
const data = {
|
const data = {
|
||||||
item: item.data,
|
item: item.data,
|
||||||
title: game.i18n.format("SW5E.AbilityUseHint", item.data),
|
title: game.i18n.format("SW5E.AbilityUseHint", item.data),
|
||||||
note: this._getAbilityUseNote(item.data, uses, recharge),
|
note: this._getAbilityUseNote(item.data, uses, recharge),
|
||||||
hasLimitedUses: uses.max || recharges,
|
consumePowerSlot: false,
|
||||||
canUse: recharges ? recharge.charged : (quantity > 0 && !uses.value) || uses.value > 0,
|
consumeRecharge: recharges,
|
||||||
hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
consumeResource: !!itemData.consume.target,
|
||||||
|
consumeUses: uses.max,
|
||||||
|
canUse: recharges ? recharge.charged : sufficientUses,
|
||||||
|
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
if ( item.data.type === "power" ) this._getPowerData(actorData, itemData, data);
|
if ( item.data.type === "power" ) this._getPowerData(actorData, itemData, data);
|
||||||
|
@ -50,7 +54,7 @@ export default class AbilityUseDialog extends Dialog {
|
||||||
// Render the ability usage template
|
// Render the ability usage template
|
||||||
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
|
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
|
||||||
|
|
||||||
// Create the Dialog and return as a Promise
|
// Create the Dialog and return data as a Promise
|
||||||
const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
|
const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
|
||||||
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
|
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
@ -61,7 +65,10 @@ export default class AbilityUseDialog extends Dialog {
|
||||||
use: {
|
use: {
|
||||||
icon: `<i class="fas ${icon}"></i>`,
|
icon: `<i class="fas ${icon}"></i>`,
|
||||||
label: label,
|
label: label,
|
||||||
callback: html => resolve(new FormData(html[0].querySelector("form")))
|
callback: html => {
|
||||||
|
const fd = new FormDataExtended(html[0].querySelector("form"));
|
||||||
|
resolve(fd.toObject());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
default: "use",
|
default: "use",
|
||||||
|
@ -83,11 +90,11 @@ export default class AbilityUseDialog extends Dialog {
|
||||||
|
|
||||||
// Determine whether the power may be up-cast
|
// Determine whether the power may be up-cast
|
||||||
const lvl = itemData.level;
|
const lvl = itemData.level;
|
||||||
const canUpcast = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
const consumePowerSlot = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||||
|
|
||||||
// If can't upcast, return early and don't bother calculating available power slots
|
// If can't upcast, return early and don't bother calculating available power slots
|
||||||
if (!canUpcast) {
|
if (!consumePowerSlot) {
|
||||||
data = mergeObject(data, { isPower: true, canUpcast });
|
mergeObject(data, { isPower: true, consumePowerSlot });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,10 +127,13 @@ export default class AbilityUseDialog extends Dialog {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const canCast = powerLevels.some(l => l.hasSlots);
|
const canCast = powerLevels.some(l => l.hasSlots);
|
||||||
|
if ( !canCast ) data.errors.push(game.i18n.format("SW5E.PowerCastNoSlots", {
|
||||||
|
level: CONFIG.SW5E.powerLevels[lvl],
|
||||||
|
name: data.item.name
|
||||||
|
}));
|
||||||
|
|
||||||
// Return merged data
|
// Merge power casting data
|
||||||
data = mergeObject(data, { isPower: true, canUpcast, powerLevels });
|
return mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||||
if ( !canCast ) data.errors.push("SW5E.PowerCastNoSlots");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -158,6 +168,8 @@ export default class AbilityUseDialog extends Dialog {
|
||||||
type: item.data.consumableType,
|
type: item.data.consumableType,
|
||||||
value: uses.value,
|
value: uses.value,
|
||||||
quantity: item.data.quantity,
|
quantity: item.data.quantity,
|
||||||
|
max: uses.max,
|
||||||
|
per: CONFIG.SW5E.limitedUsePeriods[uses.per]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,21 +2,27 @@
|
||||||
* A simple form to set actor movement speeds
|
* A simple form to set actor movement speeds
|
||||||
* @implements {BaseEntitySheet}
|
* @implements {BaseEntitySheet}
|
||||||
*/
|
*/
|
||||||
export default class MovementConfig extends BaseEntitySheet {
|
export default class ActorMovementConfig extends BaseEntitySheet {
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static get defaultOptions() {
|
static get defaultOptions() {
|
||||||
return mergeObject(super.defaultOptions, {
|
return mergeObject(super.defaultOptions, {
|
||||||
title: "SW5E.MovementConfig",
|
|
||||||
classes: ["sw5e"],
|
classes: ["sw5e"],
|
||||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||||
width: 240,
|
width: 300,
|
||||||
height: "auto"
|
height: "auto"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
get title() {
|
||||||
|
return `${game.i18n.localize("SW5E.MovementConfig")}: ${this.entity.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getData(options) {
|
getData(options) {
|
||||||
const data = {
|
const data = {
|
||||||
|
|
43
module/apps/senses-config.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* A simple form to set actor movement speeds
|
||||||
|
* @implements {BaseEntitySheet}
|
||||||
|
*/
|
||||||
|
export default class ActorSensesConfig extends BaseEntitySheet {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static get defaultOptions() {
|
||||||
|
return mergeObject(super.defaultOptions, {
|
||||||
|
classes: ["sw5e"],
|
||||||
|
template: "systems/sw5e/templates/apps/senses-config.html",
|
||||||
|
width: 300,
|
||||||
|
height: "auto"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
get title() {
|
||||||
|
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.entity.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getData(options) {
|
||||||
|
const senses = this.entity._data.data.attributes?.senses ?? {};
|
||||||
|
const data = {
|
||||||
|
senses: {},
|
||||||
|
special: senses.special ?? "",
|
||||||
|
units: senses.units, movementUnits: CONFIG.SW5E.movementUnits
|
||||||
|
};
|
||||||
|
for ( let [name, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||||
|
const v = senses[name];
|
||||||
|
data.senses[name] = {
|
||||||
|
label: game.i18n.localize(label),
|
||||||
|
value: Number.isNumeric(v) ? v.toNearest(0.1) : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,8 +36,8 @@ export default class TraitSelector extends FormApplication {
|
||||||
getData() {
|
getData() {
|
||||||
|
|
||||||
// Get current values
|
// Get current values
|
||||||
let attr = getProperty(this.object._data, this.attribute) || {};
|
let attr = getProperty(this.object._data, this.attribute);
|
||||||
attr.value = attr.value || [];
|
if ( getType(attr) !== "Object" ) attr = {value: [], custom: ""};
|
||||||
|
|
||||||
// Populate choices
|
// Populate choices
|
||||||
const choices = duplicate(this.options.choices);
|
const choices = duplicate(this.options.choices);
|
||||||
|
@ -49,7 +49,7 @@ export default class TraitSelector extends FormApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return data
|
// Return data
|
||||||
return {
|
return {
|
||||||
allowCustom: this.options.allowCustom,
|
allowCustom: this.options.allowCustom,
|
||||||
choices: choices,
|
choices: choices,
|
||||||
custom: attr ? attr.custom : ""
|
custom: attr ? attr.custom : ""
|
||||||
|
|
|
@ -66,7 +66,7 @@ export const displayChatActionButtons = function(message, html, data) {
|
||||||
export const addChatMessageContextOptions = function(html, options) {
|
export const addChatMessageContextOptions = function(html, options) {
|
||||||
let canApply = li => {
|
let canApply = li => {
|
||||||
const message = game.messages.get(li.data("messageId"));
|
const message = game.messages.get(li.data("messageId"));
|
||||||
return message.isRoll && message.isContentVisible && canvas.tokens.controlled.length;
|
return message?.isRoll && message?.isContentVisible && canvas?.tokens.controlled.length;
|
||||||
};
|
};
|
||||||
options.push(
|
options.push(
|
||||||
{
|
{
|
||||||
|
@ -103,15 +103,16 @@ export const addChatMessageContextOptions = function(html, options) {
|
||||||
* Apply rolled dice damage to the token or tokens which are currently controlled.
|
* Apply rolled dice damage to the token or tokens which are currently controlled.
|
||||||
* This allows for damage to be scaled by a multiplier to account for healing, critical hits, or resistance
|
* This allows for damage to be scaled by a multiplier to account for healing, critical hits, or resistance
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} roll The chat entry which contains the roll data
|
* @param {HTMLElement} li The chat entry which contains the roll data
|
||||||
* @param {Number} multiplier A damage multiplier to apply to the rolled damage.
|
* @param {Number} multiplier A damage multiplier to apply to the rolled damage.
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function applyChatCardDamage(roll, multiplier) {
|
function applyChatCardDamage(li, multiplier) {
|
||||||
const amount = roll.find('.dice-total').text();
|
const message = game.messages.get(li.data("messageId"));
|
||||||
|
const roll = message.roll;
|
||||||
return Promise.all(canvas.tokens.controlled.map(t => {
|
return Promise.all(canvas.tokens.controlled.map(t => {
|
||||||
const a = t.actor;
|
const a = t.actor;
|
||||||
return a.applyDamage(amount, multiplier);
|
return a.applyDamage(roll.total, multiplier);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export const ClassFeatures = {
|
export const ClassFeatures = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ export const _getInitiativeFormula = function(combatant) {
|
||||||
|
|
||||||
let nd = 1;
|
let nd = 1;
|
||||||
let mods = "";
|
let mods = "";
|
||||||
|
|
||||||
if (actor.getFlag("sw5e", "halflingLucky")) mods += "r=1";
|
if (actor.getFlag("sw5e", "halflingLucky")) mods += "r1=1";
|
||||||
if (actor.getFlag("sw5e", "initiativeAdv")) {
|
if (actor.getFlag("sw5e", "initiativeAdv")) {
|
||||||
nd = 2;
|
nd = 2;
|
||||||
mods += "kh";
|
mods += "kh";
|
||||||
|
@ -26,15 +26,3 @@ export const _getInitiativeFormula = function(combatant) {
|
||||||
if ( tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100);
|
if ( tiebreaker ) parts.push(actor.data.data.abilities.dex.value / 100);
|
||||||
return parts.filter(p => p !== null).join(" + ");
|
return parts.filter(p => p !== null).join(" + ");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* When the Combat encounter updates - re-render open Actor sheets for combatants in the encounter.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -4,14 +4,13 @@ import {ClassFeatures} from "./classFeatures.js"
|
||||||
export const SW5E = {};
|
export const SW5E = {};
|
||||||
|
|
||||||
// ASCII Artwork
|
// ASCII Artwork
|
||||||
SW5E.ASCII = `__________________________________________
|
SW5E.ASCII = `
|
||||||
_
|
___________ ___________
|
||||||
| |
|
/ _____/ \\ / \\ ____/ ____
|
||||||
___| |_ __ _ _ ____ ____ _ _ __ ___
|
\\_____ \\\\ \\/\\/ /____ \\_/ __ \\
|
||||||
/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __|
|
/ \\\\ // \\ ___/
|
||||||
\__ \ || (_) | | \ V V / (_) | | \__ \
|
\\______ / \\__/\\ //______ /\\__ >
|
||||||
|___/\__\__/_|_| \_/\_/ \__/_|_| |___/
|
\\/ \\/ \\/ \\/ `;
|
||||||
__________________________________________`;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,6 +53,30 @@ SW5E.alignments = {
|
||||||
'cd': "SW5E.AlignmentCD"
|
'cd': "SW5E.AlignmentCD"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of item attunement types
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
SW5E.attunementTypes = {
|
||||||
|
NONE: 0,
|
||||||
|
REQUIRED: 1,
|
||||||
|
ATTUNED: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of item attunement states
|
||||||
|
* @type {{"0": string, "1": string, "2": string}}
|
||||||
|
*/
|
||||||
|
SW5E.attunements = {
|
||||||
|
0: "SW5E.AttunementNone",
|
||||||
|
1: "SW5E.AttunementRequired",
|
||||||
|
2: "SW5E.AttunementAttuned"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
SW5E.weaponProficiencies = {
|
SW5E.weaponProficiencies = {
|
||||||
"sim": "SW5E.WeaponSimpleProficiency",
|
"sim": "SW5E.WeaponSimpleProficiency",
|
||||||
|
@ -291,6 +314,7 @@ SW5E.damageResistanceTypes = duplicate(SW5E.damageTypes);
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
// armor Types
|
// armor Types
|
||||||
SW5E.armorPropertiesTypes = {
|
SW5E.armorPropertiesTypes = {
|
||||||
"Absorptive": "SW5E.ArmorProperAbsorptive",
|
"Absorptive": "SW5E.ArmorProperAbsorptive",
|
||||||
|
@ -325,6 +349,19 @@ SW5E.armorPropertiesTypes = {
|
||||||
"Versatile": "SW5E.ArmorProperVersatile"
|
"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.movementTypes = {
|
||||||
|
"burrow": "SW5E.MovementBurrow",
|
||||||
|
"climb": "SW5E.MovementClimb",
|
||||||
|
"fly": "SW5E.MovementFly",
|
||||||
|
"swim": "SW5E.MovementSwim",
|
||||||
|
"walk": "SW5E.MovementWalk",
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The valid units of measure for movement distances in the game system.
|
* The valid units of measure for movement distances in the game system.
|
||||||
* By default this uses the imperial units of feet and miles.
|
* By default this uses the imperial units of feet and miles.
|
||||||
|
@ -433,17 +470,16 @@ SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12", "d20"];
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Character senses options
|
* The set of possible sensory perception types which an Actor may have
|
||||||
* @type {Object}
|
* @type {object}
|
||||||
*/
|
*/
|
||||||
SW5E.senses = {
|
SW5E.senses = {
|
||||||
"bs": "SW5E.SenseBS",
|
"blindsight": "SW5E.SenseBlindsight",
|
||||||
"dv": "SW5E.SenseDV",
|
"darkvision": "SW5E.SenseDarkvision",
|
||||||
"ts": "SW5E.SenseTS",
|
"tremorsense": "SW5E.SenseTremorsense",
|
||||||
"tr": "SW5E.SenseTR"
|
"truesight": "SW5E.SenseTruesight"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1140,7 +1176,7 @@ SW5E.characterFlags = {
|
||||||
section: "Feats",
|
section: "Feats",
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
"remarkableAthlete": {
|
"remarkableAthlete": {
|
||||||
name: "SW5E.FlagsRemarkableAthlete",
|
name: "SW5E.FlagsRemarkableAthlete",
|
||||||
hint: "SW5E.FlagsRemarkableAthleteHint",
|
hint: "SW5E.FlagsRemarkableAthleteHint",
|
||||||
abilities: ['str','dex','con'],
|
abilities: ['str','dex','con'],
|
||||||
|
|
|
@ -1,3 +1,69 @@
|
||||||
|
/**
|
||||||
|
* A standardized helper function for simplifying the constant parts of a multipart roll formula
|
||||||
|
*
|
||||||
|
* @param {string} formula The original Roll formula
|
||||||
|
* @param {Object} data Actor or item data against which to parse the roll
|
||||||
|
* @param {Object} options Formatting options
|
||||||
|
* @param {boolean} options.constantFirst Puts the constants before the dice terms in the resulting formula
|
||||||
|
*
|
||||||
|
* @return {string} The resulting simplified formula
|
||||||
|
*/
|
||||||
|
export function simplifyRollFormula(formula, data, {constantFirst = false} = {}) {
|
||||||
|
const roll = new Roll(formula, data); // Parses the formula and replaces any @properties
|
||||||
|
const terms = roll.terms;
|
||||||
|
|
||||||
|
// Some terms are "too complicated" for this algorithm to simplify
|
||||||
|
// In this case, the original formula is returned.
|
||||||
|
if (terms.some(_isUnsupportedTerm)) return roll.formula;
|
||||||
|
|
||||||
|
const rollableTerms = []; // Terms that are non-constant, and their associated operators
|
||||||
|
const constantTerms = []; // Terms that are constant, and their associated operators
|
||||||
|
let operators = []; // Temporary storage for operators before they are moved to one of the above
|
||||||
|
|
||||||
|
for (let term of terms) { // For each term
|
||||||
|
if (["+", "-"].includes(term)) operators.push(term); // If the term is an addition/subtraction operator, push the term into the operators array
|
||||||
|
else { // Otherwise the term is not an operator
|
||||||
|
if (term instanceof DiceTerm) { // If the term is something rollable
|
||||||
|
rollableTerms.push(...operators); // Place all the operators into the rollableTerms array
|
||||||
|
rollableTerms.push(term); // Then place this rollable term into it as well
|
||||||
|
} //
|
||||||
|
else { // Otherwise, this must be a constant
|
||||||
|
constantTerms.push(...operators); // Place the operators into the constantTerms array
|
||||||
|
constantTerms.push(term); // Then also add this constant term to that array.
|
||||||
|
} //
|
||||||
|
operators = []; // Finally, the operators have now all been assigend to one of the arrays, so empty this before the next iteration.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const constantFormula = Roll.cleanFormula(constantTerms); // Cleans up the constant terms and produces a new formula string
|
||||||
|
const rollableFormula = Roll.cleanFormula(rollableTerms); // Cleans up the non-constant terms and produces a new formula string
|
||||||
|
|
||||||
|
const constantPart = roll._safeEval(constantFormula); // Mathematically evaluate the constant formula to produce a single constant term
|
||||||
|
|
||||||
|
const parts = constantFirst ? // Order the rollable and constant terms, either constant first or second depending on the optional argumen
|
||||||
|
[constantPart, rollableFormula] : [rollableFormula, constantPart];
|
||||||
|
|
||||||
|
// Join the parts with a + sign, pass them to `Roll` once again to clean up the formula
|
||||||
|
return new Roll(parts.filterJoin(" + ")).formula;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only some terms are supported by simplifyRollFormula, this method returns true when the term is not supported.
|
||||||
|
* @param {*} term - A single Dice term to check support on
|
||||||
|
* @return {Boolean} True when unsupported, false if supported
|
||||||
|
*/
|
||||||
|
function _isUnsupportedTerm(term) {
|
||||||
|
const diceTerm = term instanceof DiceTerm;
|
||||||
|
const operator = ["+", "-"].includes(term);
|
||||||
|
const number = !isNaN(Number(term));
|
||||||
|
|
||||||
|
return !(diceTerm || operator || number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A standardized helper function for managing core 5e "d20 rolls"
|
* A standardized helper function for managing core 5e "d20 rolls"
|
||||||
*
|
*
|
||||||
|
@ -44,8 +110,8 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
|
||||||
let adv = 0;
|
let adv = 0;
|
||||||
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||||
if (fastForward) {
|
if (fastForward) {
|
||||||
if ( advantage || event.altKey ) adv = 1;
|
if ( advantage ?? event.altKey ) adv = 1;
|
||||||
else if ( disadvantage || event.ctrlKey || event.metaKey ) adv = -1;
|
else if ( disadvantage ?? (event.ctrlKey || event.metaKey) ) adv = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the inner roll function
|
// Define the inner roll function
|
||||||
|
@ -53,7 +119,7 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
|
||||||
|
|
||||||
// Determine the d20 roll and modifiers
|
// Determine the d20 roll and modifiers
|
||||||
let nd = 1;
|
let nd = 1;
|
||||||
let mods = halflingLucky ? "r=1" : "";
|
let mods = halflingLucky ? "r1=1" : "";
|
||||||
|
|
||||||
// Handle advantage
|
// Handle advantage
|
||||||
if (adv === 1) {
|
if (adv === 1) {
|
||||||
|
@ -109,6 +175,8 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
|
||||||
if (d.faces === 20) {
|
if (d.faces === 20) {
|
||||||
d.options.critical = critical;
|
d.options.critical = critical;
|
||||||
d.options.fumble = fumble;
|
d.options.fumble = fumble;
|
||||||
|
if ( adv === 1 ) d.options.advantage = true;
|
||||||
|
else if ( adv === -1 ) d.options.disadvantage = true;
|
||||||
if (targetValue) d.options.target = targetValue;
|
if (targetValue) d.options.target = targetValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +199,6 @@ export async function d20Roll({parts=[], data={}, event={}, rollMode=null, templ
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Present a Dialog form which creates a d20 roll once submitted
|
* Present a Dialog form which creates a d20 roll once submitted
|
||||||
* @return {Promise<Roll>}
|
* @return {Promise<Roll>}
|
||||||
|
@ -175,7 +242,6 @@ async function _d20RollDialog({template, title, parts, data, rollMode, dialogOpt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,7 +280,6 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
|
||||||
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
||||||
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
||||||
parts = parts.concat(["@bonus"]);
|
parts = parts.concat(["@bonus"]);
|
||||||
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
|
||||||
|
|
||||||
// Define inner roll function
|
// Define inner roll function
|
||||||
const _roll = function(parts, crit, form) {
|
const _roll = function(parts, crit, form) {
|
||||||
|
@ -242,7 +307,9 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
|
||||||
|
|
||||||
// Execute the roll
|
// Execute the roll
|
||||||
try {
|
try {
|
||||||
return roll.roll();
|
roll.evaluate()
|
||||||
|
if ( crit ) roll.dice.forEach(d => d.options.critical = true); // TODO workaround core bug which wipes Roll#options on roll
|
||||||
|
return roll;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
|
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
|
||||||
|
@ -251,7 +318,7 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the Roll instance
|
// Create the Roll instance
|
||||||
const roll = fastForward ? _roll(parts, critical || event.altKey) : await _damageRollDialog({
|
const roll = fastForward ? _roll(parts, critical) : await _damageRollDialog({
|
||||||
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll
|
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {d20Roll, damageRoll} from "../dice.js";
|
import {simplifyRollFormula, d20Roll, damageRoll} from "../dice.js";
|
||||||
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
||||||
import AbilityTemplate from "../pixi/ability-template.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override and extend the basic :class:`Item` implementation
|
* Override and extend the basic :class:`Item` implementation
|
||||||
|
@ -101,7 +100,8 @@ export default class Item5e extends Item {
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get hasSave() {
|
get hasSave() {
|
||||||
return !!(this.data.data.save && this.data.data.save.ability);
|
const save = this.data.data?.save || {};
|
||||||
|
return !!(save.ability && save.scaling);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -152,7 +152,7 @@ export default class Item5e extends Item {
|
||||||
const itemData = this.data;
|
const itemData = this.data;
|
||||||
const data = itemData.data;
|
const data = itemData.data;
|
||||||
const C = CONFIG.SW5E;
|
const C = CONFIG.SW5E;
|
||||||
const labels = {};
|
const labels = this.labels = {};
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
if ( itemData.type === "class" ) {
|
if ( itemData.type === "class" ) {
|
||||||
|
@ -251,12 +251,13 @@ export default class Item5e extends Item {
|
||||||
|
|
||||||
// Item Actions
|
// Item Actions
|
||||||
if ( data.hasOwnProperty("actionType") ) {
|
if ( data.hasOwnProperty("actionType") ) {
|
||||||
|
// if this item is owned, we populate the label and saving throw during actor init
|
||||||
|
if (!this.isOwned) {
|
||||||
|
// Saving throws
|
||||||
|
this.getSaveDC();
|
||||||
|
|
||||||
// Saving throws for unowned items
|
// To Hit
|
||||||
const save = data.save;
|
this.getAttackToHit();
|
||||||
if ( save?.ability && !this.isOwned ) {
|
|
||||||
if ( save.scaling !== "flat" ) save.dc = null;
|
|
||||||
labels.save = game.i18n.format("SW5E.SaveDC", {dc: save.dc || "", ability: C.abilities[save.ability]});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Damage
|
// Damage
|
||||||
|
@ -265,10 +266,111 @@ export default class Item5e extends Item {
|
||||||
labels.damage = dam.parts.map(d => d[0]).join(" + ").replace(/\+ -/g, "- ");
|
labels.damage = dam.parts.map(d => d[0]).join(" + ").replace(/\+ -/g, "- ");
|
||||||
labels.damageTypes = dam.parts.map(d => C.damageTypes[d[1]]).join(", ");
|
labels.damageTypes = dam.parts.map(d => C.damageTypes[d[1]]).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Limited Uses
|
||||||
|
if ( this.isOwned && !!data.uses?.max ) {
|
||||||
|
let max = data.uses.max;
|
||||||
|
if ( !Number.isNumeric(max) ) {
|
||||||
|
max = Roll.replaceFormulaData(max, this.actor.getRollData());
|
||||||
|
if ( Roll.MATH_PROXY.safeEval ) max = Roll.MATH_PROXY.safeEval(max);
|
||||||
|
}
|
||||||
|
data.uses.max = Number(max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the derived power DC for an item that requires a saving throw
|
||||||
|
* @returns {number|null}
|
||||||
|
*/
|
||||||
|
getSaveDC() {
|
||||||
|
if ( !this.hasSave ) return;
|
||||||
|
const save = this.data.data?.save;
|
||||||
|
|
||||||
|
// Actor power-DC based scaling
|
||||||
|
if ( save.scaling === "power" ) {
|
||||||
|
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.powerdc") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign labels
|
// Ability-score based scaling
|
||||||
this.labels = labels;
|
else if ( save.scaling !== "flat" ) {
|
||||||
|
save.dc = this.isOwned ? getProperty(this.actor.data, `data.abilities.${save.scaling}.dc`) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update labels
|
||||||
|
const abl = CONFIG.SW5E.abilities[save.ability];
|
||||||
|
this.labels.save = game.i18n.format("SW5E.SaveDC", {dc: save.dc || "", ability: abl});
|
||||||
|
return save.dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a label to the Item detailing its total to hit bonus.
|
||||||
|
* Sources:
|
||||||
|
* - item entity's innate attack bonus
|
||||||
|
* - item's actor's proficiency bonus if applicable
|
||||||
|
* - item's actor's global bonuses to the given item type
|
||||||
|
* - item's ammunition if applicable
|
||||||
|
*
|
||||||
|
* @returns {Object} returns `rollData` and `parts` to be used in the item's Attack roll
|
||||||
|
*/
|
||||||
|
getAttackToHit() {
|
||||||
|
const itemData = this.data.data;
|
||||||
|
if ( !this.hasAttack || !itemData ) return;
|
||||||
|
const rollData = this.getRollData();
|
||||||
|
|
||||||
|
// Define Roll bonuses
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
// Include the item's innate attack bonus as the initial value and label
|
||||||
|
if ( itemData.attackBonus ) {
|
||||||
|
parts.push(itemData.attackBonus)
|
||||||
|
this.labels.toHit = itemData.attackBonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take no further action for un-owned items
|
||||||
|
if ( !this.isOwned ) return {rollData, parts};
|
||||||
|
|
||||||
|
// Ability score modifier
|
||||||
|
parts.push(`@mod`);
|
||||||
|
|
||||||
|
// Add proficiency bonus if an explicit proficiency flag is present or for non-item features
|
||||||
|
if ( !["weapon", "consumable"].includes(this.data.type) || itemData.proficient ) {
|
||||||
|
parts.push("@prof");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actor-level global bonus to attack rolls
|
||||||
|
const actorBonus = this.actor.data.data.bonuses?.[itemData.actionType] || {};
|
||||||
|
if ( actorBonus.attack ) parts.push(actorBonus.attack);
|
||||||
|
|
||||||
|
// One-time bonus provided by consumed ammunition
|
||||||
|
if ( (itemData.consume?.type === 'ammo') && !!this.actor.items ) {
|
||||||
|
const ammoItemData = this.actor.items.get(itemData.consume.target)?.data;
|
||||||
|
|
||||||
|
if (ammoItemData) {
|
||||||
|
const ammoItemQuantity = ammoItemData.data.quantity;
|
||||||
|
const ammoCanBeConsumed = ammoItemQuantity && (ammoItemQuantity - (itemData.consume.amount ?? 0) >= 0);
|
||||||
|
const ammoItemAttackBonus = ammoItemData.data.attackBonus;
|
||||||
|
const ammoIsTypeConsumable = (ammoItemData.type === "consumable") && (ammoItemData.data.consumableType === "ammo")
|
||||||
|
if ( ammoCanBeConsumed && ammoItemAttackBonus && ammoIsTypeConsumable ) {
|
||||||
|
parts.push("@ammo");
|
||||||
|
rollData["ammo"] = ammoItemAttackBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condense the resulting attack bonus formula into a simplified label
|
||||||
|
let toHitLabel = simplifyRollFormula(parts.join('+'), rollData).trim();
|
||||||
|
if (toHitLabel.charAt(0) !== '-') {
|
||||||
|
toHitLabel = '+ ' + toHitLabel
|
||||||
|
}
|
||||||
|
this.labels.toHit = toHitLabel;
|
||||||
|
|
||||||
|
// Update labels and return the prepared roll data
|
||||||
|
return {rollData, parts};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -279,9 +381,251 @@ export default class Item5e extends Item {
|
||||||
* @param {string} [rollMode] The roll display mode with which to display (or not) the card
|
* @param {string} [rollMode] The roll display mode with which to display (or not) the card
|
||||||
* @param {boolean} [createMessage] Whether to automatically create a chat message (if true) or simply return
|
* @param {boolean} [createMessage] Whether to automatically create a chat message (if true) or simply return
|
||||||
* the prepared chat message data (if false).
|
* the prepared chat message data (if false).
|
||||||
* @return {Promise}
|
* @return {Promise<ChatMessage|object|void>}
|
||||||
*/
|
*/
|
||||||
async roll({configureDialog=true, rollMode=null, createMessage=true}={}) {
|
async roll({configureDialog=true, rollMode, createMessage=true}={}) {
|
||||||
|
let item = this;
|
||||||
|
const actor = this.actor;
|
||||||
|
|
||||||
|
// Reference aspects of the item data necessary for usage
|
||||||
|
const id = this.data.data; // Item data
|
||||||
|
const hasArea = this.hasAreaTarget; // Is the ability usage an AoE?
|
||||||
|
const resource = id.consume || {}; // Resource consumption
|
||||||
|
const recharge = id.recharge || {}; // Recharge mechanic
|
||||||
|
const uses = id?.uses ?? {}; // Limited uses
|
||||||
|
const isPower = this.type === "power"; // Does the item require a power slot?
|
||||||
|
const requirePowerSlot = isPower && (id.level > 0) && CONFIG.SW5E.powerUpcastModes.includes(id.preparation.mode);
|
||||||
|
|
||||||
|
// Define follow-up actions resulting from the item usage
|
||||||
|
let createMeasuredTemplate = hasArea; // Trigger a template creation
|
||||||
|
let consumeRecharge = !!recharge.value; // Consume recharge
|
||||||
|
let consumeResource = !!resource.target && (resource.type !== "ammo") // Consume a linked (non-ammo) resource
|
||||||
|
let consumePowerSlot = requirePowerSlot; // Consume a power slot
|
||||||
|
let consumeUsage = !!uses.per; // Consume limited uses
|
||||||
|
let consumeQuantity = uses.autoDestroy; // Consume quantity of the item in lieu of uses
|
||||||
|
|
||||||
|
// Display a configuration dialog to customize the usage
|
||||||
|
const needsConfiguration = createMeasuredTemplate || consumeRecharge || consumeResource || consumePowerSlot || consumeUsage;
|
||||||
|
if (configureDialog && needsConfiguration) {
|
||||||
|
const configuration = await AbilityUseDialog.create(this);
|
||||||
|
if (!configuration) return;
|
||||||
|
|
||||||
|
// Determine consumption preferences
|
||||||
|
createMeasuredTemplate = Boolean(configuration.placeTemplate);
|
||||||
|
consumeUsage = Boolean(configuration.consumeUse);
|
||||||
|
consumeRecharge = Boolean(configuration.consumeRecharge);
|
||||||
|
consumeResource = Boolean(configuration.consumeResource);
|
||||||
|
consumePowerSlot = Boolean(configuration.consumeSlot);
|
||||||
|
|
||||||
|
// Handle power upcasting
|
||||||
|
if ( requirePowerSlot ) {
|
||||||
|
const slotLevel = configuration.level;
|
||||||
|
const powerLevel = slotLevel === "pact" ? actor.data.data.powers.pact.level : parseInt(slotLevel);
|
||||||
|
if (powerLevel !== id.level) {
|
||||||
|
const upcastData = mergeObject(this.data, {"data.level": powerLevel}, {inplace: false});
|
||||||
|
item = this.constructor.createOwned(upcastData, actor); // Replace the item with an upcast version
|
||||||
|
}
|
||||||
|
if ( consumePowerSlot ) consumePowerSlot = slotLevel === "pact" ? "pact" : `power${powerLevel}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the item can be used by testing for resource consumption
|
||||||
|
const usage = item._getUsageUpdates({consumeRecharge, consumeResource, consumePowerSlot, consumeUsage, consumeQuantity});
|
||||||
|
if ( !usage ) return;
|
||||||
|
const {actorUpdates, itemUpdates, resourceUpdates} = usage;
|
||||||
|
|
||||||
|
// Commit pending data updates
|
||||||
|
if ( !isObjectEmpty(itemUpdates) ) await item.update(itemUpdates);
|
||||||
|
if ( consumeQuantity && (item.data.data.quantity === 0) ) await item.delete();
|
||||||
|
if ( !isObjectEmpty(actorUpdates) ) await actor.update(actorUpdates);
|
||||||
|
if ( !isObjectEmpty(resourceUpdates) ) {
|
||||||
|
const resource = actor.items.get(id.consume?.target);
|
||||||
|
if ( resource ) await resource.update(resourceUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate measured template creation
|
||||||
|
if ( createMeasuredTemplate ) {
|
||||||
|
const template = game.sw5e.canvas.AbilityTemplate.fromItem(item);
|
||||||
|
if ( template ) template.drawPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or return the Chat Message data
|
||||||
|
return item.displayCard({rollMode, createMessage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the consumed resources used by an Item are available.
|
||||||
|
* Otherwise display an error and return false.
|
||||||
|
* @param {boolean} consumeQuantity Consume quantity of the item if other consumption modes are not available?
|
||||||
|
* @param {boolean} consumeRecharge Whether the item consumes the recharge mechanic
|
||||||
|
* @param {boolean} consumeResource Whether the item consumes a limited resource
|
||||||
|
* @param {string|boolean} consumePowerSlot A level of power slot consumed, or false
|
||||||
|
* @param {boolean} consumeUsage Whether the item consumes a limited usage
|
||||||
|
* @returns {object|boolean} A set of data changes to apply when the item is used, or false
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getUsageUpdates({consumeQuantity=false, consumeRecharge=false, consumeResource=false, consumePowerSlot=false, consumeUsage=false}) {
|
||||||
|
|
||||||
|
// Reference item data
|
||||||
|
const id = this.data.data;
|
||||||
|
const actorUpdates = {};
|
||||||
|
const itemUpdates = {};
|
||||||
|
const resourceUpdates = {};
|
||||||
|
|
||||||
|
// Consume Recharge
|
||||||
|
if ( consumeRecharge ) {
|
||||||
|
const recharge = id.recharge || {};
|
||||||
|
if ( recharge.charged === false ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
itemUpdates["data.recharge.charged"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume Limited Resource
|
||||||
|
if ( consumeResource ) {
|
||||||
|
const canConsume = this._handleConsumeResource(itemUpdates, actorUpdates, resourceUpdates);
|
||||||
|
if ( canConsume === false ) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume Power Slots
|
||||||
|
if ( consumePowerSlot ) {
|
||||||
|
const level = this.actor?.data.data.powers[consumePowerSlot];
|
||||||
|
const powers = Number(level?.value ?? 0);
|
||||||
|
if ( powers === 0 ) {
|
||||||
|
const label = game.i18n.localize(consumePowerSlot === "pact" ? "SW5E.PowerProgPact" : `SW5E.PowerLevel${id.level}`);
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.PowerCastNoSlots", {name: this.name, level: label}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
actorUpdates[`data.powers.${consumePowerSlot}.value`] = Math.max(powers - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume Limited Usage
|
||||||
|
if ( consumeUsage ) {
|
||||||
|
const uses = id.uses || {};
|
||||||
|
const available = Number(uses.value ?? 0);
|
||||||
|
let used = false;
|
||||||
|
|
||||||
|
// Reduce usages
|
||||||
|
const remaining = Math.max(available - 1, 0);
|
||||||
|
if ( available >= 1 ) {
|
||||||
|
used = true;
|
||||||
|
itemUpdates["data.uses.value"] = remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce quantity if not reducing usages or if usages hit 0 and we are set to consumeQuantity
|
||||||
|
if ( consumeQuantity && (!used || (remaining === 0)) ) {
|
||||||
|
const q = Number(id.quantity ?? 1);
|
||||||
|
if ( q >= 1 ) {
|
||||||
|
used = true;
|
||||||
|
itemUpdates["data.quantity"] = Math.max(q - 1, 0);
|
||||||
|
itemUpdates["data.uses.value"] = uses.max ?? 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the item was not used, return a warning
|
||||||
|
if ( !used ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the configured usage
|
||||||
|
return {itemUpdates, actorUpdates, resourceUpdates};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle update actions required when consuming an external resource
|
||||||
|
* @param {object} itemUpdates An object of data updates applied to this item
|
||||||
|
* @param {object} actorUpdates An object of data updates applied to the item owner (Actor)
|
||||||
|
* @param {object} resourceUpdates An object of data updates applied to a different resource item (Item)
|
||||||
|
* @return {boolean|void} Return false to block further progress, or return nothing to continue
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleConsumeResource(itemUpdates, actorUpdates, resourceUpdates) {
|
||||||
|
const actor = this.actor;
|
||||||
|
const itemData = this.data.data;
|
||||||
|
const consume = itemData.consume || {};
|
||||||
|
if ( !consume.type ) return;
|
||||||
|
|
||||||
|
// No consumed target
|
||||||
|
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
|
||||||
|
if ( !consume.target ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the consumed resource and its current quantity
|
||||||
|
let resource = null;
|
||||||
|
let amount = Number(consume.amount ?? 1);
|
||||||
|
let quantity = 0;
|
||||||
|
switch ( consume.type ) {
|
||||||
|
case "attribute":
|
||||||
|
resource = getProperty(actor.data.data, consume.target);
|
||||||
|
quantity = resource || 0;
|
||||||
|
break;
|
||||||
|
case "ammo":
|
||||||
|
case "material":
|
||||||
|
resource = actor.items.get(consume.target);
|
||||||
|
quantity = resource ? resource.data.data.quantity : 0;
|
||||||
|
break;
|
||||||
|
case "charges":
|
||||||
|
resource = actor.items.get(consume.target);
|
||||||
|
if ( !resource ) break;
|
||||||
|
const uses = resource.data.data.uses;
|
||||||
|
if ( uses.per && uses.max ) quantity = uses.value;
|
||||||
|
else if ( resource.data.data.recharge?.value ) {
|
||||||
|
quantity = resource.data.data.recharge.charged ? 1 : 0;
|
||||||
|
amount = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that a consumed resource is available
|
||||||
|
if ( !resource ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoSource", {name: this.name, type: typeLabel}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the required quantity is available
|
||||||
|
let remaining = quantity - amount;
|
||||||
|
if ( remaining < 0 ) {
|
||||||
|
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoQuantity", {name: this.name, type: typeLabel}));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define updates to provided data objects
|
||||||
|
switch ( consume.type ) {
|
||||||
|
case "attribute":
|
||||||
|
actorUpdates[`data.${consume.target}`] = remaining;
|
||||||
|
break;
|
||||||
|
case "ammo":
|
||||||
|
case "material":
|
||||||
|
resourceUpdates["data.quantity"] = remaining;
|
||||||
|
break;
|
||||||
|
case "charges":
|
||||||
|
const uses = resource.data.data.uses || {};
|
||||||
|
const recharge = resource.data.data.recharge || {};
|
||||||
|
if ( uses.per && uses.max ) resourceUpdates["data.uses.value"] = remaining;
|
||||||
|
else if ( recharge.value ) resourceUpdates["data.recharge.charged"] = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the chat card for an Item as a Chat Message
|
||||||
|
* @param {object} options Options which configure the display of the item chat card
|
||||||
|
* @param {string} rollMode The message visibility mode to apply to the created card
|
||||||
|
* @param {boolean} createMessage Whether to automatically create a ChatMessage entity (if true), or only return
|
||||||
|
* the prepared message data (if false)
|
||||||
|
*/
|
||||||
|
async displayCard({rollMode, createMessage=true}={}) {
|
||||||
|
|
||||||
// Basic template rendering data
|
// Basic template rendering data
|
||||||
const token = this.actor.token;
|
const token = this.actor.token;
|
||||||
|
@ -300,190 +644,31 @@ export default class Item5e extends Item {
|
||||||
hasAreaTarget: this.hasAreaTarget
|
hasAreaTarget: this.hasAreaTarget
|
||||||
};
|
};
|
||||||
|
|
||||||
// For feature items, optionally show an ability usage dialog
|
|
||||||
if (this.data.type === "feat") {
|
|
||||||
let configured = await this._rollFeat(configureDialog);
|
|
||||||
if ( configured === false ) return;
|
|
||||||
} else if ( this.data.type === "consumable" ) {
|
|
||||||
let configured = await this._rollConsumable(configureDialog);
|
|
||||||
if ( configured === false ) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For items which consume a resource, handle that here
|
|
||||||
const allowed = await this._handleResourceConsumption({isCard: true, isAttack: false});
|
|
||||||
if ( allowed === false ) return;
|
|
||||||
|
|
||||||
// Render the chat card template
|
// Render the chat card template
|
||||||
const templateType = ["tool"].includes(this.data.type) ? this.data.type : "item";
|
const templateType = ["tool"].includes(this.data.type) ? this.data.type : "item";
|
||||||
const template = `systems/sw5e/templates/chat/${templateType}-card.html`;
|
const template = `systems/sw5e/templates/chat/${templateType}-card.html`;
|
||||||
const html = await renderTemplate(template, templateData);
|
const html = await renderTemplate(template, templateData);
|
||||||
|
|
||||||
// Basic chat message data
|
// Create the ChatMessage data object
|
||||||
const chatData = {
|
const chatData = {
|
||||||
user: game.user._id,
|
user: game.user._id,
|
||||||
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
||||||
content: html,
|
content: html,
|
||||||
flavor: this.data.data.chatFlavor || this.name,
|
flavor: this.data.data.chatFlavor || this.name,
|
||||||
speaker: {
|
speaker: ChatMessage.getSpeaker({actor: this.actor, token}),
|
||||||
actor: this.actor._id,
|
|
||||||
token: this.actor.token,
|
|
||||||
alias: this.actor.name
|
|
||||||
},
|
|
||||||
flags: {"core.canPopout": true}
|
flags: {"core.canPopout": true}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the consumable was destroyed in the process - embed the item data in the surviving message
|
// If the Item was destroyed in the process of displaying its card - embed the item data in the chat message
|
||||||
if ( (this.data.type === "consumable") && !this.actor.items.has(this.id) ) {
|
if ( (this.data.type === "consumable") && !this.actor.items.has(this.id) ) {
|
||||||
chatData.flags["sw5e.itemData"] = this.data;
|
chatData.flags["sw5e.itemData"] = this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle default roll mode
|
// Apply the roll mode to adjust message visibility
|
||||||
rollMode = rollMode || game.settings.get("core", "rollMode");
|
ChatMessage.applyRollMode(chatData, rollMode || game.settings.get("core", "rollMode"));
|
||||||
if ( ["gmroll", "blindroll"].includes(rollMode) ) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM");
|
|
||||||
if ( rollMode === "blindroll" ) chatData["blind"] = true;
|
|
||||||
|
|
||||||
// Create the chat message
|
// Create the Chat Message or return its data
|
||||||
if ( createMessage ) return ChatMessage.create(chatData);
|
return createMessage ? ChatMessage.create(chatData) : chatData;
|
||||||
else return chatData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For items which consume a resource, handle the consumption of that resource when the item is used.
|
|
||||||
* There are four types of ability consumptions which are handled:
|
|
||||||
* 1. Ammunition (on attack rolls)
|
|
||||||
* 2. Attributes (on card usage)
|
|
||||||
* 3. Materials (on card usage)
|
|
||||||
* 4. Item Charges (on card usage)
|
|
||||||
*
|
|
||||||
* @param {boolean} isCard Is the item card being played?
|
|
||||||
* @param {boolean} isAttack Is an attack roll being made?
|
|
||||||
* @return {Promise<boolean>} Can the item card or attack roll be allowed to proceed?
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _handleResourceConsumption({isCard=false, isAttack=false}={}) {
|
|
||||||
const itemData = this.data.data;
|
|
||||||
const consume = itemData.consume || {};
|
|
||||||
if ( !consume.type ) return true;
|
|
||||||
const actor = this.actor;
|
|
||||||
const typeLabel = CONFIG.SW5E.abilityConsumptionTypes[consume.type];
|
|
||||||
|
|
||||||
// Only handle certain types for certain actions
|
|
||||||
if ( ((consume.type === "ammo") && !isAttack ) || ((consume.type !== "ammo") && !isCard) ) return true;
|
|
||||||
|
|
||||||
// No consumed target set
|
|
||||||
if ( !consume.target ) {
|
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoResource", {name: this.name, type: typeLabel}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify the consumed resource and it's quantity
|
|
||||||
let consumed = null;
|
|
||||||
let amount = parseInt(consume.amount || 1);
|
|
||||||
let quantity = 0;
|
|
||||||
switch ( consume.type ) {
|
|
||||||
case "attribute":
|
|
||||||
consumed = getProperty(actor.data.data, consume.target);
|
|
||||||
quantity = consumed || 0;
|
|
||||||
break;
|
|
||||||
case "ammo":
|
|
||||||
case "material":
|
|
||||||
consumed = actor.items.get(consume.target);
|
|
||||||
quantity = consumed ? consumed.data.data.quantity : 0;
|
|
||||||
break;
|
|
||||||
case "charges":
|
|
||||||
consumed = actor.items.get(consume.target);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the consumed resource is available
|
|
||||||
if ( [null, undefined].includes(consumed) ) {
|
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoSource", {name: this.name, type: typeLabel}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let remaining = quantity - amount;
|
|
||||||
if ( remaining < 0) {
|
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ConsumeWarningNoQuantity", {name: this.name, type: typeLabel}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the consumed resource
|
|
||||||
switch ( consume.type ) {
|
|
||||||
case "attribute":
|
|
||||||
await this.actor.update({[`data.${consume.target}`]: remaining});
|
|
||||||
break;
|
|
||||||
case "ammo":
|
|
||||||
case "material":
|
|
||||||
await consumed.update({"data.quantity": remaining});
|
|
||||||
break;
|
|
||||||
case "charges":
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional rolling steps when rolling a feat-type item
|
|
||||||
* @private
|
|
||||||
* @return {boolean} whether the roll should be prevented
|
|
||||||
*/
|
|
||||||
async _rollFeat(configureDialog) {
|
|
||||||
if ( this.data.type !== "feat" ) throw new Error("Wrong Item type");
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
let placeTemplate = false;
|
|
||||||
let consume = charge.value || usesCharges;
|
|
||||||
|
|
||||||
// Determine whether the feat uses charges
|
|
||||||
configureDialog = configureDialog && (consume || this.hasAreaTarget);
|
|
||||||
if ( configureDialog ) {
|
|
||||||
const usage = await AbilityUseDialog.create(this);
|
|
||||||
if ( usage === null ) return false;
|
|
||||||
consume = Boolean(usage.get("consumeUse"));
|
|
||||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Item data
|
|
||||||
const current = getProperty(this.data, "data.uses.value") || 0;
|
|
||||||
if ( consume && charge.value ) {
|
|
||||||
if ( !charge.charged ) {
|
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else await this.update({"data.recharge.charged": false});
|
|
||||||
}
|
|
||||||
else if ( consume && usesCharges ) {
|
|
||||||
if ( uses.value <= 0 ) {
|
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await this.update({"data.uses.value": Math.max(current - 1, 0)});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe initiate template placement workflow
|
|
||||||
if ( this.hasAreaTarget && placeTemplate ) {
|
|
||||||
const template = AbilityTemplate.fromItem(this);
|
|
||||||
if ( template ) template.drawPreview();
|
|
||||||
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -507,8 +692,9 @@ export default class Item5e extends Item {
|
||||||
const fn = this[`_${this.data.type}ChatData`];
|
const fn = this[`_${this.data.type}ChatData`];
|
||||||
if ( fn ) fn.bind(this)(data, labels, props);
|
if ( fn ) fn.bind(this)(data, labels, props);
|
||||||
|
|
||||||
// General equipment properties
|
// Equipment properties
|
||||||
if ( data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type) ) {
|
if ( data.hasOwnProperty("equipped") && !["loot", "tool"].includes(this.data.type) ) {
|
||||||
|
if ( data.attunement === CONFIG.SW5E.attunementTypes.REQUIRED ) props.push(game.i18n.localize(CONFIG.SW5E.attunements[CONFIG.SW5E.attunementTypes.REQUIRED]));
|
||||||
props.push(
|
props.push(
|
||||||
game.i18n.localize(data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped"),
|
game.i18n.localize(data.equipped ? "SW5E.Equipped" : "SW5E.Unequipped"),
|
||||||
game.i18n.localize(data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient"),
|
game.i18n.localize(data.proficient ? "SW5E.Proficient" : "SW5E.NotProficient"),
|
||||||
|
@ -633,43 +819,35 @@ export default class Item5e extends Item {
|
||||||
*/
|
*/
|
||||||
async rollAttack(options={}) {
|
async rollAttack(options={}) {
|
||||||
const itemData = this.data.data;
|
const itemData = this.data.data;
|
||||||
const actorData = this.actor.data.data;
|
|
||||||
const flags = this.actor.data.flags.sw5e || {};
|
const flags = this.actor.data.flags.sw5e || {};
|
||||||
if ( !this.hasAttack ) {
|
if ( !this.hasAttack ) {
|
||||||
throw new Error("You may not place an Attack Roll with this Item.");
|
throw new Error("You may not place an Attack Roll with this Item.");
|
||||||
}
|
}
|
||||||
let title = `${this.name} - ${game.i18n.localize("SW5E.AttackRoll")}`;
|
let title = `${this.name} - ${game.i18n.localize("SW5E.AttackRoll")}`;
|
||||||
const rollData = this.getRollData();
|
|
||||||
|
|
||||||
// Define Roll bonuses
|
// get the parts and rollData for this item's attack
|
||||||
const parts = [`@mod`];
|
const {parts, rollData} = this.getAttackToHit();
|
||||||
if ( (this.data.type !== "weapon") || itemData.proficient ) {
|
|
||||||
parts.push("@prof");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attack Bonus
|
// Handle ammunition consumption
|
||||||
if ( itemData.attackBonus ) parts.push(itemData.attackBonus);
|
|
||||||
const actorBonus = actorData?.bonuses?.[itemData.actionType] || {};
|
|
||||||
if ( actorBonus.attack ) parts.push(actorBonus.attack);
|
|
||||||
|
|
||||||
// Ammunition Bonus
|
|
||||||
delete this._ammo;
|
delete this._ammo;
|
||||||
|
let ammo = null;
|
||||||
|
let ammoUpdate = null;
|
||||||
const consume = itemData.consume;
|
const consume = itemData.consume;
|
||||||
if ( consume?.type === "ammo" ) {
|
if ( consume?.type === "ammo" ) {
|
||||||
const ammo = this.actor.items.get(consume.target);
|
ammo = this.actor.items.get(consume.target);
|
||||||
if(ammo?.data){
|
if (ammo?.data) {
|
||||||
const q = ammo.data.data.quantity;
|
const q = ammo.data.data.quantity;
|
||||||
const consumeAmount = consume.amount ?? 0;
|
const consumeAmount = consume.amount ?? 0;
|
||||||
if ( q && (q - consumeAmount >= 0) ) {
|
if ( q && (q - consumeAmount >= 0) ) {
|
||||||
this._ammo = ammo;
|
this._ammo = ammo;
|
||||||
let ammoBonus = ammo.data.data.attackBonus;
|
title += ` [${ammo.name}]`;
|
||||||
if ( ammoBonus ) {
|
|
||||||
parts.push("@ammo");
|
|
||||||
rollData["ammo"] = ammoBonus;
|
|
||||||
title += ` [${ammo.name}]`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get pending ammunition update
|
||||||
|
const usage = this._getUsageUpdates({consumeResource: true});
|
||||||
|
if ( usage === false ) return null;
|
||||||
|
ammoUpdate = usage.resourceUpdates || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compose roll options
|
// Compose roll options
|
||||||
|
@ -710,9 +888,8 @@ export default class Item5e extends Item {
|
||||||
const roll = await d20Roll(rollConfig);
|
const roll = await d20Roll(rollConfig);
|
||||||
if ( roll === false ) return null;
|
if ( roll === false ) return null;
|
||||||
|
|
||||||
// Handle resource consumption if the attack roll was made
|
// Commit ammunition consumption on attack rolls resource consumption if the attack roll was made
|
||||||
const allowed = await this._handleResourceConsumption({isCard: false, isAttack: true});
|
if ( ammo && !isObjectEmpty(ammoUpdate) ) await ammo.update(ammoUpdate);
|
||||||
if ( allowed === false ) return null;
|
|
||||||
return roll;
|
return roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -722,12 +899,13 @@ export default class Item5e extends Item {
|
||||||
* Place a damage roll using an item (weapon, feat, power, or equipment)
|
* Place a damage roll using an item (weapon, feat, power, or equipment)
|
||||||
* Rely upon the damageRoll logic for the core implementation.
|
* Rely upon the damageRoll logic for the core implementation.
|
||||||
* @param {MouseEvent} [event] An event which triggered this roll, if any
|
* @param {MouseEvent} [event] An event which triggered this roll, if any
|
||||||
|
* @param {boolean} [critical] Should damage be rolled as a critical hit?
|
||||||
* @param {number} [powerLevel] If the item is a power, override the level for damage scaling
|
* @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 {boolean} [versatile] If the item is a weapon, roll damage using the versatile formula
|
||||||
* @param {object} [options] Additional options passed to the damageRoll function
|
* @param {object} [options] Additional options passed to the damageRoll function
|
||||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||||
*/
|
*/
|
||||||
rollDamage({event, powerLevel=null, versatile=false, options={}}={}) {
|
rollDamage({critical=false, event=null, powerLevel=null, versatile=false, options={}}={}) {
|
||||||
if ( !this.hasDamage ) throw new Error("You may not make a Damage Roll with this Item.");
|
if ( !this.hasDamage ) throw new Error("You may not make a Damage Roll with this Item.");
|
||||||
const itemData = this.data.data;
|
const itemData = this.data.data;
|
||||||
const actorData = this.actor.data.data;
|
const actorData = this.actor.data.data;
|
||||||
|
@ -739,12 +917,15 @@ export default class Item5e extends Item {
|
||||||
if ( powerLevel ) rollData.item.level = powerLevel;
|
if ( powerLevel ) rollData.item.level = powerLevel;
|
||||||
|
|
||||||
// Configure the damage roll
|
// Configure the damage roll
|
||||||
const title = `${this.name} - ${game.i18n.localize("SW5E.DamageRoll")}`;
|
const actionFlavor = game.i18n.localize(itemData.actionType === "heal" ? "SW5E.Healing" : "SW5E.DamageRoll");
|
||||||
|
const title = `${this.name} - ${actionFlavor}`;
|
||||||
const rollConfig = {
|
const rollConfig = {
|
||||||
event: event,
|
|
||||||
parts: parts,
|
|
||||||
actor: this.actor,
|
actor: this.actor,
|
||||||
|
critical: critical ?? event?.altKey ?? false,
|
||||||
data: rollData,
|
data: rollData,
|
||||||
|
event: event,
|
||||||
|
fastForward: event ? event.shiftKey || event.altKey || event.ctrlKey || event.metaKey : false,
|
||||||
|
parts: parts,
|
||||||
title: title,
|
title: title,
|
||||||
flavor: this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title,
|
flavor: this.labels.damageTypes.length ? `${title} (${this.labels.damageTypes})` : title,
|
||||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||||
|
@ -780,10 +961,13 @@ export default class Item5e extends Item {
|
||||||
parts.push(actorBonus.damage);
|
parts.push(actorBonus.damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add ammunition damage
|
// Handle ammunition damage
|
||||||
if ( this._ammo ) {
|
const ammoData = this._ammo?.data;
|
||||||
|
|
||||||
|
// only add the ammunition damage if the ammution is a consumable with type 'ammo'
|
||||||
|
if ( this._ammo && (ammoData.type === "consumable") && (ammoData.data.consumableType === "ammo") ) {
|
||||||
parts.push("@ammo");
|
parts.push("@ammo");
|
||||||
rollData["ammo"] = this._ammo.data.data.damage.parts.map(p => p[0]).join("+");
|
rollData["ammo"] = ammoData.data.damage.parts.map(p => p[0]).join("+");
|
||||||
rollConfig.flavor += ` [${this._ammo.name}]`;
|
rollConfig.flavor += ` [${this._ammo.name}]`;
|
||||||
delete this._ammo;
|
delete this._ammo;
|
||||||
}
|
}
|
||||||
|
@ -893,74 +1077,6 @@ 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
|
|
||||||
* @return {boolean} Whether further execution should be prevented
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _rollConsumable(configureDialog) {
|
|
||||||
if ( this.data.type !== "consumable" ) throw new Error("Wrong Item type");
|
|
||||||
const itemData = this.data.data;
|
|
||||||
|
|
||||||
// Determine whether to deduct uses of the item
|
|
||||||
const uses = itemData.uses || {};
|
|
||||||
const autoDestroy = uses.autoDestroy;
|
|
||||||
let usesCharges = !!uses.per && (uses.max > 0);
|
|
||||||
const recharge = itemData.recharge || {};
|
|
||||||
const usesRecharge = !!recharge.value;
|
|
||||||
|
|
||||||
// Display a configuration dialog to confirm the usage
|
|
||||||
let placeTemplate = false;
|
|
||||||
let consume = uses.autoUse || true;
|
|
||||||
if ( configureDialog ) {
|
|
||||||
const usage = await AbilityUseDialog.create(this);
|
|
||||||
if ( usage === null ) return false;
|
|
||||||
consume = Boolean(usage.get("consumeUse"));
|
|
||||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Item data
|
|
||||||
if ( consume ) {
|
|
||||||
const current = uses.value || 0;
|
|
||||||
const remaining = usesCharges ? Math.max(current - 1, 0) : current;
|
|
||||||
if ( usesRecharge ) await this.update({"data.recharge.charged": false});
|
|
||||||
else {
|
|
||||||
const q = itemData.quantity;
|
|
||||||
// Case 1, reduce charges
|
|
||||||
if ( remaining ) {
|
|
||||||
await this.update({"data.uses.value": remaining});
|
|
||||||
}
|
|
||||||
// Case 2, reduce quantity
|
|
||||||
else if ( q > 1 ) {
|
|
||||||
await this.update({"data.quantity": q - 1, "data.uses.value": uses.max || 0});
|
|
||||||
}
|
|
||||||
// Case 3, destroy the item
|
|
||||||
else if ( (q <= 1) && autoDestroy ) {
|
|
||||||
await this.actor.deleteOwnedItem(this.id);
|
|
||||||
}
|
|
||||||
// Case 4, reduce item to 0 quantity and 0 charges
|
|
||||||
else if ( (q === 1) ) {
|
|
||||||
await this.update({"data.quantity": q - 1, "data.uses.value": 0});
|
|
||||||
}
|
|
||||||
// Case 5, item unusable, display warning and do nothing
|
|
||||||
else {
|
|
||||||
ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: this.name}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe initiate template placement workflow
|
|
||||||
if ( this.hasAreaTarget && placeTemplate ) {
|
|
||||||
const template = AbilityTemplate.fromItem(this);
|
|
||||||
if ( template ) template.drawPreview();
|
|
||||||
if ( this.owner && this.owner.sheet ) this.owner.sheet.minimize();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform an ability recharge test for an item which uses the d6 recharge mechanic
|
* Perform an ability recharge test for an item which uses the d6 recharge mechanic
|
||||||
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
* @return {Promise<Roll>} A Promise which resolves to the created Roll instance
|
||||||
|
@ -1013,6 +1129,7 @@ export default class Item5e extends Item {
|
||||||
left: window.innerWidth - 710,
|
left: window.innerWidth - 710,
|
||||||
},
|
},
|
||||||
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky" ) || false,
|
halflingLucky: this.actor.getFlag("sw5e", "halflingLucky" ) || false,
|
||||||
|
reliableTalent: (this.data.data.proficient >= 1) && this.actor.getFlag("sw5e", "reliableTalent"),
|
||||||
messageData: {"flags.sw5e.roll": {type: "tool", itemId: this.id }}
|
messageData: {"flags.sw5e.roll": {type: "tool", itemId: this.id }}
|
||||||
}, options);
|
}, options);
|
||||||
rollConfig.event = options.event;
|
rollConfig.event = options.event;
|
||||||
|
@ -1094,9 +1211,14 @@ export default class Item5e extends Item {
|
||||||
case "attack":
|
case "attack":
|
||||||
await item.rollAttack({event}); break;
|
await item.rollAttack({event}); break;
|
||||||
case "damage":
|
case "damage":
|
||||||
await item.rollDamage({event, powerLevel}); break;
|
|
||||||
case "versatile":
|
case "versatile":
|
||||||
await item.rollDamage({event, powerLevel, versatile: true}); break;
|
await item.rollDamage({
|
||||||
|
critical: event.altKey,
|
||||||
|
event: event,
|
||||||
|
powerLevel: powerLevel,
|
||||||
|
versatile: action === "versatile"
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "formula":
|
case "formula":
|
||||||
await item.rollFormula({event, powerLevel}); break;
|
await item.rollFormula({event, powerLevel}); break;
|
||||||
case "save":
|
case "save":
|
||||||
|
@ -1109,7 +1231,7 @@ export default class Item5e extends Item {
|
||||||
case "toolCheck":
|
case "toolCheck":
|
||||||
await item.rollToolCheck({event}); break;
|
await item.rollToolCheck({event}); break;
|
||||||
case "placeTemplate":
|
case "placeTemplate":
|
||||||
const template = AbilityTemplate.fromItem(item);
|
const template = game.sw5e.canvas.AbilityTemplate.fromItem(item);
|
||||||
if ( template ) template.drawPreview();
|
if ( template ) template.drawPreview();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,9 @@ export default class ItemSheet5e extends ItemSheet {
|
||||||
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
|
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);
|
||||||
|
|
||||||
|
// Original maximum uses formula
|
||||||
|
if ( this.item._data.data?.uses?.max ) data.data.uses.max = this.item._data.data.uses.max;
|
||||||
|
|
||||||
// Vehicles
|
// Vehicles
|
||||||
data.isCrewed = data.item.data.activation?.type === 'crew';
|
data.isCrewed = data.item.data.activation?.type === 'crew';
|
||||||
data.isMountable = this._isItemMountable(data.item);
|
data.isMountable = this._isItemMountable(data.item);
|
||||||
|
@ -91,7 +94,7 @@ export default class ItemSheet5e extends ItemSheet {
|
||||||
ammo[i.id] = `${i.name} (${i.data.data.quantity})`;
|
ammo[i.id] = `${i.name} (${i.data.data.quantity})`;
|
||||||
}
|
}
|
||||||
return ammo;
|
return ammo;
|
||||||
}, {});
|
}, {[item._id]: `${item.name} (${item.data.quantity})`});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes
|
// Attributes
|
||||||
|
@ -335,7 +338,7 @@ export default class ItemSheet5e extends ItemSheet {
|
||||||
|
|
||||||
// Render the Trait Selector dialog
|
// Render the Trait Selector dialog
|
||||||
new TraitSelector(this.item, {
|
new TraitSelector(this.item, {
|
||||||
name: a.dataset.edit,
|
name: a.dataset.target,
|
||||||
title: label.innerText,
|
title: label.innerText,
|
||||||
choices: Object.entries(CONFIG.SW5E.skills).reduce((obj, e) => {
|
choices: Object.entries(CONFIG.SW5E.skills).reduce((obj, e) => {
|
||||||
if ( choices.includes(e[0] ) ) obj[e[0]] = e[1];
|
if ( choices.includes(e[0] ) ) obj[e[0]] = e[1];
|
||||||
|
|
|
@ -55,6 +55,5 @@ export function rollItemMacro(itemName) {
|
||||||
const item = items[0];
|
const item = items[0];
|
||||||
|
|
||||||
// Trigger the item roll
|
// Trigger the item roll
|
||||||
if ( item.data.type === "power" ) return actor.usePower(item);
|
|
||||||
return item.roll();
|
return item.roll();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* @return {Promise} A Promise which resolves once the migration is completed
|
* @return {Promise} A Promise which resolves once the migration is completed
|
||||||
*/
|
*/
|
||||||
export const migrateWorld = async function() {
|
export const migrateWorld = async function() {
|
||||||
ui.notifications.info(`Applying SW5E System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
|
ui.notifications.info(`Applying SW5e System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
|
||||||
|
|
||||||
// Migrate World Actors
|
// Migrate World Actors
|
||||||
for ( let a of game.actors.entities ) {
|
for ( let a of game.actors.entities ) {
|
||||||
|
@ -56,7 +56,7 @@ export const migrateWorld = async function() {
|
||||||
|
|
||||||
// Set the migration as complete
|
// Set the migration as complete
|
||||||
game.settings.set("sw5e", "systemMigrationVersion", game.system.data.version);
|
game.settings.set("sw5e", "systemMigrationVersion", game.system.data.version);
|
||||||
ui.notifications.info(`SW5E System Migration to version ${game.system.data.version} completed!`, {permanent: true});
|
ui.notifications.info(`SW5e System Migration to version ${game.system.data.version} completed!`, {permanent: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -120,15 +120,15 @@ export const migrateCompendium = async function(pack) {
|
||||||
/**
|
/**
|
||||||
* Migrate a single Actor entity to incorporate latest data model changes
|
* Migrate a single Actor entity to incorporate latest data model changes
|
||||||
* Return an Object of updateData to be applied
|
* Return an Object of updateData to be applied
|
||||||
* @param {Actor} actor The actor to Update
|
* @param {object} actor The actor data object to update
|
||||||
* @return {Object} The updateData to apply
|
* @return {Object} The updateData to apply
|
||||||
*/
|
*/
|
||||||
export const migrateActorData = function(actor) {
|
export const migrateActorData = function(actor) {
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
|
|
||||||
// Actor Data Updates
|
// Actor Data Updates
|
||||||
_migrateActorBonuses(actor, updateData);
|
|
||||||
_migrateActorMovement(actor, updateData);
|
_migrateActorMovement(actor, updateData);
|
||||||
|
_migrateActorSenses(actor, updateData);
|
||||||
|
|
||||||
// Migrate Owned Items
|
// Migrate Owned Items
|
||||||
if ( !actor.items ) return updateData;
|
if ( !actor.items ) return updateData;
|
||||||
|
@ -191,6 +191,7 @@ function cleanActorData(actorData) {
|
||||||
*/
|
*/
|
||||||
export const migrateItemData = function(item) {
|
export const migrateItemData = function(item) {
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
|
_migrateItemAttunement(item, updateData);
|
||||||
return updateData;
|
return updateData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -228,33 +229,83 @@ export const migrateSceneData = function(scene) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate the actor bonuses object
|
* Migrate the actor speed string to movement object
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _migrateActorBonuses(actor, updateData) {
|
function _migrateActorMovement(actorData, updateData) {
|
||||||
const b = game.system.model.Actor.character.bonuses;
|
const ad = actorData.data;
|
||||||
for ( let k of Object.keys(actor.data.bonuses || {}) ) {
|
|
||||||
if ( k in b ) updateData[`data.bonuses.${k}`] = b[k];
|
// Work is needed if old data is present
|
||||||
else updateData[`data.bonuses.-=${k}`] = null;
|
const old = actorData.type === 'vehicle' ? ad?.attributes?.speed : ad?.attributes?.speed?.value;
|
||||||
|
const hasOld = old !== undefined;
|
||||||
|
if ( hasOld ) {
|
||||||
|
|
||||||
|
// If new data is not present, migrate the old data
|
||||||
|
const hasNew = ad?.attributes?.movement?.walk !== undefined;
|
||||||
|
if ( !hasNew && (typeof old === "string") ) {
|
||||||
|
const s = (old || "").split(" ");
|
||||||
|
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the old attribute
|
||||||
|
updateData["data.attributes.-=speed"] = null;
|
||||||
}
|
}
|
||||||
|
return updateData
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate the actor bonuses object
|
* Migrate the actor traits.senses string to attributes.senses object
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _migrateActorMovement(actor, updateData) {
|
function _migrateActorSenses(actor, updateData) {
|
||||||
if ( actor.data.attributes?.movement?.walk !== undefined ) return;
|
const ad = actor.data;
|
||||||
const s = (actor.data.attributes?.speed?.value || "").split(" ");
|
if ( ad?.traits?.senses === undefined ) return;
|
||||||
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
|
const original = ad.traits.senses || "";
|
||||||
|
|
||||||
|
// Try to match old senses with the format like "Darkvision 60 ft, Blindsight 30 ft"
|
||||||
|
const pattern = /([A-z]+)\s?([0-9]+)\s?([A-z]+)?/;
|
||||||
|
let wasMatched = false;
|
||||||
|
|
||||||
|
// Match each comma-separated term
|
||||||
|
for ( let s of original.split(",") ) {
|
||||||
|
s = s.trim();
|
||||||
|
const match = s.match(pattern);
|
||||||
|
if ( !match ) continue;
|
||||||
|
const type = match[1].toLowerCase();
|
||||||
|
if ( type in CONFIG.SW5E.senses ) {
|
||||||
|
updateData[`data.attributes.senses.${type}`] = Number(match[2]).toNearest(0.5);
|
||||||
|
wasMatched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing was matched, but there was an old string - put the whole thing in "special"
|
||||||
|
if ( !wasMatched && !!original ) {
|
||||||
|
updateData["data.attributes.senses.special"] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the old traits.senses string once the migration is complete
|
||||||
|
updateData["data.traits.-=senses"] = null;
|
||||||
|
return updateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the old data.attuned boolean
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _migrateItemAttunement(item, updateData) {
|
||||||
|
if ( item.data.attuned === undefined ) return;
|
||||||
|
updateData["data.attunement"] = CONFIG.SW5E.attunementTypes.NONE;
|
||||||
|
updateData["data.-=attuned"] = null;
|
||||||
|
return updateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A general tool to purge flags from all entities in a Compendium pack.
|
* A general tool to purge flags from all entities in a Compendium pack.
|
||||||
* @param {Compendium} pack The compendium pack to clean
|
* @param {Compendium} pack The compendium pack to clean
|
||||||
|
|
|
@ -29,8 +29,8 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
||||||
|
|
||||||
// Additional type-specific data
|
// Additional type-specific data
|
||||||
switch ( templateShape ) {
|
switch ( templateShape ) {
|
||||||
case "cone": // 5e cone RAW should be 53.13 degrees
|
case "cone":
|
||||||
templateData.angle = 53.13;
|
templateData.angle = CONFIG.MeasuredTemplate.defaults.angle;
|
||||||
break;
|
break;
|
||||||
case "rect": // 5e rectangular AoEs are always cubes
|
case "rect": // 5e rectangular AoEs are always cubes
|
||||||
templateData.distance = Math.hypot(target.value, target.value);
|
templateData.distance = Math.hypot(target.value, target.value);
|
||||||
|
@ -45,7 +45,10 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the template constructed from the item data
|
// Return the template constructed from the item data
|
||||||
return new this(templateData);
|
const template = new this(templateData);
|
||||||
|
template.item = item;
|
||||||
|
template.actorSheet = item.actor?.sheet || null;
|
||||||
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
@ -55,9 +58,16 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
||||||
*/
|
*/
|
||||||
drawPreview() {
|
drawPreview() {
|
||||||
const initialLayer = canvas.activeLayer;
|
const initialLayer = canvas.activeLayer;
|
||||||
|
|
||||||
|
// Draw the template and switch to the template layer
|
||||||
this.draw();
|
this.draw();
|
||||||
this.layer.activate();
|
this.layer.activate();
|
||||||
this.layer.preview.addChild(this);
|
this.layer.preview.addChild(this);
|
||||||
|
|
||||||
|
// Hide the sheet that originated the preview
|
||||||
|
if ( this.actorSheet ) this.actorSheet.minimize();
|
||||||
|
|
||||||
|
// Activate interactivity
|
||||||
this.activatePreviewListeners(initialLayer);
|
this.activatePreviewListeners(initialLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +102,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
||||||
canvas.app.view.oncontextmenu = null;
|
canvas.app.view.oncontextmenu = null;
|
||||||
canvas.app.view.onwheel = null;
|
canvas.app.view.onwheel = null;
|
||||||
initialLayer.activate();
|
initialLayer.activate();
|
||||||
|
this.actorSheet.maximize();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Confirm the workflow (left-click)
|
// Confirm the workflow (left-click)
|
||||||
|
|
|
@ -14,12 +14,13 @@ export const preloadHandlebarsTemplates = async function() {
|
||||||
"systems/sw5e/templates/actors/oldActor/parts/actor-inventory.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-features.html",
|
||||||
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
|
"systems/sw5e/templates/actors/oldActor/parts/actor-powerbook.html",
|
||||||
|
"systems/sw5e/templates/actors/oldActor/parts/actor-notes.html",
|
||||||
|
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-biography.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-core.html",
|
||||||
|
"systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html",
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-features.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-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-powerbook.html",
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-resources.html",
|
||||||
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",
|
"systems/sw5e/templates/actors/newActor/parts/swalt-traits.html",
|
||||||
|
|
75
package-lock.json
generated
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"name": "sw5e",
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -89,7 +90,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||||
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
|
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"micromatch": "^3.1.4",
|
"micromatch": "^3.1.4",
|
||||||
"normalize-path": "^2.1.1"
|
"normalize-path": "^2.1.1"
|
||||||
|
@ -99,7 +99,6 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||||
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"remove-trailing-separator": "^1.0.1"
|
"remove-trailing-separator": "^1.0.1"
|
||||||
}
|
}
|
||||||
|
@ -272,7 +271,6 @@
|
||||||
"version": "0.11.2",
|
"version": "0.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
|
||||||
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
|
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"cache-base": "^1.0.1",
|
"cache-base": "^1.0.1",
|
||||||
"class-utils": "^0.3.5",
|
"class-utils": "^0.3.5",
|
||||||
|
@ -287,7 +285,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
||||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-descriptor": "^1.0.0"
|
"is-descriptor": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -296,7 +293,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
||||||
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^6.0.0"
|
"kind-of": "^6.0.0"
|
||||||
}
|
}
|
||||||
|
@ -305,7 +301,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
||||||
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^6.0.0"
|
"kind-of": "^6.0.0"
|
||||||
}
|
}
|
||||||
|
@ -314,7 +309,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
||||||
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-accessor-descriptor": "^1.0.0",
|
"is-accessor-descriptor": "^1.0.0",
|
||||||
"is-data-descriptor": "^1.0.0",
|
"is-data-descriptor": "^1.0.0",
|
||||||
|
@ -332,6 +326,7 @@
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"file-uri-to-path": "1.0.0"
|
"file-uri-to-path": "1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -349,7 +344,6 @@
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"arr-flatten": "^1.1.0",
|
"arr-flatten": "^1.1.0",
|
||||||
"array-unique": "^0.3.2",
|
"array-unique": "^0.3.2",
|
||||||
|
@ -367,7 +361,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -388,7 +381,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||||
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
|
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"collection-visit": "^1.0.0",
|
"collection-visit": "^1.0.0",
|
||||||
"component-emitter": "^1.2.1",
|
"component-emitter": "^1.2.1",
|
||||||
|
@ -419,7 +411,6 @@
|
||||||
"version": "2.1.8",
|
"version": "2.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||||
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
"integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "^2.0.0",
|
"anymatch": "^2.0.0",
|
||||||
"async-each": "^1.0.1",
|
"async-each": "^1.0.1",
|
||||||
|
@ -697,6 +688,7 @@
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
|
||||||
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
|
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"prr": "~1.0.1"
|
"prr": "~1.0.1"
|
||||||
}
|
}
|
||||||
|
@ -782,7 +774,6 @@
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||||
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
|
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^2.3.3",
|
"debug": "^2.3.3",
|
||||||
"define-property": "^0.2.5",
|
"define-property": "^0.2.5",
|
||||||
|
@ -797,7 +788,6 @@
|
||||||
"version": "0.2.5",
|
"version": "0.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
||||||
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-descriptor": "^0.1.0"
|
"is-descriptor": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -806,7 +796,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -864,7 +853,6 @@
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
|
||||||
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
|
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"array-unique": "^0.3.2",
|
"array-unique": "^0.3.2",
|
||||||
"define-property": "^1.0.0",
|
"define-property": "^1.0.0",
|
||||||
|
@ -880,7 +868,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
||||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-descriptor": "^1.0.0"
|
"is-descriptor": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -889,7 +876,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -898,7 +884,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
||||||
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^6.0.0"
|
"kind-of": "^6.0.0"
|
||||||
}
|
}
|
||||||
|
@ -907,7 +892,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
||||||
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^6.0.0"
|
"kind-of": "^6.0.0"
|
||||||
}
|
}
|
||||||
|
@ -916,7 +900,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
||||||
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-accessor-descriptor": "^1.0.0",
|
"is-accessor-descriptor": "^1.0.0",
|
||||||
"is-data-descriptor": "^1.0.0",
|
"is-data-descriptor": "^1.0.0",
|
||||||
|
@ -944,13 +927,13 @@
|
||||||
"file-uri-to-path": {
|
"file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"extend-shallow": "^2.0.1",
|
"extend-shallow": "^2.0.1",
|
||||||
"is-number": "^3.0.0",
|
"is-number": "^3.0.0",
|
||||||
|
@ -962,7 +945,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -982,7 +964,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
|
||||||
"integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
|
"integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"detect-file": "^1.0.0",
|
"detect-file": "^1.0.0",
|
||||||
"is-glob": "^4.0.0",
|
"is-glob": "^4.0.0",
|
||||||
|
@ -1055,7 +1036,6 @@
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
|
@ -1130,7 +1110,6 @@
|
||||||
"version": "5.0.5",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz",
|
||||||
"integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==",
|
"integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "^2.0.0",
|
"anymatch": "^2.0.0",
|
||||||
"async-done": "^1.2.0",
|
"async-done": "^1.2.0",
|
||||||
|
@ -1180,7 +1159,6 @@
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
|
||||||
"integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==",
|
"integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob-watcher": "^5.0.3",
|
"glob-watcher": "^5.0.3",
|
||||||
"gulp-cli": "^2.2.0",
|
"gulp-cli": "^2.2.0",
|
||||||
|
@ -1192,7 +1170,6 @@
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz",
|
||||||
"integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==",
|
"integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-colors": "^1.0.1",
|
"ansi-colors": "^1.0.1",
|
||||||
"archy": "^1.0.0",
|
"archy": "^1.0.0",
|
||||||
|
@ -1220,7 +1197,6 @@
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz",
|
||||||
"integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==",
|
"integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"accord": "^0.29.0",
|
"accord": "^0.29.0",
|
||||||
"less": "2.6.x || ^3.7.1",
|
"less": "2.6.x || ^3.7.1",
|
||||||
|
@ -1256,7 +1232,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
|
||||||
"integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
|
"integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"get-value": "^2.0.6",
|
"get-value": "^2.0.6",
|
||||||
"has-values": "^1.0.0",
|
"has-values": "^1.0.0",
|
||||||
|
@ -1267,7 +1242,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
|
||||||
"integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
|
"integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-number": "^3.0.0",
|
"is-number": "^3.0.0",
|
||||||
"kind-of": "^4.0.0"
|
"kind-of": "^4.0.0"
|
||||||
|
@ -1277,7 +1251,6 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
|
||||||
"integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
|
"integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-buffer": "^1.1.5"
|
"is-buffer": "^1.1.5"
|
||||||
}
|
}
|
||||||
|
@ -1300,7 +1273,8 @@
|
||||||
"image-size": {
|
"image-size": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w="
|
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"indx": {
|
"indx": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.3",
|
||||||
|
@ -1322,9 +1296,9 @@
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||||
},
|
},
|
||||||
"interpret": {
|
"interpret": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
|
@ -1474,7 +1448,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||||
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"kind-of": "^3.0.2"
|
"kind-of": "^3.0.2"
|
||||||
},
|
},
|
||||||
|
@ -1483,7 +1456,6 @@
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-buffer": "^1.1.5"
|
"is-buffer": "^1.1.5"
|
||||||
}
|
}
|
||||||
|
@ -1640,7 +1612,6 @@
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz",
|
||||||
"integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==",
|
"integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"extend": "^3.0.0",
|
"extend": "^3.0.0",
|
||||||
"findup-sync": "^3.0.0",
|
"findup-sync": "^3.0.0",
|
||||||
|
@ -1708,6 +1679,7 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"pify": "^4.0.1",
|
"pify": "^4.0.1",
|
||||||
"semver": "^5.6.0"
|
"semver": "^5.6.0"
|
||||||
|
@ -1716,7 +1688,8 @@
|
||||||
"pify": {
|
"pify": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1745,7 +1718,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
|
||||||
"integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=",
|
"integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"findup-sync": "^2.0.0",
|
"findup-sync": "^2.0.0",
|
||||||
"micromatch": "^3.0.4",
|
"micromatch": "^3.0.4",
|
||||||
|
@ -1757,7 +1729,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
|
||||||
"integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
|
"integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"detect-file": "^1.0.0",
|
"detect-file": "^1.0.0",
|
||||||
"is-glob": "^3.1.0",
|
"is-glob": "^3.1.0",
|
||||||
|
@ -1769,7 +1740,6 @@
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
||||||
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
|
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extglob": "^2.1.0"
|
"is-extglob": "^2.1.0"
|
||||||
}
|
}
|
||||||
|
@ -1780,7 +1750,6 @@
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"arr-diff": "^4.0.0",
|
"arr-diff": "^4.0.0",
|
||||||
"array-unique": "^0.3.2",
|
"array-unique": "^0.3.2",
|
||||||
|
@ -1800,7 +1769,8 @@
|
||||||
"mime": {
|
"mime": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
|
@ -1842,13 +1812,13 @@
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.14.2",
|
"version": "2.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
|
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
"integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
|
"integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"arr-diff": "^4.0.0",
|
"arr-diff": "^4.0.0",
|
||||||
"array-unique": "^0.3.2",
|
"array-unique": "^0.3.2",
|
||||||
|
@ -2187,7 +2157,8 @@
|
||||||
"prr": {
|
"prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
|
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
@ -2245,7 +2216,6 @@
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
|
||||||
"integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
|
"integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.11",
|
"graceful-fs": "^4.1.11",
|
||||||
"micromatch": "^3.1.10",
|
"micromatch": "^3.1.10",
|
||||||
|
@ -2428,7 +2398,6 @@
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||||
"integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
|
"integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"base": "^0.11.1",
|
"base": "^0.11.1",
|
||||||
"debug": "^2.2.0",
|
"debug": "^2.2.0",
|
||||||
|
@ -2444,7 +2413,6 @@
|
||||||
"version": "0.2.5",
|
"version": "0.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
||||||
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-descriptor": "^0.1.0"
|
"is-descriptor": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -2453,7 +2421,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-extendable": "^0.1.0"
|
"is-extendable": "^0.1.0"
|
||||||
}
|
}
|
||||||
|
@ -2787,7 +2754,6 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
||||||
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
|
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-number": "^3.0.0",
|
"is-number": "^3.0.0",
|
||||||
"repeat-string": "^1.6.1"
|
"repeat-string": "^1.6.1"
|
||||||
|
@ -2857,7 +2823,8 @@
|
||||||
"uglify-to-browserify": {
|
"uglify-to-browserify": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
|
||||||
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc="
|
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"unc-path-regex": {
|
"unc-path-regex": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
|
9
package.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "sw5e",
|
||||||
|
"description": "This game system for [Foundry Virtual Tabletop](http://foundryvtt.com) provides character sheet and game system \r support for the SW5E roleplaying game.",
|
||||||
|
"main": "sw5e.js",
|
||||||
|
"dependencies": {
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"gulp-less": "^4.0.1"
|
||||||
|
}
|
||||||
|
}
|
BIN
packs/Icons/Archetypes/Archaeologist Pursuit.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Archetypes/Construction Engineering.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Archetypes/Kro Var Order.webp
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
BIN
packs/Icons/Archetypes/Vonil-Ishu Form.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Archetypes/Way of Tutelage.webp
Normal file
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 770 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
packs/Icons/Feats/Adaptive Training.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Augmented Cyborg.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Feats/Blinding Agility.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Climber.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Close Quarters Caster.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Feats/Combat Caster.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Feats/Companion Keeper.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Cunning Intellect.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Customized Droid.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Dual Focused Caster.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Exalted Awareness.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Expert Potency.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Focused Vitality.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Investigative Attunement.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Kinetic Stoicism.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Feats/Mariner.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
packs/Icons/Feats/Martial Adept.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Meditative Mindfulness.webp
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
packs/Icons/Feats/Mounted Caster.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
packs/Icons/Feats/Overwhelming Presence.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Precision Applications.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Prone Combatant.webp
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
packs/Icons/Feats/Quick Caster.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
packs/Icons/Feats/Relentless Pursuer.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Savage Shorty.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Serene Resolve.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Shard Modification.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Sniping Caster.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Feats/Tiny Terror.webp
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
packs/Icons/Feats/Tireless Outrider.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
packs/Icons/Feats/Titan's Power.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/Unnatural Resilience.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
packs/Icons/Feats/Versatile Design.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
packs/Icons/Feats/War Caster.webp
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
packs/Icons/Lightsaber Forms/Vonil-Ishu Form.webp
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
packs/Icons/Martial Blasters/Blaster Cannon.webp
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
packs/Icons/Martial Blasters/Carbine Rifle.webp
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
packs/Icons/Martial Blasters/Chaingun.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
packs/Icons/Martial Blasters/Cycler Rifle.webp
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
packs/Icons/Martial Blasters/Heavy Bowcaster.webp
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
packs/Icons/Martial Blasters/Heavy Repeater.webp
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
packs/Icons/Martial Blasters/Heavy Shotgun.webp
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
packs/Icons/Martial Blasters/Heavy Slugpistol.webp
Normal file
After Width: | Height: | Size: 8.6 KiB |
BIN
packs/Icons/Martial Blasters/Hunting Rifle.webp
Normal file
After Width: | Height: | Size: 6.9 KiB |