forked from GitHub-Mirrors/foundry-sw5e
Merge pull request #221 from unrealkakeman89/Develop-0.8
Updated to 0.8
This commit is contained in:
commit
76ef89b518
89 changed files with 4053 additions and 2237 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -22,6 +22,9 @@
|
|||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
# Mac-OS file
|
||||
.DS_Store
|
||||
|
||||
# IDE Folders
|
||||
.idea/
|
||||
.vs/
|
||||
|
|
43
lang/en.json
43
lang/en.json
|
@ -43,8 +43,10 @@
|
|||
"SETTINGS.5eInitTBN": "Initiative Dexterity Tiebreaker",
|
||||
"SETTINGS.5eNoExpL": "Remove experience bars from character sheets.",
|
||||
"SETTINGS.5eNoExpN": "Disable Experience Tracking",
|
||||
"SETTINGS.5eReset": "Reset",
|
||||
"SETTINGS.5eRestEpic": "Epic Heroism (LR: 1 hour, SR: 1 min)",
|
||||
"SETTINGS.5eRestGritty": "Gritty Realism (LR: 7 days, SR: 8 hours)",
|
||||
"SETTINGS.5eUndoChanges": "Undo Changes",
|
||||
"SETTINGS.5eRestL": "Configure which rest variant should be used for games within this system.",
|
||||
"SETTINGS.5eRestN": "Rest Variant",
|
||||
"SETTINGS.5eRestPHB": "Player's Handbook (LR: 8 hours, SR: 1 hour)",
|
||||
|
@ -104,7 +106,10 @@
|
|||
"SW5E.ActionUtil": "Utility",
|
||||
"SW5E.ActionWarningNoItem": "The requested item {item} no longer exists on Actor {name}",
|
||||
"SW5E.ActionWarningNoToken": "You must have one or more controlled Tokens in order to use this option.",
|
||||
"SW5E.ActorWarningInvalidItem": "{itemType} items cannot be added to a {actorType}.",
|
||||
"SW5E.Add": "Add",
|
||||
"SW5E.AddEmbeddedItemPromptHint": "Do you want to add these items to your character sheet?",
|
||||
"SW5E.SelectItemsPromptTitle": "Select Items",
|
||||
"SW5E.AdditionalNotes": "Additional Notes",
|
||||
"SW5E.Advantage": "Advantage",
|
||||
"SW5E.Alignment": "Alignment",
|
||||
|
@ -118,6 +123,7 @@
|
|||
"SW5E.AlignmentND": "Neutral Dark",
|
||||
"SW5E.AlignmentNL": "Neutral Light",
|
||||
"SW5E.Appearance": "Appearance",
|
||||
"SW5E.Apply": "Apply",
|
||||
"SW5E.ArchetypeName": "Archetype Name",
|
||||
"SW5E.Archetypes": "Archetypes",
|
||||
"SW5E.ArmorClass": "Armor Class",
|
||||
|
@ -199,7 +205,11 @@
|
|||
"SW5E.ChatContextHealing": "Apply Healing",
|
||||
"SW5E.ChatFlavor": "Chat Message Flavor",
|
||||
"SW5E.ClassLevels": "Class Levels",
|
||||
"SW5E.ClassMakeOriginal": "Original Class",
|
||||
"SW5E.ClassMakeOriginalHint": "First class taken by character used to determine certain class traits when multiclassing.",
|
||||
"SW5E.ClassName": "Class Name",
|
||||
"SW5E.ClassOriginal": "Original Class",
|
||||
"SW5E.ClassSaves": "Saving Throws",
|
||||
"SW5E.ClassSkillsChosen": "Chosen Class Skills",
|
||||
"SW5E.ClassSkillsNumber": "Number of Starting Skills",
|
||||
"SW5E.Collapse": "Collapse/Expand",
|
||||
|
@ -257,6 +267,31 @@
|
|||
"SW5E.ConUnconscious": "Unconscious",
|
||||
"SW5E.Core": "Core",
|
||||
"SW5E.CostGP": "Cost (CR)",
|
||||
"SW5E.CreatureAberration": "Aberration",
|
||||
"SW5E.CreatureAberrationPl": "Aberrations",
|
||||
"SW5E.CreatureBeast": "Beast",
|
||||
"SW5E.CreatureBeastPl": "Beasts",
|
||||
"SW5E.CreatureConstruct": "Construct",
|
||||
"SW5E.CreatureConstructPl": "Constructs",
|
||||
"SW5E.CreatureDroid": "Droid",
|
||||
"SW5E.CreatureDroidPl": "Droids",
|
||||
"SW5E.CreatureForceEntity": "Force Entity",
|
||||
"SW5E.CreatureForceEntityPl": "Force Entities",
|
||||
"SW5E.CreatureHumanoid": "Humanoid",
|
||||
"SW5E.CreatureHumanoidPl": "Humanoids",
|
||||
"SW5E.CreaturePlant": "Plant",
|
||||
"SW5E.CreaturePlantPl": "Plants",
|
||||
"SW5E.CreatureUndead": "Undead",
|
||||
"SW5E.CreatureUndeadPl": "Undead",
|
||||
"SW5E.CreatureType": "Creature Type",
|
||||
"SW5E.CreatureTypeTitle": "Configure Creature Type",
|
||||
"SW5E.CreatureSwarm": "Swarm",
|
||||
"SW5E.CreatureSwarmSize": "Swarm Size",
|
||||
"SW5E.CreatureSwarmPhrase": "Swarm of {size} {type}",
|
||||
"SW5E.CreatureTypeConfig": "Configure Creature Type",
|
||||
"SW5E.CreatureTypeSelectorCustom": "Custom Type",
|
||||
"SW5E.CreatureTypeSelectorSubtype": "Subtype",
|
||||
"SW5E.Crewed": "Crewed",
|
||||
"SW5E.Cover": "Cover",
|
||||
"SW5E.CoverHalf": "Half",
|
||||
"SW5E.CoverThreeQuarters": "Three Quarters",
|
||||
|
@ -264,6 +299,7 @@
|
|||
"SW5E.CrewCap": "Crew Capacity",
|
||||
"SW5E.Critical": "Critical",
|
||||
"SW5E.CriticalHit": "Critical Hit",
|
||||
"SW5E.PowerfulCritical": "Powerful Critical",
|
||||
"SW5E.Currency": "Currency",
|
||||
"SW5E.CurrencyConvert": "Convert All Currency",
|
||||
"SW5E.CurrencyConvertHint": "Convert all carried currency to the highest possible denomination to reduce the amount of coinage carried by the character. Be wary, this action cannot be undone.",
|
||||
|
@ -493,6 +529,10 @@
|
|||
"SW5E.HealthConditions": "Health Conditions",
|
||||
"SW5E.HealthFormula": "Health Formula",
|
||||
"SW5E.HitDice": "Hit Dice",
|
||||
"SW5E.HitDiceConfig": "Adjust Hit Dice",
|
||||
"SW5E.HitDiceConfigHint": "Adjust remaining hit dice levels for each class.",
|
||||
"SW5E.HitDiceMax": "Maximum Hit Dice",
|
||||
"SW5E.HitDiceRemaining": "Remaining Hit Dice",
|
||||
"SW5E.HitDiceRoll": "Roll Hit Dice",
|
||||
"SW5E.HitDiceUsed": "Hit Dice Used",
|
||||
"SW5E.HitDiceWarn": "{name} has no available {formula} Hit Dice remaining!",
|
||||
|
@ -866,7 +906,7 @@
|
|||
"SW5E.Role": "Role",
|
||||
"SW5E.RolePl": "Roles",
|
||||
"SW5E.Roll": "Roll",
|
||||
"SW5E.RollExample": "e.g. +1d4",
|
||||
"SW5E.RollExample": "e.g. 1d4",
|
||||
"SW5E.RollMode": "Roll Mode",
|
||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||
"SW5E.Save": "Save",
|
||||
|
@ -935,6 +975,7 @@
|
|||
"SW5E.SkillPrc": "Perception",
|
||||
"SW5E.SkillPrf": "Performance",
|
||||
"SW5E.SkillPromptTitle": "{skill} Skill Check",
|
||||
"SW5E.Skip": "Skip",
|
||||
"SW5E.Skills": "Skills",
|
||||
"SW5E.SkillSlt": "Sleight of Hand",
|
||||
"SW5E.SkillSte": "Stealth",
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
}
|
||||
|
||||
// Movement Configuration
|
||||
.movement {
|
||||
.movement, .hit-dice {
|
||||
h4.attribute-name {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -655,6 +655,15 @@
|
|||
// Empty powerbook controls
|
||||
.powerbook-empty .item-controls { flex: 1; }
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Features Tab */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
// Original class icon
|
||||
.features i.original-class {
|
||||
color: #4b4a44
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* TinyMCE */
|
||||
/* ----------------------------------------- */
|
||||
|
@ -678,4 +687,4 @@
|
|||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@
|
|||
|
||||
.sw5e {
|
||||
.window-content {
|
||||
background: @sheetBackground;
|
||||
font-size: 13px;
|
||||
color: @colorDark;
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
|
@ -44,6 +42,8 @@
|
|||
select:disabled,
|
||||
textarea:disabled {
|
||||
color: @colorOlive;
|
||||
border: 1px solid transparent !important;
|
||||
outline: none !important;
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: none !important;
|
||||
|
@ -58,28 +58,6 @@
|
|||
border: @borderGroove;
|
||||
}
|
||||
|
||||
// Checkbox Labels
|
||||
// TODO: THIS CAN BE MOSTLY REMOVED NOW THAT IT IS IN CORE, see core forms.less
|
||||
label.checkbox {
|
||||
flex: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 11px;
|
||||
> input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 2px 0 0;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
&.right > input[type="checkbox"] {
|
||||
margin: 0 0 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Form Groups */
|
||||
.form-group {
|
||||
label {
|
||||
|
@ -98,11 +76,12 @@
|
|||
|
||||
// Stacked Groups
|
||||
.form-group.stacked {
|
||||
label {
|
||||
> label {
|
||||
flex: 0 0 100%;
|
||||
margin: 0;
|
||||
}
|
||||
label.checkbox {
|
||||
label.checkbox,
|
||||
label.radio {
|
||||
flex: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -131,6 +110,34 @@
|
|||
}
|
||||
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Hit Dice Config Sheet Specifically */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.sw5e.hd-config {
|
||||
.form-group {
|
||||
button.increment, button.decrement {
|
||||
flex: 0 0 1rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
button.decrement {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
span.sep {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 0 0 2rem;
|
||||
text-align: center;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Entity Sheets Specifically */
|
||||
/* ----------------------------------------- */
|
||||
|
@ -475,7 +482,7 @@
|
|||
/* Trait Selector
|
||||
/* ----------------------------------------- */
|
||||
|
||||
#trait-selector {
|
||||
.trait-selector {
|
||||
.trait-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
@ -488,6 +495,59 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Actor Type Config Sheet Specifically */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.actor-type {
|
||||
.trait-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
li {
|
||||
flex-basis: 50%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
li.form-group {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
label.radio {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
font-weight: normal;
|
||||
> input[type="radio"] {
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
}
|
||||
li.custom-type input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* Add Feature Prompt Specifically */
|
||||
/* ----------------------------------------- */
|
||||
|
||||
.sw5e.select-items-prompt {
|
||||
.dialog-content {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.item-name > label, .item-image, input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-name > label {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
/* HUD
|
||||
/* ----------------------------------------- */
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
|
||||
// Custom Resources
|
||||
.resource .attribute-value {
|
||||
input {
|
||||
> input {
|
||||
flex: 0 0 25%;
|
||||
}
|
||||
label.recharge {
|
||||
|
@ -99,6 +99,7 @@
|
|||
font-size: 11px;
|
||||
text-align: center;
|
||||
color: @colorOlive;
|
||||
align-items: center;
|
||||
input[type="checkbox"] {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
|
|
|
@ -106,17 +106,17 @@
|
|||
&:nth-child(even) {
|
||||
width: 150px;
|
||||
margin: 0.5em 0.5em;
|
||||
padding: 0px 10px 0px 10px;
|
||||
padding: 0 10px 0 10px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
thead {
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
th {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-weight: bold;
|
||||
|
@ -129,7 +129,7 @@
|
|||
&:nth-child(even) {
|
||||
width: 150px;
|
||||
margin: 0.5em 0.5em;
|
||||
padding: 0px 10px 0px 10px;
|
||||
padding: 0 10px 0 10px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +137,7 @@
|
|||
.medtable {
|
||||
table {
|
||||
width: 500px;
|
||||
border: 0px;
|
||||
border: 0;
|
||||
margin: 0.5em 0.5em;
|
||||
}
|
||||
td {
|
||||
|
@ -149,17 +149,17 @@
|
|||
&:nth-child(even) {
|
||||
width: 450px;
|
||||
margin: 0.5em 0.5em;
|
||||
padding: 0px 10px 0px 0px;
|
||||
padding: 0 10px 0 0;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
thead {
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
th {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-weight: bold;
|
||||
|
@ -174,8 +174,8 @@
|
|||
}
|
||||
.classtable {
|
||||
blockquote {
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
background-color: #bdc8cc;
|
||||
width: 600px;
|
||||
h3 {
|
||||
|
@ -189,8 +189,8 @@
|
|||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
margin: 0.5em 0;
|
||||
|
@ -200,7 +200,7 @@
|
|||
thead {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-style: normal;
|
||||
|
@ -209,7 +209,7 @@
|
|||
th {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-style: normal;
|
||||
|
@ -246,7 +246,7 @@
|
|||
width: 100%;
|
||||
line-height: 18px;
|
||||
margin-bottom: 15px;
|
||||
border: 0 0 0 0;
|
||||
border: 0;
|
||||
border-bottom: none;
|
||||
overflow-x: auto;
|
||||
tbody {
|
||||
|
|
|
@ -30,5 +30,28 @@
|
|||
|
||||
.summary {
|
||||
font-size: 18px;
|
||||
|
||||
li.creature-type {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 1em;
|
||||
padding: 0 3px;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.config-button {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 2em;
|
||||
}
|
||||
&:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -140,6 +140,7 @@
|
|||
height: auto;
|
||||
.russoOne(17px);
|
||||
line-height: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.proficiency {
|
||||
|
@ -184,7 +185,7 @@
|
|||
display: inline-block;
|
||||
text-align: right;
|
||||
|
||||
padding: 0px 3px;
|
||||
padding: 0 3px;
|
||||
|
||||
&:last-child {
|
||||
text-align: left;
|
||||
|
@ -779,7 +780,7 @@
|
|||
display: block;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding: 0px 3px;
|
||||
padding: 0 3px;
|
||||
&:last-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -955,7 +956,7 @@
|
|||
display: block;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding: 0px 3px;
|
||||
padding: 0 3px;
|
||||
&:last-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -1053,10 +1054,35 @@
|
|||
h1.character-name {
|
||||
align-self: auto;
|
||||
}
|
||||
.npc-size {
|
||||
.npc-size, .creature-type {
|
||||
.russoOne(18px);
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
div.creature-type {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 1px 4px;
|
||||
border: 1px solid transparent;
|
||||
overflow-x: auto;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.config-button {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 2em;
|
||||
}
|
||||
&:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.attributes {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
footer {
|
||||
|
|
|
@ -408,6 +408,9 @@
|
|||
&.npc {
|
||||
.swalt-sheet {
|
||||
header {
|
||||
div.creature-type:hover {
|
||||
border-color: @inputBorderFocus;
|
||||
}
|
||||
.experience {
|
||||
color: @actorProficiencyTextColor;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
input[type="text"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], select, textarea {
|
||||
input[type="text"], input[type="number"], input[type="password"], input[type="date"], input[type="time"], select, textarea, .roundTransition {
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
padding-bottom: 4px;
|
||||
.folder {
|
||||
& > .folder-header {
|
||||
line-height: default;
|
||||
line-height: initial;
|
||||
padding: 0 0 0 8px;
|
||||
position: relative;
|
||||
border: none;
|
||||
|
|
|
@ -302,7 +302,7 @@
|
|||
}
|
||||
.folder {
|
||||
& > .folder-header {
|
||||
line-height: default;
|
||||
line-height: initial;
|
||||
padding: 0 0 0 8px;
|
||||
position: relative;
|
||||
border: none;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,10 @@
|
|||
import Item5e from "../../../item/entity.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||
import {SW5E} from '../../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
|
||||
|
@ -46,6 +48,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A set of item types that should be prevented from being dropped on this type of actor sheet.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
static unsupportedItemTypes = new Set();
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html";
|
||||
|
@ -55,44 +65,51 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
getData(options) {
|
||||
|
||||
// Basic data
|
||||
let isOwner = this.entity.owner;
|
||||
let isOwner = this.actor.isOwner;
|
||||
const data = {
|
||||
owner: isOwner,
|
||||
limited: this.entity.limited,
|
||||
limited: this.actor.limited,
|
||||
options: this.options,
|
||||
editable: this.isEditable,
|
||||
cssClass: isOwner ? "editable" : "locked",
|
||||
isCharacter: this.entity.data.type === "character",
|
||||
isNPC: this.entity.data.type === "npc",
|
||||
isStarship: this.entity.data.type === "starship",
|
||||
isVehicle: this.entity.data.type === 'vehicle',
|
||||
isCharacter: this.actor.data.type === "character",
|
||||
isNPC: this.actor.data.type === "npc",
|
||||
isStarship: this.actor.data.type === "starship",
|
||||
isVehicle: this.actor.data.type === 'vehicle',
|
||||
config: CONFIG.SW5E,
|
||||
rollData: this.actor.getRollData.bind(this.actor)
|
||||
};
|
||||
|
||||
// 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;
|
||||
});
|
||||
// The Actor's data
|
||||
const actorData = this.actor.data.toObject(false);
|
||||
data.actor = actorData;
|
||||
data.data = actorData.data;
|
||||
|
||||
// Owned Items
|
||||
data.items = actorData.items;
|
||||
for ( let i of data.items ) {
|
||||
const item = this.actor.items.get(i._id);
|
||||
i.labels = item.labels;
|
||||
}
|
||||
data.items.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
data.data = data.actor.data;
|
||||
|
||||
// Labels and filters
|
||||
data.labels = this.actor.labels || {};
|
||||
data.filters = this._filters;
|
||||
|
||||
// Ability Scores
|
||||
for ( let [a, abl] of Object.entries(data.actor.data.abilities)) {
|
||||
for ( let [a, abl] of Object.entries(actorData.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)) {
|
||||
if (actorData.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(actorData.data.skills)) {
|
||||
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||
skl.icon = this._getProficiencyIcon(skl.value);
|
||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||
|
@ -105,19 +122,19 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
data.movement = this._getMovementSpeed(actorData);
|
||||
|
||||
// Senses
|
||||
data.senses = this._getSenses(data.actor);
|
||||
data.senses = this._getSenses(actorData);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
this._prepareTraits(actorData.data.traits);
|
||||
|
||||
// Prepare owned items
|
||||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
|
@ -223,12 +240,13 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/**
|
||||
* 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
|
||||
* @param {Object} data The Actor data being prepared
|
||||
* @param {Array} powers The power data being prepared
|
||||
* @param {string} school The school of the powerbook being prepared
|
||||
* @private
|
||||
*/
|
||||
_preparePowerbook(data, powers, school) {
|
||||
const owner = this.actor.owner;
|
||||
const owner = this.actor.isOwner;
|
||||
const levels = data.data.powers;
|
||||
const powerbook = {};
|
||||
|
||||
|
@ -375,10 +393,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* 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
|
||||
*/
|
||||
/** @inheritdoc */
|
||||
activateListeners(html) {
|
||||
|
||||
// Activate Item Filters
|
||||
|
@ -389,6 +404,9 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
// Item summaries
|
||||
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
|
||||
|
||||
// View Item Sheets
|
||||
html.find('.item-edit').click(this._onItemEdit.bind(this));
|
||||
|
||||
// Editable Only Listeners
|
||||
if ( this.isEditable ) {
|
||||
|
||||
|
@ -411,20 +429,19 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
// 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-collapse').click(this._onItemCollapse.bind(this));
|
||||
html.find('.item-collapse').click(this._onItemCollapse.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));
|
||||
html.find('.increment-class-level').click(this._onIncrementClassLevel.bind(this));
|
||||
html.find('.decrement-class-level').click(this._onDecrementClassLevel.bind(this));
|
||||
html.find('.increment-class-level').click(this._onIncrementClassLevel.bind(this));
|
||||
html.find('.decrement-class-level').click(this._onDecrementClassLevel.bind(this));
|
||||
|
||||
// Active Effect management
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.actor));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
if ( this.actor.owner ) {
|
||||
if ( this.actor.isOwner ) {
|
||||
|
||||
// Ability Checks
|
||||
html.find('.ability-name').click(this._onRollAbilityTest.bind(this));
|
||||
|
@ -491,17 +508,25 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onConfigMenu(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
let app;
|
||||
switch ( button.dataset.action ) {
|
||||
case "hit-dice":
|
||||
app = new ActorHitDiceConfig(this.object);
|
||||
break;
|
||||
case "movement":
|
||||
new ActorMovementConfig(this.object).render(true);
|
||||
app = new ActorMovementConfig(this.object);
|
||||
break;
|
||||
case "flags":
|
||||
new ActorSheetFlags(this.object).render(true);
|
||||
app = new ActorSheetFlags(this.object);
|
||||
break;
|
||||
case "senses":
|
||||
new ActorSensesConfig(this.object).render(true);
|
||||
app = new ActorSensesConfig(this.object);
|
||||
break;
|
||||
case "type":
|
||||
new ActorTypeConfig(this.object).render(true);
|
||||
break;
|
||||
}
|
||||
app?.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -535,7 +560,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/** @override */
|
||||
async _onDropActor(event, data) {
|
||||
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
if ( !canPolymorph ) return false;
|
||||
|
||||
// Get the target actor
|
||||
|
@ -610,15 +635,40 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
// Check to make sure items of this type are allowed on this actor
|
||||
if ( this.constructor.unsupportedItemTypes.has(itemData.type) ) {
|
||||
return ui.notifications.warn(game.i18n.format("SW5E.ActorWarningInvalidItem", {
|
||||
itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]),
|
||||
actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type])
|
||||
}));
|
||||
}
|
||||
|
||||
// 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]);
|
||||
// Ignore certain statuses
|
||||
["equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||
|
||||
// Downgrade ATTUNED to REQUIRED
|
||||
itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED);
|
||||
}
|
||||
|
||||
// Stack identical consumables
|
||||
if ( itemData.type === "consumable" && itemData.flags.core?.sourceId ) {
|
||||
const similarItem = this.actor.items.find(i => {
|
||||
const sourceId = i.getFlag("core", "sourceId");
|
||||
return sourceId && (sourceId === itemData.flags.core?.sourceId) &&
|
||||
(i.type === "consumable");
|
||||
});
|
||||
if ( similarItem ) {
|
||||
return similarItem.update({
|
||||
'data.quantity': similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create the owned item as normal
|
||||
|
@ -659,7 +709,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
async _onUsesChange(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(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 });
|
||||
|
@ -674,7 +724,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRoll(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.roll();
|
||||
}
|
||||
|
||||
|
@ -688,7 +738,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRecharge(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.rollRecharge();
|
||||
};
|
||||
|
||||
|
@ -701,8 +751,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_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});
|
||||
item = this.actor.items.get(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.isOwner});
|
||||
|
||||
// Toggle summary
|
||||
if ( li.hasClass("expanded") ) {
|
||||
|
@ -733,10 +783,10 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const itemData = {
|
||||
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||
type: type,
|
||||
data: duplicate(header.dataset)
|
||||
data: foundry.utils.deepClone(header.dataset)
|
||||
};
|
||||
delete itemData.data["type"];
|
||||
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
return this.actor.createEmbeddedDocuments("Item", [itemData]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -749,8 +799,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemEdit(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
const item = this.actor.getOwnedItem(li.dataset.itemId);
|
||||
item.sheet.render(true);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
return item.sheet.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -763,7 +813,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
this.actor.deleteOwnedItem(li.dataset.itemId);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
if ( item ) return item.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -803,12 +854,12 @@ _onItemCollapse(event) {
|
|||
const itemId = li.dataset.itemId;
|
||||
|
||||
const actor = game.actors.get(actorId);
|
||||
const item = actor.getOwnedItem(itemId);
|
||||
const item = actor.items.get(itemId);
|
||||
|
||||
let levels = item.data.data.levels;
|
||||
const update = {_id: item._id, data: {levels: (levels + 1) }};
|
||||
const update = {_id: item.data._id, data: {levels: (levels + 1) }};
|
||||
|
||||
actor.updateOwnedItem(update)
|
||||
actor.updateEmbeddedDocuments("Item", [update]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -827,12 +878,12 @@ _onItemCollapse(event) {
|
|||
const itemId = li.dataset.itemId;
|
||||
|
||||
const actor = game.actors.get(actorId);
|
||||
const item = actor.getOwnedItem(itemId);
|
||||
const item = actor.items.get(itemId);
|
||||
|
||||
let levels = item.data.data.levels;
|
||||
const update = {_id: item._id, data: {levels: (levels - 1) }};
|
||||
const update = {_id: item.data._id, data: {levels: (levels - 1) }};
|
||||
|
||||
actor.updateOwnedItem(update)
|
||||
actor.updateEmbeddedDocuments("Item", [update]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -845,7 +896,7 @@ _onItemCollapse(event) {
|
|||
_onRollAbilityTest(event) {
|
||||
event.preventDefault();
|
||||
let ability = event.currentTarget.parentElement.dataset.ability;
|
||||
this.actor.rollAbility(ability, {event: event});
|
||||
return this.actor.rollAbility(ability, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -858,7 +909,7 @@ _onItemCollapse(event) {
|
|||
_onRollSkillCheck(event) {
|
||||
event.preventDefault();
|
||||
const skill = event.currentTarget.parentElement.dataset.skill;
|
||||
this.actor.rollSkill(skill, {event: event});
|
||||
return this.actor.rollSkill(skill, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -871,7 +922,7 @@ _onItemCollapse(event) {
|
|||
_onToggleAbilityProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = event.currentTarget.previousElementSibling;
|
||||
this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
return this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -888,7 +939,7 @@ _onItemCollapse(event) {
|
|||
const filter = li.dataset.filter;
|
||||
if ( set.has(filter) ) set.delete(filter);
|
||||
else set.add(filter);
|
||||
this.render();
|
||||
return this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -904,7 +955,7 @@ _onItemCollapse(event) {
|
|||
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)
|
||||
return new TraitSelector(this.actor, options).render(true)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -912,15 +963,14 @@ _onItemCollapse(event) {
|
|||
/** @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()
|
||||
});
|
||||
if (this.actor.isPolymorphed) {
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: () => this.actor.revertOriginalForm()
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
let [items, forcepowers, techpowers, feats, classes, deployments, deploymentfeatures, ventures, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.attunement = {
|
||||
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
|
||||
|
@ -111,6 +111,9 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
// Item toggle state
|
||||
this._prepareItemToggleState(item);
|
||||
|
||||
// Primary Class
|
||||
if ( item.type === "class" ) item.isOriginalClass = ( item.data._id === this.actor.data.data.details.originalClass );
|
||||
|
||||
// Classify items into types
|
||||
if ( item.type === "power" && ["lgt", "drk", "uni"].includes(item.data.school) ) arr[1].push(item);
|
||||
else if ( item.type === "power" && ["tec"].includes(item.data.school) ) arr[2].push(item);
|
||||
|
@ -168,7 +171,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
if ( f.data.activation.type ) features.active.items.push(f);
|
||||
else features.passive.items.push(f);
|
||||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
classes.sort((a, b) => b.data.levels - a.data.levels);
|
||||
features.classes.items = classes;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
|
@ -218,11 +221,11 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
|
||||
/**
|
||||
* Activate event listeners using the prepared sheet HTML
|
||||
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
|
||||
* @param html {jQuery} The prepared HTML object ready to be rendered into the DOM
|
||||
*/
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if ( !this.options.editable ) return;
|
||||
if ( !this.isEditable ) return;
|
||||
|
||||
// Inventory Functions
|
||||
// html.find(".currency-convert").click(this._onConvertCurrency.bind(this));
|
||||
|
@ -240,11 +243,11 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
// Send Languages to Chat onClick
|
||||
html.find('[data-options="share-languages"]').click(event => {
|
||||
event.preventDefault();
|
||||
let langs = this.actor.data.data.traits.languages.value.map(l => SW5E.languages[l] || l).join(", ");
|
||||
let langs = this.actor.data.data.traits.languages.value.map(l => CONFIG.SW5E.languages[l] || l).join(", ");
|
||||
let custom = this.actor.data.data.traits.languages.custom;
|
||||
if (custom) langs += ", " + custom.replace(/;/g, ",");
|
||||
let content = `
|
||||
<div class="sw5e chat-card item-card" data-acor-id="${this.actor._id}">
|
||||
<div class="sw5e chat-card item-card" data-acor-id="${this.actor.data._id}">
|
||||
<header class="card-header flexrow">
|
||||
<img src="${this.actor.data.token.img}" title="" width="36" height="36" style="border: none;"/>
|
||||
<h3>Known Languages</h3>
|
||||
|
@ -254,21 +257,25 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
`;
|
||||
|
||||
// Send to Chat
|
||||
let rollWhisper = null;
|
||||
let rollBlind = false;
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
if (["gmroll", "blindroll"].includes(rollMode)) rollWhisper = ChatMessage.getWhisperIDs("GM");
|
||||
if (rollMode === "blindroll") rollBlind = true;
|
||||
ChatMessage.create({
|
||||
user: game.user._id,
|
||||
let data = {
|
||||
user: game.user.data._id,
|
||||
content: content,
|
||||
blind: rollBlind,
|
||||
speaker: {
|
||||
actor: this.actor._id,
|
||||
actor: this.actor.data._id,
|
||||
token: this.actor.token,
|
||||
alias: this.actor.name
|
||||
},
|
||||
type: CONST.CHAT_MESSAGE_TYPES.OTHER
|
||||
});
|
||||
};
|
||||
|
||||
if (["gmroll", "blindroll"].includes(rollMode)) data["whisper"] = ChatMessage.getWhisperRecipients("GM");
|
||||
else if (rollMode === "selfroll") data["whisper"] = [game.users.get(game.user.data._id)];
|
||||
|
||||
ChatMessage.create(data);
|
||||
});
|
||||
|
||||
// Item Delete Confirmation
|
||||
|
@ -276,7 +283,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
html.find('.item-delete').click(event => {
|
||||
let li = $(event.currentTarget).parents('.item');
|
||||
let itemId = li.attr("data-item-id");
|
||||
let item = this.actor.getOwnedItem(itemId);
|
||||
let item = this.actor.items.get(itemId);
|
||||
new Dialog({
|
||||
title: `Deleting ${item.data.name}`,
|
||||
content: `<p>Are you sure you want to delete ${item.data.name}?</p>`,
|
||||
|
@ -327,7 +334,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
const attr = item.data.type === "power" ? "data.preparation.prepared" : "data.equipped";
|
||||
return item.update({[attr]: !getProperty(item.data, attr)});
|
||||
}
|
||||
|
@ -390,7 +397,7 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
// }
|
||||
|
||||
// Default drop handling if levels were not added
|
||||
super._onDropItemCreate(itemData);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
||||
async function addFavorites(app, html, data) {
|
||||
|
@ -465,11 +472,11 @@ async function addFavorites(app, html, data) {
|
|||
if (app.options.editable) {
|
||||
let favBtn = $(`<a class="item-control item-toggle item-fav ${isFav ? "active" : ""}" data-fav="${isFav}" title="${isFav ? "Remove from Favourites" : "Add to Favourites"}"><i class="fas fa-star"></i></a>`);
|
||||
favBtn.click(ev => {
|
||||
app.actor.getOwnedItem(item._id).update({
|
||||
app.actor.items.get(item.data._id).update({
|
||||
"flags.favtab.isFavourite": !item.flags.favtab.isFavourite
|
||||
});
|
||||
});
|
||||
html.find(`.item[data-item-id="${item._id}"]`).find('.item-controls').prepend(favBtn);
|
||||
html.find(`.item[data-item-id="${item.data._id}"]`).find('.item-controls').prepend(favBtn);
|
||||
}
|
||||
|
||||
if (isFav) {
|
||||
|
@ -479,8 +486,8 @@ async function addFavorites(app, html, data) {
|
|||
let v = (comps.vocal) ? "V" : "";
|
||||
let s = (comps.somatic) ? "S" : "";
|
||||
let m = (comps.material) ? "M" : "";
|
||||
let c = (comps.concentration) ? true : false;
|
||||
let r = (comps.ritual) ? true : false;
|
||||
let c = !!(comps.concentration);
|
||||
let r = !!(comps.ritual);
|
||||
item.powerComps = `${v}${s}${m}`;
|
||||
item.powerCon = c;
|
||||
item.powerRit = r;
|
||||
|
@ -543,12 +550,12 @@ async function addFavorites(app, html, data) {
|
|||
//favtabHtml.find('.item-toggle').click(event => app._onToggleItem(event));
|
||||
favtabHtml.find('.item-edit').click(ev => {
|
||||
let itemId = $(ev.target).parents('.item')[0].dataset.itemId;
|
||||
app.actor.getOwnedItem(itemId).sheet.render(true);
|
||||
app.actor.items.get(itemId).sheet.render(true);
|
||||
});
|
||||
favtabHtml.find('.item-fav').click(ev => {
|
||||
let itemId = $(ev.target).parents('.item')[0].dataset.itemId;
|
||||
let val = !app.actor.getOwnedItem(itemId).data.flags.favtab.isFavourite
|
||||
app.actor.getOwnedItem(itemId).update({
|
||||
let val = !app.actor.items.get(itemId).data.flags.favtab.isFavourite
|
||||
app.actor.items.get(itemId).update({
|
||||
"flags.favtab.isFavourite": val
|
||||
});
|
||||
});
|
||||
|
@ -564,10 +571,10 @@ async function addFavorites(app, html, data) {
|
|||
let list = null;
|
||||
if (dropData.data.type === 'feat') list = favFeats;
|
||||
else list = favItems;
|
||||
let dragSource = list.find(i => i._id === dropData.data._id);
|
||||
let siblings = list.filter(i => i._id !== dropData.data._id);
|
||||
let dragSource = list.find(i => i.data._id === dropData.data._id);
|
||||
let siblings = list.filter(i => i.data._id !== dropData.data._id);
|
||||
let targetId = ev.target.closest('.item').dataset.itemId;
|
||||
let dragTarget = siblings.find(s => s._id === targetId);
|
||||
let dragTarget = siblings.find(s => s.data._id === targetId);
|
||||
|
||||
if (dragTarget === undefined) return;
|
||||
const sortUpdates = SortingHelpers.performIntegerSort(dragSource, {
|
||||
|
@ -577,7 +584,7 @@ async function addFavorites(app, html, data) {
|
|||
});
|
||||
const updateData = sortUpdates.map(u => {
|
||||
const update = u.update;
|
||||
update._id = u.target._id;
|
||||
update._id = u.target.data._id;
|
||||
return update;
|
||||
});
|
||||
app.actor.updateEmbeddedEntity("OwnedItem", updateData);
|
||||
|
@ -636,11 +643,7 @@ async function addSubTabs(app, html, data) {
|
|||
return tab.target == target
|
||||
});
|
||||
data.options.subTabs[subgroup].map(el => {
|
||||
if(el.target == target) {
|
||||
el.active = true;
|
||||
} else {
|
||||
el.active = false;
|
||||
}
|
||||
el.active = el.target == target;
|
||||
return el;
|
||||
})
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Actor5e from "../../entity.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
|
@ -27,6 +28,11 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the NPC sheet
|
||||
* @private
|
||||
|
@ -43,7 +49,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
|
||||
// Start by classifying items into groups for rendering
|
||||
let [forcepowers, techpowers, other] = data.items.reduce((arr, item) => {
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||
|
@ -80,17 +86,19 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
data.techPowerbook = techPowerbook;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
getData(options) {
|
||||
const data = super.getData(options);
|
||||
|
||||
// Challenge Rating
|
||||
const cr = parseFloat(data.data.details.cr || 0);
|
||||
const crLabels = {0: "0", 0.125: "1/8", 0.25: "1/4", 0.5: "1/2"};
|
||||
data.labels["cr"] = cr >= 1 ? String(cr) : crLabels[cr] || 1;
|
||||
|
||||
// Creature Type
|
||||
data.labels["type"] = this.actor.labels.creatureType;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -99,7 +107,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
async _updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
|
@ -109,7 +117,7 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
return super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -44,7 +44,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
|
||||
// Start by classifying items into groups for rendering
|
||||
let [forcepowers, techpowers, other] = data.items.reduce((arr, item) => {
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||
|
@ -91,8 +91,8 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
getData(options) {
|
||||
const data = super.getData(options);
|
||||
|
||||
// Add Size info
|
||||
data.isTiny = data.actor.data.traits.size === "tiny";
|
||||
|
@ -114,7 +114,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
async _updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
|
@ -124,7 +124,7 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
return super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -152,5 +152,4 @@ export default class ActorSheet5eStarship extends ActorSheet5e {
|
|||
AudioHelper.play({src: CONFIG.sounds.dice});
|
||||
this.actor.update({"data.attributes.hp.value": hp, "data.attributes.hp.max": hp});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new cargo entry for a vehicle Actor.
|
||||
*/
|
||||
|
@ -206,24 +212,39 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
}
|
||||
};
|
||||
|
||||
// Classify items owned by the vehicle and compute total cargo weight
|
||||
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') {
|
||||
|
||||
// Handle cargo explicitly
|
||||
const isCargo = item.flags.sw5e?.vehicleCargo === true;
|
||||
if ( isCargo ) {
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
continue
|
||||
}
|
||||
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);
|
||||
|
||||
// Handle non-cargo item types
|
||||
switch ( item.type ) {
|
||||
case "weapon":
|
||||
features.weapons.items.push(item);
|
||||
break;
|
||||
case "equipment":
|
||||
features.equipment.items.push(item);
|
||||
break;
|
||||
case "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);
|
||||
break;
|
||||
default:
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the rendering context data
|
||||
data.features = Object.values(features);
|
||||
data.cargo = Object.values(cargo);
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
|
@ -236,7 +257,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if (!this.options.editable) return;
|
||||
if (!this.isEditable) return;
|
||||
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
html.find('.item-hp input')
|
||||
|
@ -272,7 +293,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
|
||||
// Get the cargo entry
|
||||
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[property]);
|
||||
const entry = cargo[idx];
|
||||
if (!entry) return null;
|
||||
|
||||
|
@ -322,7 +343,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const target = event.currentTarget;
|
||||
const type = target.dataset.type;
|
||||
if (type === 'crew' || type === 'passengers') {
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]);
|
||||
cargo.push(this.constructor.newCargo);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
@ -343,13 +364,21 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
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);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
||||
return super._onItemDelete(event);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"];
|
||||
const isCargo = cargoTypes.includes(itemData.type) && (this._tabs[0].active === "cargo");
|
||||
foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import Item5e from "../../../item/entity.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||
import {SW5E} from '../../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
|
||||
|
@ -44,6 +46,15 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A set of item types that should be prevented from being dropped on this type of actor sheet.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
static unsupportedItemTypes = new Set();
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/oldActor/limited-sheet.html";
|
||||
|
@ -53,44 +64,51 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
getData(options) {
|
||||
|
||||
// Basic data
|
||||
let isOwner = this.entity.owner;
|
||||
let isOwner = this.actor.isOwner;
|
||||
const data = {
|
||||
owner: isOwner,
|
||||
limited: this.entity.limited,
|
||||
limited: this.actor.limited,
|
||||
options: this.options,
|
||||
editable: this.isEditable,
|
||||
cssClass: isOwner ? "editable" : "locked",
|
||||
isCharacter: this.entity.data.type === "character",
|
||||
isNPC: this.entity.data.type === "npc",
|
||||
isStarship: this.entity.data.type === "starship",
|
||||
isVehicle: this.entity.data.type === 'vehicle',
|
||||
isCharacter: this.actor.type === "character",
|
||||
isNPC: this.actor.type === "npc",
|
||||
isStarship: this.actor.type === "starship",
|
||||
isVehicle: this.actor.type === 'vehicle',
|
||||
config: CONFIG.SW5E,
|
||||
rollData: this.actor.getRollData.bind(this.actor)
|
||||
};
|
||||
|
||||
// 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;
|
||||
});
|
||||
// The Actor's data
|
||||
const actorData = this.actor.data.toObject(false);
|
||||
data.actor = actorData;
|
||||
data.data = actorData.data;
|
||||
|
||||
// Owned Items
|
||||
data.items = actorData.items;
|
||||
for ( let i of data.items ) {
|
||||
const item = this.actor.items.get(i._id);
|
||||
i.labels = item.labels;
|
||||
}
|
||||
data.items.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
data.data = data.actor.data;
|
||||
|
||||
// Labels and filters
|
||||
data.labels = this.actor.labels || {};
|
||||
data.filters = this._filters;
|
||||
|
||||
// Ability Scores
|
||||
for ( let [a, abl] of Object.entries(data.actor.data.abilities)) {
|
||||
for ( let [a, abl] of Object.entries(actorData.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)) {
|
||||
if (actorData.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(actorData.data.skills)) {
|
||||
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||
skl.icon = this._getProficiencyIcon(skl.value);
|
||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||
|
@ -99,19 +117,19 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
data.movement = this._getMovementSpeed(actorData);
|
||||
|
||||
// Senses
|
||||
data.senses = this._getSenses(data.actor);
|
||||
data.senses = this._getSenses(actorData);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
this._prepareTraits(actorData.data.traits);
|
||||
|
||||
// Prepare owned items
|
||||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
|
@ -222,7 +240,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
* @private
|
||||
*/
|
||||
_preparePowerbook(data, powers) {
|
||||
const owner = this.actor.owner;
|
||||
const owner = this.actor.isOwner;
|
||||
const levels = data.data.powers;
|
||||
const powerbook = {};
|
||||
|
||||
|
@ -275,11 +293,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
|
||||
// Pact magic users have cantrips and a pact magic section
|
||||
// TODO: Check if this is needed, we've removed pacts everywhere else
|
||||
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, {
|
||||
const level = game.i18n.localize(`SW5E.PowerLevel${levels.pact.level}`);
|
||||
const label = `${config} — ${level}`;
|
||||
registerSection("pact", sections.pact, label, {
|
||||
prepMode: "pact",
|
||||
value: l.value,
|
||||
max: l.max,
|
||||
|
@ -382,10 +403,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* 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
|
||||
*/
|
||||
/** @inheritdoc */
|
||||
activateListeners(html) {
|
||||
|
||||
// Activate Item Filters
|
||||
|
@ -396,6 +414,9 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
// Item summaries
|
||||
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
|
||||
|
||||
// View Item Sheets
|
||||
html.find('.item-edit').click(this._onItemEdit.bind(this));
|
||||
|
||||
// Editable Only Listeners
|
||||
if ( this.isEditable ) {
|
||||
|
||||
|
@ -418,17 +439,16 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
// 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));
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.actor));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
if ( this.actor.owner ) {
|
||||
if ( this.actor.isOwner ) {
|
||||
|
||||
// Ability Checks
|
||||
html.find('.ability-name').click(this._onRollAbilityTest.bind(this));
|
||||
|
@ -495,17 +515,25 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onConfigMenu(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
let app;
|
||||
switch ( button.dataset.action ) {
|
||||
case "hit-dice":
|
||||
app = new ActorHitDiceConfig(this.object);
|
||||
break;
|
||||
case "movement":
|
||||
new ActorMovementConfig(this.object).render(true);
|
||||
app = new ActorMovementConfig(this.object);
|
||||
break;
|
||||
case "flags":
|
||||
new ActorSheetFlags(this.object).render(true);
|
||||
app = new ActorSheetFlags(this.object);
|
||||
break;
|
||||
case "senses":
|
||||
new ActorSensesConfig(this.object).render(true);
|
||||
app = new ActorSensesConfig(this.object);
|
||||
break;
|
||||
case "type":
|
||||
new ActorTypeConfig(this.object).render(true);
|
||||
break;
|
||||
}
|
||||
app?.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -539,7 +567,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/** @override */
|
||||
async _onDropActor(event, data) {
|
||||
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
const canPolymorph = game.user.isGM || (this.actor.isOwner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
if ( !canPolymorph ) return false;
|
||||
|
||||
// Get the target actor
|
||||
|
@ -614,15 +642,41 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
// Check to make sure items of this type are allowed on this actor
|
||||
if ( this.constructor.unsupportedItemTypes.has(itemData.type) ) {
|
||||
return ui.notifications.warn(game.i18n.format("SW5E.ActorWarningInvalidItem", {
|
||||
itemType: game.i18n.localize(CONFIG.Item.typeLabels[itemData.type]),
|
||||
actorType: game.i18n.localize(CONFIG.Actor.typeLabels[this.actor.type])
|
||||
}));
|
||||
}
|
||||
|
||||
// Create a Consumable power scroll on the Inventory tab
|
||||
// TODO: This is pretty non functional as the base items for the scrolls, and the powers, are not defined, maybe consider using holocrons
|
||||
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]);
|
||||
// Ignore certain statuses
|
||||
["equipped", "proficient", "prepared"].forEach(k => delete itemData.data[k]);
|
||||
|
||||
// Downgrade ATTUNED to REQUIRED
|
||||
itemData.data.attunement = Math.min(itemData.data.attunement, CONFIG.SW5E.attunementTypes.REQUIRED);
|
||||
}
|
||||
|
||||
// Stack identical consumables
|
||||
if ( itemData.type === "consumable" && itemData.flags.core?.sourceId ) {
|
||||
const similarItem = this.actor.items.find(i => {
|
||||
const sourceId = i.getFlag("core", "sourceId");
|
||||
return sourceId && (sourceId === itemData.flags.core?.sourceId) &&
|
||||
(i.type === "consumable");
|
||||
});
|
||||
if ( similarItem ) {
|
||||
return similarItem.update({
|
||||
'data.quantity': similarItem.data.data.quantity + Math.max(itemData.data.quantity, 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create the owned item as normal
|
||||
|
@ -663,7 +717,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
async _onUsesChange(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(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 });
|
||||
|
@ -678,7 +732,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRoll(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.roll();
|
||||
}
|
||||
|
||||
|
@ -692,7 +746,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemRecharge(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
return item.rollRecharge();
|
||||
};
|
||||
|
||||
|
@ -705,8 +759,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_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});
|
||||
item = this.actor.items.get(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.isOwner});
|
||||
|
||||
// Toggle summary
|
||||
if ( li.hasClass("expanded") ) {
|
||||
|
@ -737,10 +791,10 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const itemData = {
|
||||
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||
type: type,
|
||||
data: duplicate(header.dataset)
|
||||
data: foundry.utils.deepClone(header.dataset)
|
||||
};
|
||||
delete itemData.data["type"];
|
||||
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
return this.actor.createEmbeddedDocuments("Item", [itemData]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -753,8 +807,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onItemEdit(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
const item = this.actor.getOwnedItem(li.dataset.itemId);
|
||||
item.sheet.render(true);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
return item.sheet.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -766,8 +820,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
*/
|
||||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
this.actor.deleteOwnedItem(li.dataset.itemId);
|
||||
const item = this.actor.items.get(li.dataset.itemId);
|
||||
if ( item ) return item.delete();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -780,7 +834,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onRollAbilityTest(event) {
|
||||
event.preventDefault();
|
||||
let ability = event.currentTarget.parentElement.dataset.ability;
|
||||
this.actor.rollAbility(ability, {event: event});
|
||||
return this.actor.rollAbility(ability, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -793,7 +847,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onRollSkillCheck(event) {
|
||||
event.preventDefault();
|
||||
const skill = event.currentTarget.parentElement.dataset.skill;
|
||||
this.actor.rollSkill(skill, {event: event});
|
||||
return this.actor.rollSkill(skill, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -806,7 +860,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
_onToggleAbilityProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = event.currentTarget.previousElementSibling;
|
||||
this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
return this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -823,7 +877,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const filter = li.dataset.filter;
|
||||
if ( set.has(filter) ) set.delete(filter);
|
||||
else set.add(filter);
|
||||
this.render();
|
||||
return this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -839,7 +893,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
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)
|
||||
return new TraitSelector(this.actor, options).render(true)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -847,15 +901,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/** @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()
|
||||
});
|
||||
if ( this.actor.isPolymorphed ) {
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: () => this.actor.revertOriginalForm()
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
let [items, powers, feats, classes, species, archetypes, classfeatures, backgrounds, fightingstyles, fightingmasteries, lightsaberforms] = data.items.reduce((arr, item) => {
|
||||
|
||||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.attunement = {
|
||||
[CONFIG.SW5E.attunementTypes.REQUIRED]: {
|
||||
|
@ -100,6 +100,9 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
// Item toggle state
|
||||
this._prepareItemToggleState(item);
|
||||
|
||||
// Primary Class
|
||||
if ( item.type === "class" ) item.isOriginalClass = ( item.data._id === this.actor.data.data.details.originalClass );
|
||||
|
||||
// Classify items into types
|
||||
if ( item.type === "power" ) arr[1].push(item);
|
||||
else if ( item.type === "feat" ) arr[2].push(item);
|
||||
|
@ -151,7 +154,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
if ( f.data.activation.type ) features.active.items.push(f);
|
||||
else features.passive.items.push(f);
|
||||
}
|
||||
classes.sort((a, b) => b.levels - a.levels);
|
||||
classes.sort((a, b) => b.data.levels - a.data.levels);
|
||||
features.classes.items = classes;
|
||||
features.classfeatures.items = classfeatures;
|
||||
features.archetype.items = archetypes;
|
||||
|
@ -198,11 +201,11 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
|
||||
/**
|
||||
* Activate event listeners using the prepared sheet HTML
|
||||
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
|
||||
* @param html {jQuery} The prepared HTML object ready to be rendered into the DOM
|
||||
*/
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if ( !this.options.editable ) return;
|
||||
if ( !this.isEditable ) return;
|
||||
|
||||
// Item State Toggling
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
|
@ -243,7 +246,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const item = this.actor.items.get(itemId);
|
||||
const attr = item.data.type === "power" ? "data.preparation.prepared" : "data.equipped";
|
||||
return item.update({[attr]: !getProperty(item.data, attr)});
|
||||
}
|
||||
|
@ -293,6 +296,6 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
}
|
||||
|
||||
// Default drop handling if levels were not added
|
||||
super._onDropItemCreate(itemData);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the NPC sheet
|
||||
* @private
|
||||
|
@ -34,7 +39,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
|
||||
// Start by classifying items into groups for rendering
|
||||
let [powers, other] = data.items.reduce((arr, item) => {
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.img = item.img || CONST.DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
item.isOnCooldown = item.data.recharge && !!item.data.recharge.value && (item.data.recharge.charged === false);
|
||||
|
@ -70,14 +75,17 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
/** @inheritdoc */
|
||||
getData(options) {
|
||||
const data = super.getData(options);
|
||||
|
||||
// Challenge Rating
|
||||
const cr = parseFloat(data.data.details.cr || 0);
|
||||
const crLabels = {0: "0", 0.125: "1/8", 0.25: "1/4", 0.5: "1/2"};
|
||||
data.labels["cr"] = cr >= 1 ? String(cr) : crLabels[cr] || 1;
|
||||
|
||||
// Creature Type
|
||||
data.labels["type"] = this.actor.labels.creatureType;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -86,7 +94,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
async _updateObject(event, formData) {
|
||||
|
||||
// Format NPC Challenge Rating
|
||||
const crs = {"1/8": 0.125, "1/4": 0.25, "1/2": 0.5};
|
||||
|
@ -96,7 +104,7 @@ export default class ActorSheet5eNPC extends ActorSheet5e {
|
|||
if ( cr ) formData[crv] = cr < 1 ? cr : parseInt(cr);
|
||||
|
||||
// Parent ActorSheet update steps
|
||||
super._updateObject(event, formData);
|
||||
return super._updateObject(event, formData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -20,6 +20,12 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static unsupportedItemTypes = new Set(["class"]);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new cargo entry for a vehicle Actor.
|
||||
*/
|
||||
|
@ -206,24 +212,39 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
}
|
||||
};
|
||||
|
||||
// Classify items owned by the vehicle and compute total cargo weight
|
||||
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') {
|
||||
|
||||
// Handle cargo explicitly
|
||||
const isCargo = item.flags.sw5e?.vehicleCargo === true;
|
||||
if ( isCargo ) {
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
|
||||
// Handle non-cargo item types
|
||||
switch ( item.type ) {
|
||||
case "weapon":
|
||||
features.weapons.items.push(item);
|
||||
break;
|
||||
case "equipment":
|
||||
features.equipment.items.push(item);
|
||||
break;
|
||||
case "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);
|
||||
break;
|
||||
default:
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the rendering context data
|
||||
data.features = Object.values(features);
|
||||
data.cargo = Object.values(cargo);
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
|
@ -236,7 +257,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if (!this.options.editable) return;
|
||||
if (!this.isEditable) return;
|
||||
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
html.find('.item-hp input')
|
||||
|
@ -272,7 +293,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
|
||||
// Get the cargo entry
|
||||
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[property]);
|
||||
const entry = cargo[idx];
|
||||
if (!entry) return null;
|
||||
|
||||
|
@ -322,7 +343,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
const target = event.currentTarget;
|
||||
const type = target.dataset.type;
|
||||
if (type === 'crew' || type === 'passengers') {
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]);
|
||||
cargo.push(this.constructor.newCargo);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
@ -343,7 +364,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
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);
|
||||
const cargo = foundry.utils.deepClone(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
||||
|
@ -352,6 +373,16 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
const cargoTypes = ["weapon", "equipment", "consumable", "tool", "loot", "backpack"];
|
||||
const isCargo = cargoTypes.includes(itemData.type) && (this._tabs[0].active === "cargo");
|
||||
foundry.utils.setProperty(itemData, "flags.sw5e.vehicleCargo", isCargo);
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Special handling for editing HP to clamp it within appropriate range.
|
||||
* @param event {Event}
|
||||
|
|
|
@ -44,7 +44,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
consumePowerSlot: false,
|
||||
consumeRecharge: recharges,
|
||||
consumeResource: !!itemData.consume.target,
|
||||
consumeUses: uses.max,
|
||||
consumeUses: uses.per && (uses.max > 0),
|
||||
canUse: recharges ? recharge.charged : sufficientUses,
|
||||
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||
errors: []
|
||||
|
@ -169,7 +169,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
}));
|
||||
|
||||
// Merge power casting data
|
||||
return mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||
return foundry.utils.mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -219,10 +219,4 @@ export default class AbilityUseDialog extends Dialog {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static _handleSubmit(formData, item) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
/**
|
||||
* An application class which provides advanced configuration for special character flags which modify an Actor
|
||||
* @implements {BaseEntitySheet}
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorSheetFlags extends BaseEntitySheet {
|
||||
export default class ActorSheetFlags extends DocumentSheet {
|
||||
static get defaultOptions() {
|
||||
const options = super.defaultOptions;
|
||||
return mergeObject(options, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "actor-flags",
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/actor-flags.html",
|
||||
|
@ -27,6 +26,7 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
getData() {
|
||||
const data = {};
|
||||
data.actor = this.object;
|
||||
data.classes = this._getClasses();
|
||||
data.flags = this._getFlags();
|
||||
data.bonuses = this._getBonuses();
|
||||
return data;
|
||||
|
@ -34,17 +34,33 @@ export default class ActorSheetFlags extends BaseEntitySheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare an object of sorted classes.
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_getClasses() {
|
||||
const classes = this.object.items.filter(i => i.type === "class");
|
||||
return classes.sort((a, b) => a.name.localeCompare(b.name)).reduce((obj, i) => {
|
||||
obj[i.id] = i.name;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare an object of flags data which groups flags by section
|
||||
* Add some additional data for rendering
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_getFlags() {
|
||||
const flags = {};
|
||||
const baseData = this.entity._data;
|
||||
const baseData = this.document.toJSON();
|
||||
for ( let [k, v] of Object.entries(CONFIG.SW5E.characterFlags) ) {
|
||||
if ( !flags.hasOwnProperty(v.section) ) flags[v.section] = {};
|
||||
let flag = duplicate(v);
|
||||
let flag = foundry.utils.deepClone(v);
|
||||
flag.type = v.type.name;
|
||||
flag.isCheckbox = v.type === Boolean;
|
||||
flag.isSelect = v.hasOwnProperty('choices');
|
||||
|
|
110
module/apps/actor-type.js
Normal file
110
module/apps/actor-type.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
import Actor5e from "../actor/entity.js";
|
||||
|
||||
/**
|
||||
* A specialized form used to select from a checklist of attributes, traits, or properties
|
||||
* @extends {FormApplication}
|
||||
*/
|
||||
export default class ActorTypeConfig extends FormApplication {
|
||||
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "actor-type", "trait-selector"],
|
||||
template: "systems/sw5e/templates/apps/actor-type.html",
|
||||
title: "SW5E.CreatureTypeTitle",
|
||||
width: 280,
|
||||
height: "auto",
|
||||
choices: {},
|
||||
allowCustom: true,
|
||||
minimum: 0,
|
||||
maximum: null
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get id() {
|
||||
return `actor-type-${this.object.id}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
|
||||
// Get current value or new default
|
||||
let attr = foundry.utils.getProperty(this.object.data.data, 'details.type');
|
||||
if ( foundry.utils.getType(attr) !== "Object" ) attr = {
|
||||
value: (attr in CONFIG.SW5E.creatureTypes) ? attr : "humanoid",
|
||||
subtype: "",
|
||||
swarm: "",
|
||||
custom: ""
|
||||
};
|
||||
|
||||
// Populate choices
|
||||
const types = {};
|
||||
for ( let [k, v] of Object.entries(CONFIG.SW5E.creatureTypes) ) {
|
||||
types[k] = {
|
||||
label: game.i18n.localize(v),
|
||||
chosen: attr.value === k
|
||||
}
|
||||
}
|
||||
|
||||
// Return data for rendering
|
||||
return {
|
||||
types: types,
|
||||
custom: {
|
||||
value: attr.custom,
|
||||
label: game.i18n.localize("SW5E.CreatureTypeSelectorCustom"),
|
||||
chosen: attr.value === "custom"
|
||||
},
|
||||
subtype: attr.subtype,
|
||||
swarm: attr.swarm,
|
||||
sizes: Array.from(Object.entries(CONFIG.SW5E.actorSizes)).reverse().reduce((obj, e) => {
|
||||
obj[e[0]] = e[1];
|
||||
return obj;
|
||||
}, {}),
|
||||
preview: Actor5e.formatCreatureType(attr) || "–"
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const typeObject = foundry.utils.expandObject(formData);
|
||||
return this.object.update({ 'data.details.type': typeObject });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
html.find("input[name='custom']").focusin(this._onCustomFieldFocused.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
_onChangeInput(event) {
|
||||
super._onChangeInput(event);
|
||||
const typeObject = foundry.utils.expandObject(this._getSubmitData());
|
||||
this.form["preview"].value = Actor5e.formatCreatureType(typeObject) || "—";
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Select the custom radio button when the custom text field is focused.
|
||||
* @param {FocusEvent} event The original focusin event
|
||||
* @private
|
||||
*/
|
||||
_onCustomFieldFocused(event) {
|
||||
this.form.querySelector("input[name='value'][value='custom']").checked = true;
|
||||
this._onChangeInput(event);
|
||||
}
|
||||
}
|
91
module/apps/hit-dice-config.js
Normal file
91
module/apps/hit-dice-config.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* A simple form to set actor hit dice amounts
|
||||
* @implements {DocumentSheet}
|
||||
*/
|
||||
export default class ActorHitDiceConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "hd-config", "dialog"],
|
||||
template: "systems/sw5e/templates/apps/hit-dice-config.html",
|
||||
width: 360,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.HitDiceConfig")}: ${this.object.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
return {
|
||||
classes: this.object.items.reduce((classes, item) => {
|
||||
if (item.data.type === "class") {
|
||||
// Add the appropriate data only if this item is a "class"
|
||||
classes.push({
|
||||
classItemId: item.data._id,
|
||||
name: item.data.name,
|
||||
diceDenom: item.data.data.hitDice,
|
||||
currentHitDice: item.data.data.levels - item.data.data.hitDiceUsed,
|
||||
maxHitDice: item.data.data.levels,
|
||||
canRoll: (item.data.data.levels - item.data.data.hitDiceUsed) > 0
|
||||
});
|
||||
}
|
||||
return classes;
|
||||
}, []).sort((a, b) => parseInt(b.diceDenom.slice(1)) - parseInt(a.diceDenom.slice(1)))
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
// Hook up -/+ buttons to adjust the current value in the form
|
||||
html.find("button.increment,button.decrement").click(event => {
|
||||
const button = event.currentTarget;
|
||||
const current = button.parentElement.querySelector(".current");
|
||||
const max = button.parentElement.querySelector(".max");
|
||||
const direction = button.classList.contains("increment") ? 1 : -1;
|
||||
current.value = Math.clamped(parseInt(current.value) + direction, 0, parseInt(max.value));
|
||||
});
|
||||
|
||||
html.find("button.roll-hd").click(this._onRollHitDie.bind(this));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _updateObject(event, formData) {
|
||||
const actorItems = this.object.items;
|
||||
const classUpdates = Object.entries(formData).map(([id, hd]) => ({
|
||||
_id: id,
|
||||
"data.hitDiceUsed": actorItems.get(id).data.data.levels - hd,
|
||||
}));
|
||||
return this.object.updateEmbeddedDocuments("Item", classUpdates);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Rolls the hit die corresponding with the class row containing the event's target button.
|
||||
* @param {MouseEvent} event
|
||||
* @private
|
||||
*/
|
||||
async _onRollHitDie(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
await this.object.rollHitDie(button.dataset.hdDenom);
|
||||
|
||||
// Re-render dialog to reflect changed hit dice quantities
|
||||
this.render();
|
||||
}
|
||||
}
|
|
@ -46,11 +46,9 @@ export default class LongRestDialog extends Dialog {
|
|||
icon: '<i class="fas fa-bed"></i>',
|
||||
label: "Rest",
|
||||
callback: html => {
|
||||
let newDay = false;
|
||||
if (game.settings.get("sw5e", "restVariant") === "normal")
|
||||
let newDay = true;
|
||||
if (game.settings.get("sw5e", "restVariant") !== "gritty")
|
||||
newDay = html.find('input[name="newDay"]')[0].checked;
|
||||
else if(game.settings.get("sw5e", "restVariant") === "gritty")
|
||||
newDay = true;
|
||||
resolve(newDay);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorMovementConfig extends BaseEntitySheet {
|
||||
export default class ActorMovementConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||
width: 300,
|
||||
|
@ -18,17 +18,18 @@ export default class ActorMovementConfig extends BaseEntitySheet {
|
|||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.MovementConfig")}: ${this.entity.name}`;
|
||||
return `${game.i18n.localize("SW5E.MovementConfig")}: ${this.document.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
const sourceMovement = foundry.utils.getProperty(this.document.data._source, "data.attributes.movement") || {};
|
||||
const data = {
|
||||
movement: duplicate(this.entity._data.data.attributes.movement),
|
||||
movement: foundry.utils.deepClone(sourceMovement),
|
||||
units: CONFIG.SW5E.movementUnits
|
||||
}
|
||||
};
|
||||
for ( let [k, v] of Object.entries(data.movement) ) {
|
||||
if ( ["units", "hover"].includes(k) ) continue;
|
||||
data.movement[k] = Number.isNumeric(v) ? v.toNearest(0.1) : 0;
|
||||
|
|
68
module/apps/select-items-prompt.js
Normal file
68
module/apps/select-items-prompt.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* A Dialog to prompt the user to select from a list of items.
|
||||
* @type {Dialog}
|
||||
*/
|
||||
export default class SelectItemsPrompt extends Dialog {
|
||||
constructor(items, dialogData={}, options={}) {
|
||||
super(dialogData, options);
|
||||
this.options.classes = ["sw5e", "dialog", "select-items-prompt", "sheet"];
|
||||
|
||||
/**
|
||||
* Store a reference to the Item entities being used
|
||||
* @type {Array<Item5e>}
|
||||
*/
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
|
||||
// render the item's sheet if its image is clicked
|
||||
html.on('click', '.item-image', (event) => {
|
||||
const item = this.items.find((feature) => feature.id === event.currentTarget.dataset?.itemId);
|
||||
|
||||
item?.sheet.render(true);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor function which displays the AddItemPrompt app for a given Actor and Item set.
|
||||
* Returns a Promise which resolves to the dialog FormData once the workflow has been completed.
|
||||
* @param {Array<Item5e>} items
|
||||
* @param {Object} options
|
||||
* @param {string} options.hint - Localized hint to display at the top of the prompt
|
||||
* @return {Promise<string[]>} - list of item ids which the user has selected
|
||||
*/
|
||||
static async create(items, {
|
||||
hint
|
||||
}) {
|
||||
// Render the ability usage template
|
||||
const html = await renderTemplate("systems/sw5e/templates/apps/select-items-prompt.html", {items, hint});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const dlg = new this(items, {
|
||||
title: game.i18n.localize('SW5E.SelectItemsPromptTitle'),
|
||||
content: html,
|
||||
buttons: {
|
||||
apply: {
|
||||
icon: `<i class="fas fa-user-plus"></i>`,
|
||||
label: game.i18n.localize('SW5E.Apply'),
|
||||
callback: html => {
|
||||
const fd = new FormDataExtended(html[0].querySelector("form")).toObject();
|
||||
const selectedIds = Object.keys(fd).filter(itemId => fd[itemId]);
|
||||
resolve(selectedIds);
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-forward"></i>',
|
||||
label: game.i18n.localize('SW5E.Skip'),
|
||||
callback: () => resolve([])
|
||||
}
|
||||
},
|
||||
default: "apply",
|
||||
close: () => resolve([])
|
||||
});
|
||||
dlg.render(true);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class ActorSensesConfig extends BaseEntitySheet {
|
||||
export default class ActorSensesConfig extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/senses-config.html",
|
||||
width: 300,
|
||||
|
@ -16,16 +16,16 @@ export default class ActorSensesConfig extends BaseEntitySheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.entity.name}`;
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.document.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
getData(options) {
|
||||
const senses = this.entity._data.data.attributes?.senses ?? {};
|
||||
const senses = foundry.utils.getProperty(this.document.data._source, "data.attributes.senses") || {};
|
||||
const data = {
|
||||
senses: {},
|
||||
special: senses.special ?? "",
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class ShortRestDialog extends Dialog {
|
|||
// Determine Hit Dice
|
||||
data.availableHD = this.actor.data.items.reduce((hd, item) => {
|
||||
if ( item.type === "class" ) {
|
||||
const d = item.data;
|
||||
const d = item.data.data;
|
||||
const denom = d.hitDice || "d6";
|
||||
const available = parseInt(d.levels || 1) - parseInt(d.hitDiceUsed || 0);
|
||||
hd[denom] = denom in hd ? hd[denom] + available : available;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/**
|
||||
* A specialized form used to select from a checklist of attributes, traits, or properties
|
||||
* @implements {FormApplication}
|
||||
* @extends {DocumentSheet}
|
||||
*/
|
||||
export default class TraitSelector extends FormApplication {
|
||||
export default class TraitSelector extends DocumentSheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
id: "trait-selector",
|
||||
classes: ["sw5e"],
|
||||
/** @inheritDoc */
|
||||
static get defaultOptions() {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
id: "trait-selector",
|
||||
classes: ["sw5e", "trait-selector", "subconfig"],
|
||||
title: "Actor Trait Selection",
|
||||
template: "systems/sw5e/templates/apps/trait-selector.html",
|
||||
width: 320,
|
||||
|
@ -16,7 +16,9 @@ export default class TraitSelector extends FormApplication {
|
|||
choices: {},
|
||||
allowCustom: true,
|
||||
minimum: 0,
|
||||
maximum: null
|
||||
maximum: null,
|
||||
valueKey: "value",
|
||||
customKey: "custom"
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -24,7 +26,7 @@ export default class TraitSelector extends FormApplication {
|
|||
|
||||
/**
|
||||
* Return a reference to the target attribute
|
||||
* @type {String}
|
||||
* @type {string}
|
||||
*/
|
||||
get attribute() {
|
||||
return this.options.name;
|
||||
|
@ -34,52 +36,50 @@ export default class TraitSelector extends FormApplication {
|
|||
|
||||
/** @override */
|
||||
getData() {
|
||||
|
||||
// Get current values
|
||||
let attr = getProperty(this.object._data, this.attribute);
|
||||
if ( getType(attr) !== "Object" ) attr = {value: [], custom: ""};
|
||||
const attr = foundry.utils.getProperty(this.object.data, this.attribute);
|
||||
const o = this.options;
|
||||
const value = (o.valueKey) ? attr[o.valueKey] ?? [] : attr;
|
||||
const custom = (o.customKey) ? attr[o.customKey] ?? "" : "";
|
||||
|
||||
// Populate choices
|
||||
const choices = duplicate(this.options.choices);
|
||||
for ( let [k, v] of Object.entries(choices) ) {
|
||||
choices[k] = {
|
||||
label: v,
|
||||
chosen: attr ? attr.value.includes(k) : false
|
||||
}
|
||||
}
|
||||
const choices = Object.entries(o.choices).reduce((obj, e) => {
|
||||
let [k, v] = e;
|
||||
obj[k] = { label: v, chosen: attr ? value.includes(k) : false };
|
||||
return obj;
|
||||
}, {})
|
||||
|
||||
// Return data
|
||||
return {
|
||||
allowCustom: this.options.allowCustom,
|
||||
choices: choices,
|
||||
custom: attr ? attr.custom : ""
|
||||
allowCustom: o.allowCustom,
|
||||
choices: choices,
|
||||
custom: custom
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_updateObject(event, formData) {
|
||||
const updateData = {};
|
||||
async _updateObject(event, formData) {
|
||||
const o = this.options;
|
||||
|
||||
// Obtain choices
|
||||
const chosen = [];
|
||||
for ( let [k, v] of Object.entries(formData) ) {
|
||||
if ( (k !== "custom") && v ) chosen.push(k);
|
||||
}
|
||||
updateData[`${this.attribute}.value`] = chosen;
|
||||
|
||||
// Object including custom data
|
||||
const updateData = {};
|
||||
if ( o.valueKey ) updateData[`${this.attribute}.${o.valueKey}`] = chosen;
|
||||
else updateData[this.attribute] = chosen;
|
||||
if ( o.allowCustom ) updateData[`${this.attribute}.${o.customKey}`] = formData.custom;
|
||||
|
||||
// Validate the number chosen
|
||||
if ( this.options.minimum && (chosen.length < this.options.minimum) ) {
|
||||
return ui.notifications.error(`You must choose at least ${this.options.minimum} options`);
|
||||
if ( o.minimum && (chosen.length < o.minimum) ) {
|
||||
return ui.notifications.error(`You must choose at least ${o.minimum} options`);
|
||||
}
|
||||
if ( this.options.maximum && (chosen.length > this.options.maximum) ) {
|
||||
return ui.notifications.error(`You may choose no more than ${this.options.maximum} options`);
|
||||
}
|
||||
|
||||
// Include custom
|
||||
if ( this.options.allowCustom ) {
|
||||
updateData[`${this.attribute}.custom`] = formData.custom;
|
||||
if ( o.maximum && (chosen.length > o.maximum) ) {
|
||||
return ui.notifications.error(`You may choose no more than ${o.maximum} options`);
|
||||
}
|
||||
|
||||
// Update the object
|
||||
|
|
|
@ -35,20 +35,4 @@ export const measureDistances = function(segments, options={}) {
|
|||
// Standard PHB Movement
|
||||
else return (ns + nd) * canvas.scene.data.gridDistance;
|
||||
});
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Hijack Token health bar rendering to include temporary and temp-max health in the bar display
|
||||
* TODO: This should probably be replaced with a formal Token class extension
|
||||
*/
|
||||
const _TokenGetBarAttribute = Token.prototype.getBarAttribute;
|
||||
export const getBarAttribute = function(...args) {
|
||||
const data = _TokenGetBarAttribute.bind(this)(...args);
|
||||
if ( data && (data.attribute === "attributes.hp") ) {
|
||||
data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0);
|
||||
data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
};
|
|
@ -40,7 +40,7 @@ export const displayChatActionButtons = function(message, html, data) {
|
|||
|
||||
// If the user is the message author or the actor owner, proceed
|
||||
let actor = game.actors.get(data.message.speaker.actor);
|
||||
if ( actor && actor.owner ) return;
|
||||
if ( actor && actor.isOwner ) return;
|
||||
else if ( game.user.isGM || (data.author.id === game.user.id)) return;
|
||||
|
||||
// Otherwise conceal action buttons except for saving throw
|
||||
|
@ -66,7 +66,7 @@ export const displayChatActionButtons = function(message, html, data) {
|
|||
export const addChatMessageContextOptions = function(html, options) {
|
||||
let canApply = li => {
|
||||
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(
|
||||
{
|
||||
|
|
|
@ -5,20 +5,19 @@
|
|||
* Apply the dexterity score as a decimal tiebreaker if requested
|
||||
* See Combat._getInitiativeFormula for more detail.
|
||||
*/
|
||||
export const _getInitiativeFormula = function(combatant) {
|
||||
const actor = combatant.actor;
|
||||
export const _getInitiativeFormula = function() {
|
||||
const actor = this.actor;
|
||||
if ( !actor ) return "1d20";
|
||||
const init = actor.data.data.attributes.init;
|
||||
|
||||
// Construct initiative formula parts
|
||||
let nd = 1;
|
||||
let mods = "";
|
||||
|
||||
if (actor.getFlag("sw5e", "halflingLucky")) mods += "r1=1";
|
||||
if (actor.getFlag("sw5e", "initiativeAdv")) {
|
||||
nd = 2;
|
||||
mods += "kh";
|
||||
}
|
||||
|
||||
const parts = [`${nd}d20${mods}`, init.mod, (init.prof !== 0) ? init.prof : null, (init.bonus !== 0) ? init.bonus : null];
|
||||
|
||||
// Optionally apply Dexterity tiebreaker
|
||||
|
|
207
module/config.js
207
module/config.js
|
@ -63,7 +63,7 @@ SW5E.attunementTypes = {
|
|||
NONE: 0,
|
||||
REQUIRED: 1,
|
||||
ATTUNED: 2,
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An enumeration of item attunement states
|
||||
|
@ -77,7 +77,6 @@ SW5E.attunements = {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
SW5E.weaponProficiencies = {
|
||||
"blp": "SW5E.WeaponBlasterPistolProficiency",
|
||||
"chk": "SW5E.WeaponChakramProficiency",
|
||||
|
@ -89,13 +88,13 @@ SW5E.weaponProficiencies = {
|
|||
"imp": "SW5E.WeaponImprovisedProficiency",
|
||||
"lfl": "SW5E.WeaponLightFoilProficiency",
|
||||
"lrg": "SW5E.WeaponLightRingProficiency",
|
||||
"mar": "SW5E.WeaponMartialProficiency",
|
||||
"mar": "SW5E.WeaponMartialProficiency",
|
||||
"mrb": "SW5E.WeaponMartialBlasterProficiency",
|
||||
"mlw": "SW5E.WeaponMartialLightweaponProficiency",
|
||||
"mvb": "SW5E.WeaponMartialVibroweaponProficiency",
|
||||
"ntl": "SW5E.WeaponNaturalProficiency",
|
||||
"swh": "SW5E.WeaponSaberWhipProficiency",
|
||||
"sim": "SW5E.WeaponSimpleProficiency",
|
||||
"sim": "SW5E.WeaponSimpleProficiency",
|
||||
"smb": "SW5E.WeaponSimpleBlasterProficiency",
|
||||
"slw": "SW5E.WeaponSimpleLightweaponProficiency",
|
||||
"svb": "SW5E.WeaponSimpleVibroweaponProficiency",
|
||||
|
@ -104,6 +103,59 @@ SW5E.weaponProficiencies = {
|
|||
"vbw": "SW5E.WeaponVibrowhipProficiency"
|
||||
};
|
||||
|
||||
// TODO: Check to see if this can be used
|
||||
// It's not actually been used anywhere in DND5e 1.3.2
|
||||
// Note name mapped to ID in compendium
|
||||
/**
|
||||
* The basic weapon types in 5e. This enables specific weapon proficiencies or
|
||||
* starting equipment provided by classes and backgrounds.
|
||||
*
|
||||
* @enum {string}
|
||||
|
||||
SW5E.weaponIds = {
|
||||
"battleaxe": "I0WocDSuNpGJayPb",
|
||||
"blowgun": "wNWK6yJMHG9ANqQV",
|
||||
"club": "nfIRTECQIG81CvM4",
|
||||
"dagger": "0E565kQUBmndJ1a2",
|
||||
"dart": "3rCO8MTIdPGSW6IJ",
|
||||
"flail": "UrH3sMdnUDckIHJ6",
|
||||
"glaive": "rOG1OM2ihgPjOvFW",
|
||||
"greataxe": "1Lxk6kmoRhG8qQ0u",
|
||||
"greatclub": "QRCsxkCwWNwswL9o",
|
||||
"greatsword": "xMkP8BmFzElcsMaR",
|
||||
"halberd": "DMejWAc8r8YvDPP1",
|
||||
"handaxe": "eO7Fbv5WBk5zvGOc",
|
||||
"handcrossbow": "qaSro7kFhxD6INbZ",
|
||||
"heavycrossbow": "RmP0mYRn2J7K26rX",
|
||||
"javelin": "DWLMnODrnHn8IbAG",
|
||||
"lance": "RnuxdHUAIgxccVwj",
|
||||
"lightcrossbow": "ddWvQRLmnnIS0eLF",
|
||||
"lighthammer": "XVK6TOL4sGItssAE",
|
||||
"longbow": "3cymOVja8jXbzrdT",
|
||||
"longsword": "10ZP2Bu3vnCuYMIB",
|
||||
"mace": "Ajyq6nGwF7FtLhDQ",
|
||||
"maul": "DizirD7eqjh8n95A",
|
||||
"morningstar": "dX8AxCh9o0A9CkT3",
|
||||
"net": "aEiM49V8vWpWw7rU",
|
||||
"pike": "tC0kcqZT9HHAO0PD",
|
||||
"quarterstaff": "g2dWN7PQiMRYWzyk",
|
||||
"rapier": "Tobce1hexTnDk4sV",
|
||||
"scimitar": "fbC0Mg1a73wdFbqO",
|
||||
"shortsword": "osLzOwQdPtrK3rQH",
|
||||
"sickle": "i4NeNZ30ycwPDHMx",
|
||||
"spear": "OG4nBBydvmfWYXIk",
|
||||
"shortbow": "GJv6WkD7D2J6rP6M",
|
||||
"sling": "3gynWO9sN4OLGMWD",
|
||||
"trident": "F65ANO66ckP8FDMa",
|
||||
"warpick": "2YdfjN1PIIrSHZii",
|
||||
"warhammer": "F0Df164Xv1gWcYt0",
|
||||
"whip": "QKTyxoO0YDnAsbYe"
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
SW5E.toolProficiencies = {
|
||||
"armor": "SW5E.ToolArmormech",
|
||||
"arms": "SW5E.ToolArmstech",
|
||||
|
@ -137,6 +189,51 @@ SW5E.toolProficiencies = {
|
|||
"vehicle": "SW5E.ToolVehicle"
|
||||
};
|
||||
|
||||
// TODO: Same as weapon IDs
|
||||
// Also unused, and SW5E.toolProficiencies is already pretty verbose anyway
|
||||
/**
|
||||
* The basic tool types in 5e. This enables specific tool proficiencies or
|
||||
* starting equipment provided by classes and backgrounds.
|
||||
*
|
||||
* @enum {string}
|
||||
SW5E.toolIds = {
|
||||
"alchemist": "SztwZhbhZeCqyAes",
|
||||
"bagpipes": "yxHi57T5mmVt0oDr",
|
||||
"brewer": "Y9S75go1hLMXUD48",
|
||||
"calligrapher": "jhjo20QoiD5exf09",
|
||||
"card": "YwlHI3BVJapz4a3E",
|
||||
"carpenter": "8NS6MSOdXtUqD7Ib",
|
||||
"cartographer": "fC0lFK8P4RuhpfaU",
|
||||
"cobbler": "hM84pZnpCqKfi8XH",
|
||||
"cook": "Gflnp29aEv5Lc1ZM",
|
||||
"dice": "iBuTM09KD9IoM5L8",
|
||||
"disg": "IBhDAr7WkhWPYLVn",
|
||||
"drum": "69Dpr25pf4BjkHKb",
|
||||
"dulcimer": "NtdDkjmpdIMiX7I2",
|
||||
"flute": "eJOrPcAz9EcquyRQ",
|
||||
"forg": "cG3m4YlHfbQlLEOx",
|
||||
"glassblower": "rTbVrNcwApnuTz5E",
|
||||
"herb": "i89okN7GFTWHsvPy",
|
||||
"horn": "aa9KuBy4dst7WIW9",
|
||||
"jeweler": "YfBwELTgPFHmQdHh",
|
||||
"leatherworker": "PUMfwyVUbtyxgYbD",
|
||||
"lute": "qBydtUUIkv520DT7",
|
||||
"lyre": "EwG1EtmbgR3bM68U",
|
||||
"mason": "skUih6tBvcBbORzA",
|
||||
"navg": "YHCmjsiXxZ9UdUhU",
|
||||
"painter": "ccm5xlWhx74d6lsK",
|
||||
"panflute": "G5m5gYIx9VAUWC3J",
|
||||
"pois": "il2GNi8C0DvGLL9P",
|
||||
"potter": "hJS8yEVkqgJjwfWa",
|
||||
"shawm": "G3cqbejJpfB91VhP",
|
||||
"smith": "KndVe2insuctjIaj",
|
||||
"thief": "woWZ1sO5IUVGzo58",
|
||||
"tinker": "0d08g1i5WXnNrCNA",
|
||||
"viol": "baoe3U5BfMMMxhCU",
|
||||
"weaver": "ap9prThUB2y9lDyj",
|
||||
"woodcarver": "xKErqkLo4ASYr5EP",
|
||||
};
|
||||
*/
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -210,6 +307,34 @@ SW5E.tokenSizes = {
|
|||
"grg": 4
|
||||
};
|
||||
|
||||
/**
|
||||
* Colors used to visualize temporary and temporary maximum HP in token health bars
|
||||
* @enum {number}
|
||||
*/
|
||||
SW5E.tokenHPColors = {
|
||||
temp: 0x66CCFF,
|
||||
tempmax: 0x440066,
|
||||
negmax: 0x550000
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Creature types
|
||||
* @type {Object}
|
||||
*/
|
||||
SW5E.creatureTypes = {
|
||||
"aberration": "SW5E.CreatureAberration",
|
||||
"beast": "SW5E.CreatureBeast",
|
||||
"construct": "SW5E.CreatureConstruct",
|
||||
"droid": "SW5E.CreatureDroid",
|
||||
"force": "SW5E.CreatureForceEntity",
|
||||
"humanoid": "SW5E.CreatureHumanoid",
|
||||
"plant": "SW5E.CreaturePlant",
|
||||
"undead": "SW5E.CreatureUndead"
|
||||
};
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -254,7 +379,7 @@ SW5E.limitedUsePeriods = {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The set of equipment types for armor, clothing, and other objects which can ber worn by the character
|
||||
* The set of equipment types for armor, clothing, and other objects which can be worn by the character
|
||||
* @type {Object}
|
||||
*/
|
||||
SW5E.equipmentTypes = {
|
||||
|
@ -338,7 +463,7 @@ SW5E.damageTypes = {
|
|||
};
|
||||
|
||||
// Damage Resistance Types
|
||||
SW5E.damageResistanceTypes = duplicate(SW5E.damageTypes);
|
||||
SW5E.damageResistanceTypes = foundry.utils.deepClone(SW5E.damageTypes);
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
@ -392,7 +517,7 @@ SW5E.movementTypes = {
|
|||
"swim": "SW5E.MovementSwim",
|
||||
"turn": "SW5E.MovementTurn",
|
||||
"walk": "SW5E.MovementWalk",
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The valid units of measure for movement distances in the game system.
|
||||
|
@ -402,7 +527,7 @@ SW5E.movementTypes = {
|
|||
SW5E.movementUnits = {
|
||||
"ft": "SW5E.DistFt",
|
||||
"mi": "SW5E.DistMi"
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The valid units of measure for the range of an action or effect.
|
||||
|
@ -495,7 +620,7 @@ SW5E.healingTypes = {
|
|||
|
||||
/**
|
||||
* Enumerate the denominations of hit dice which can apply to classes in the SW5E system
|
||||
* @type {Array.<string>}
|
||||
* @type {string[]}
|
||||
*/
|
||||
SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12", "d20"];
|
||||
|
||||
|
@ -504,7 +629,7 @@ SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12", "d20"];
|
|||
|
||||
/**
|
||||
* Enumerate the denominations of power dice which can apply to starships in the SW5E system
|
||||
* @type {Array.<string>}
|
||||
* @enum {string}
|
||||
*/
|
||||
SW5E.powerDieTypes = [1, "d4", "d6", "d8", "d10", "d12"];
|
||||
|
||||
|
@ -896,6 +1021,32 @@ SW5E.powerLevels = {
|
|||
9: "SW5E.PowerLevel9"
|
||||
};
|
||||
|
||||
// TODO: This is used for spell scrolls, it maps the level to the compendium ID of the item the spell would be bound to
|
||||
// We could use this with, say, holocrons to produce scrolls
|
||||
/*
|
||||
// Power Scroll Compendium UUIDs
|
||||
SW5E.powerScrollIds = {
|
||||
0: "rQ6sO7HDWzqMhSI3",
|
||||
1: "9GSfMg0VOA2b4uFN",
|
||||
2: "XdDp6CKh9qEvPTuS",
|
||||
3: "hqVKZie7x9w3Kqds",
|
||||
4: "DM7hzgL836ZyUFB1",
|
||||
5: "wa1VF8TXHmkrrR35",
|
||||
6: "tI3rWx4bxefNCexS",
|
||||
7: "mtyw4NS1s7j2EJaD",
|
||||
8: "aOrinPg7yuDZEuWr",
|
||||
9: "O4YbkJkLlnsgUszZ"
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compendium packs used for localized items.
|
||||
* @enum {string}
|
||||
*/
|
||||
SW5E.sourcePacks = {
|
||||
ITEMS: "sw5e.items"
|
||||
}
|
||||
|
||||
// Polymorph options.
|
||||
SW5E.polymorphSettings = {
|
||||
keepPhysical: 'SW5E.PolymorphKeepPhysical',
|
||||
|
@ -1111,10 +1262,10 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"armorIntegration": {
|
||||
name: "SW5E.FlagsArmorIntegration",
|
||||
name: "SW5E.FlagsArmorIntegration",
|
||||
hint: "SW5E.FlagsArmorIntegrationHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
type: Boolean
|
||||
},
|
||||
"businessSavvy": {
|
||||
name: "SW5E.FlagsBusinessSavvy",
|
||||
|
@ -1123,7 +1274,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"cannibalize": {
|
||||
name: "SW5E.FlagsCannibalize",
|
||||
name: "SW5E.FlagsCannibalize",
|
||||
hint: "SW5E.FlagsCannibalizeHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1153,7 +1304,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"enthrallingPheromones": {
|
||||
name: "SW5E.FlagsEnthrallingPheromones",
|
||||
name: "SW5E.FlagsEnthrallingPheromones",
|
||||
hint: "SW5E.FlagsEnthrallingPheromonesHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1177,13 +1328,13 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"foreignBiology": {
|
||||
name: "SW5E.FlagsForeignBiology",
|
||||
name: "SW5E.FlagsForeignBiology",
|
||||
hint: "SW5E.FlagsForeignBiologyHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
},
|
||||
"furyOfTheSmall": {
|
||||
name: "SW5E.FlagsFuryOfTheSmall",
|
||||
name: "SW5E.FlagsFuryOfTheSmall",
|
||||
hint: "SW5E.FlagsFuryOfTheSmallHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1195,7 +1346,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"inscrutable": {
|
||||
name: "SW5E.FlagsInscrutable",
|
||||
name: "SW5E.FlagsInscrutable",
|
||||
hint: "SW5E.FlagsInscrutableHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1225,7 +1376,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"multipleHearts": {
|
||||
name: "SW5E.FlagsMultipleHearts",
|
||||
name: "SW5E.FlagsMultipleHearts",
|
||||
hint: "SW5E.FlagsMultipleHeartsHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1267,7 +1418,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"precognition": {
|
||||
name: "SW5E.FlagsPrecognition",
|
||||
name: "SW5E.FlagsPrecognition",
|
||||
hint: "SW5E.FlagsPrecognitionHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1280,9 +1431,9 @@ SW5E.characterFlags = {
|
|||
},
|
||||
"puny": {
|
||||
name: "SW5E.FlagsPuny",
|
||||
hint: "SW5E.FlagsPunyHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
hint: "SW5E.FlagsPunyHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
},
|
||||
"rapidReconstruction": {
|
||||
name: "SW5E.FlagsRapidReconstruction",
|
||||
|
@ -1291,7 +1442,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"rapidlyRegenerative": {
|
||||
name: "SW5E.FlagsRapidlyRegenerative",
|
||||
name: "SW5E.FlagsRapidlyRegenerative",
|
||||
hint: "SW5E.FlagsRapidlyRegenerativeHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1303,7 +1454,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"savageAttacks": {
|
||||
name: "SW5E.FlagsSavageAttacks",
|
||||
name: "SW5E.FlagsSavageAttacks",
|
||||
hint: "SW5E.FlagsSavageAttacksHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
@ -1315,15 +1466,15 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"strongLegged": {
|
||||
name: "SW5E.FlagsStrongLegged",
|
||||
name: "SW5E.FlagsStrongLegged",
|
||||
hint: "SW5E.FlagsStrongLeggedHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
},
|
||||
"sunlightSensitivity": {
|
||||
name: "SW5E.FlagsSunlightSensitivity",
|
||||
name: "SW5E.FlagsSunlightSensitivity",
|
||||
hint: "SW5E.FlagsSunlightSensitivityHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
},
|
||||
"surpriseAttack": {
|
||||
|
@ -1345,7 +1496,7 @@ SW5E.characterFlags = {
|
|||
type: Boolean
|
||||
},
|
||||
"tinker": {
|
||||
name: "SW5E.FlagsTinker",
|
||||
name: "SW5E.FlagsTinker",
|
||||
hint: "SW5E.FlagsTinkerHint",
|
||||
section: "SW5E.SpeciesTraits",
|
||||
type: Boolean
|
||||
|
|
457
module/dice.js
457
module/dice.js
|
@ -1,3 +1,6 @@
|
|||
export {default as D20Roll} from "./dice/d20-roll.js";
|
||||
export {default as DamageRoll} from "./dice/damage-roll.js";
|
||||
|
||||
/**
|
||||
* A standardized helper function for simplifying the constant parts of a multipart roll formula
|
||||
*
|
||||
|
@ -20,28 +23,29 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
|
|||
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.
|
||||
for (let term of terms) { // For each term
|
||||
if (term instanceof OperatorTerm) 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 constantFormula = Roll.getFormula(constantTerms); // Cleans up the constant terms and produces a new formula string
|
||||
const rollableFormula = Roll.getFormula(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
|
||||
// Mathematically evaluate the constant formula to produce a single constant term
|
||||
const constantPart = constantFormula ? Roll.safeEval(constantFormula) : undefined;
|
||||
|
||||
const parts = constantFirst ? // Order the rollable and constant terms, either constant first or second depending on the optional argumen
|
||||
[constantPart, rollableFormula] : [rollableFormula, constantPart];
|
||||
// Order the rollable and constant terms, either constant first or second depending on the optional argument
|
||||
const parts = constantFirst ? [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;
|
||||
|
@ -55,316 +59,203 @@ export function simplifyRollFormula(formula, data, {constantFirst = false} = {})
|
|||
* @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));
|
||||
const diceTerm = term instanceof DiceTerm;
|
||||
const operator = term instanceof OperatorTerm && ["+", "-"].includes(term.operator);
|
||||
const number = term instanceof NumericTerm;
|
||||
|
||||
return !(diceTerm || operator || number);
|
||||
return !(diceTerm || operator || number);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* D20 Roll */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A standardized helper function for managing core 5e "d20 rolls"
|
||||
*
|
||||
* A standardized helper function for managing core 5e d20 rolls.
|
||||
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
|
||||
* This chooses the default options of a normal attack with no bonus, Advantage, or Disadvantage respectively
|
||||
*
|
||||
* @param {Array} parts The dice roll component parts, excluding the initial d20
|
||||
* @param {Object} data Actor or item data against which to parse the roll
|
||||
* @param {Event|object} event The triggering event which initiated the roll
|
||||
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {string|null} template The HTML template used to render the roll dialog
|
||||
* @param {string|null} title The dice roll UI window title
|
||||
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
|
||||
* @param {string|null} flavor Flavor text to use in the posted chat message
|
||||
* @param {Boolean} fastForward Allow fast-forward advantage selection
|
||||
* @param {Function} onClose Callback for actions to take when the dialog form is closed
|
||||
* @param {Object} dialogOptions Modal dialog options
|
||||
* @param {boolean} advantage Apply advantage to the roll (unless otherwise specified)
|
||||
* @param {boolean} disadvantage Apply disadvantage to the roll (unless otherwise specified)
|
||||
* @param {number} critical The value of d20 result which represents a critical success
|
||||
* @param {number} fumble The value of d20 result which represents a critical failure
|
||||
* @param {number} targetValue Assign a target value against which the result of this roll should be compared
|
||||
* @param {boolean} elvenAccuracy Allow Elven Accuracy to modify this roll?
|
||||
* @param {boolean} halflingLucky Allow Halfling Luck to modify this roll?
|
||||
* @param {boolean} reliableTalent Allow Reliable Talent to modify this roll?
|
||||
* @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll
|
||||
* @param {object} messageData Additional data which is applied to the created Chat Message, if any
|
||||
* @param {string[]} parts The dice roll component parts, excluding the initial d20
|
||||
* @param {object} data Actor or item data against which to parse the roll
|
||||
*
|
||||
* @return {Promise} A Promise which resolves once the roll workflow has completed
|
||||
* @param {boolean} [advantage] Apply advantage to the roll (unless otherwise specified)
|
||||
* @param {boolean} [disadvantage] Apply disadvantage to the roll (unless otherwise specified)
|
||||
* @param {number} [critical] The value of d20 result which represents a critical success
|
||||
* @param {number} [fumble] The value of d20 result which represents a critical failure
|
||||
* @param {number} [targetValue] Assign a target value against which the result of this roll should be compared
|
||||
* @param {boolean} [elvenAccuracy] Allow Elven Accuracy to modify this roll?
|
||||
* @param {boolean} [halflingLucky] Allow Halfling Luck to modify this roll?
|
||||
* @param {boolean} [reliableTalent] Allow Reliable Talent to modify this roll?
|
||||
|
||||
* @param {boolean} [chooseModifier=false] Choose the ability modifier that should be used when the roll is made
|
||||
* @param {boolean} [fastForward=false] Allow fast-forward advantage selection
|
||||
* @param {Event} [event] The triggering event which initiated the roll
|
||||
* @param {string} [rollMode] A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {string} [template] The HTML template used to render the roll dialog
|
||||
* @param {string} [title] The dialog window title
|
||||
* @param {Object} [dialogOptions] Modal dialog options
|
||||
*
|
||||
* @param {boolean} [chatMessage=true] Automatically create a Chat Message for the result of this roll
|
||||
* @param {object} [messageData={}] Additional data which is applied to the created Chat Message, if any
|
||||
* @param {string} [rollMode] A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {object} [speaker] The ChatMessage speaker to pass when creating the chat
|
||||
* @param {string} [flavor] Flavor text to use in the posted chat message
|
||||
*
|
||||
* @return {Promise<D20Roll|null>} The evaluated D20Roll, or null if the workflow was cancelled
|
||||
*/
|
||||
export async function d20Roll({parts=[], data={}, event={}, rollMode=null, template=null, title=null, speaker=null,
|
||||
flavor=null, fastForward=null, dialogOptions,
|
||||
advantage=null, disadvantage=null, critical=20, fumble=1, targetValue=null,
|
||||
elvenAccuracy=false, halflingLucky=false, reliableTalent=false,
|
||||
chatMessage=true, messageData={}}={}) {
|
||||
export async function d20Roll({
|
||||
parts=[], data={}, // Roll creation
|
||||
advantage, disadvantage, fumble=1, critical=20, targetValue, elvenAccuracy, halflingLucky, reliableTalent, // Roll customization
|
||||
chooseModifier=false, fastForward=false, event, template, title, dialogOptions, // Dialog configuration
|
||||
chatMessage=true, messageData={}, rollMode, speaker, flavor // Chat Message customization
|
||||
}={}) {
|
||||
|
||||
// Prepare Message Data
|
||||
messageData.flavor = flavor || title;
|
||||
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
||||
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
||||
parts = parts.concat(["@bonus"]);
|
||||
// Handle input arguments
|
||||
const formula = ["1d20"].concat(parts).join(" + ");
|
||||
const {advantageMode, isFF} = _determineAdvantageMode({advantage, disadvantage, fastForward, event});
|
||||
const defaultRollMode = rollMode || game.settings.get("core", "rollMode");
|
||||
if ( chooseModifier && !isFF ) data["mod"] = "@mod";
|
||||
|
||||
// Handle fast-forward events
|
||||
let adv = 0;
|
||||
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||
if (fastForward) {
|
||||
if ( advantage ?? event.altKey ) adv = 1;
|
||||
else if ( disadvantage ?? (event.ctrlKey || event.metaKey) ) adv = -1;
|
||||
// Construct the D20Roll instance
|
||||
const roll = new CONFIG.Dice.D20Roll(formula, data, {
|
||||
flavor: flavor || title,
|
||||
advantageMode,
|
||||
defaultRollMode,
|
||||
critical,
|
||||
fumble,
|
||||
targetValue,
|
||||
elvenAccuracy,
|
||||
halflingLucky,
|
||||
reliableTalent
|
||||
});
|
||||
|
||||
// Prompt a Dialog to further configure the D20Roll
|
||||
if ( !isFF ) {
|
||||
const configured = await roll.configureDialog({
|
||||
title,
|
||||
chooseModifier,
|
||||
defaultRollMode: defaultRollMode,
|
||||
defaultAction: advantageMode,
|
||||
defaultAbility: data?.item?.ability,
|
||||
template
|
||||
}, dialogOptions);
|
||||
if ( configured === null ) return null;
|
||||
}
|
||||
|
||||
// Define the inner roll function
|
||||
const _roll = (parts, adv, form) => {
|
||||
|
||||
// Determine the d20 roll and modifiers
|
||||
let nd = 1;
|
||||
let mods = halflingLucky ? "r1=1" : "";
|
||||
|
||||
// Handle advantage
|
||||
if (adv === 1) {
|
||||
nd = elvenAccuracy ? 3 : 2;
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].advantage = true;
|
||||
mods += "kh";
|
||||
}
|
||||
|
||||
// Handle disadvantage
|
||||
else if (adv === -1) {
|
||||
nd = 2;
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].disadvantage = true;
|
||||
mods += "kl";
|
||||
}
|
||||
|
||||
// Prepend the d20 roll
|
||||
let formula = `${nd}d20${mods}`;
|
||||
if (reliableTalent) formula = `{${nd}d20${mods},10}kh`;
|
||||
parts.unshift(formula);
|
||||
|
||||
// Optionally include a situational bonus
|
||||
if ( form ) {
|
||||
data['bonus'] = form.bonus.value;
|
||||
messageOptions.rollMode = form.rollMode.value;
|
||||
}
|
||||
if (!data["bonus"]) parts.pop();
|
||||
|
||||
// Optionally include an ability score selection (used for tool checks)
|
||||
const ability = form ? form.ability : null;
|
||||
if (ability && ability.value) {
|
||||
data.ability = ability.value;
|
||||
const abl = data.abilities[data.ability];
|
||||
if (abl) {
|
||||
data.mod = abl.mod;
|
||||
messageData.flavor += ` (${CONFIG.SW5E.abilities[data.ability]})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the roll
|
||||
let roll = new Roll(parts.join(" + "), data);
|
||||
try {
|
||||
roll.roll();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Flag d20 options for any 20-sided dice in the roll
|
||||
for (let d of roll.dice) {
|
||||
if (d.faces === 20) {
|
||||
d.options.critical = critical;
|
||||
d.options.fumble = fumble;
|
||||
if ( adv === 1 ) d.options.advantage = true;
|
||||
else if ( adv === -1 ) d.options.disadvantage = true;
|
||||
if (targetValue) d.options.target = targetValue;
|
||||
}
|
||||
}
|
||||
|
||||
// If reliable talent was applied, add it to the flavor text
|
||||
if (reliableTalent && roll.dice[0].total < 10) {
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.FlagsReliableTalent")})`;
|
||||
}
|
||||
return roll;
|
||||
};
|
||||
|
||||
// Create the Roll instance
|
||||
const roll = fastForward ? _roll(parts, adv) :
|
||||
await _d20RollDialog({template, title, parts, data, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll});
|
||||
// Evaluate the configured roll
|
||||
await roll.evaluate({async: true});
|
||||
|
||||
// Create a Chat Message
|
||||
if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions);
|
||||
if ( speaker ) {
|
||||
console.warn(`You are passing the speaker argument to the d20Roll function directly which should instead be passed as an internal key of messageData`);
|
||||
messageData.speaker = speaker;
|
||||
}
|
||||
if ( roll && chatMessage ) await roll.toMessage(messageData);
|
||||
return roll;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Present a Dialog form which creates a d20 roll once submitted
|
||||
* @return {Promise<Roll>}
|
||||
* @private
|
||||
* Determines whether this d20 roll should be fast-forwarded, and whether advantage or disadvantage should be applied
|
||||
* @returns {{isFF: boolean, advantageMode: number}} Whether the roll is fast-forward, and its advantage mode
|
||||
*/
|
||||
async function _d20RollDialog({template, title, parts, data, rollMode, dialogOptions, roll}={}) {
|
||||
|
||||
// Render modal dialog
|
||||
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
||||
let dialogData = {
|
||||
formula: parts.join(" + "),
|
||||
data: data,
|
||||
rollMode: rollMode,
|
||||
rollModes: CONFIG.Dice.rollModes,
|
||||
config: CONFIG.SW5E
|
||||
};
|
||||
const html = await renderTemplate(template, dialogData);
|
||||
|
||||
// Create the Dialog window
|
||||
return new Promise(resolve => {
|
||||
new Dialog({
|
||||
title: title,
|
||||
content: html,
|
||||
buttons: {
|
||||
advantage: {
|
||||
label: game.i18n.localize("SW5E.Advantage"),
|
||||
callback: html => resolve(roll(parts, 1, html[0].querySelector("form")))
|
||||
},
|
||||
normal: {
|
||||
label: game.i18n.localize("SW5E.Normal"),
|
||||
callback: html => resolve(roll(parts, 0, html[0].querySelector("form")))
|
||||
},
|
||||
disadvantage: {
|
||||
label: game.i18n.localize("SW5E.Disadvantage"),
|
||||
callback: html => resolve(roll(parts, -1, html[0].querySelector("form")))
|
||||
}
|
||||
},
|
||||
default: "normal",
|
||||
close: () => resolve(null)
|
||||
}, dialogOptions).render(true);
|
||||
});
|
||||
function _determineAdvantageMode({event, advantage=false, disadvantage=false, fastForward=false}={}) {
|
||||
const isFF = fastForward || (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||
let advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.NORMAL;
|
||||
if ( advantage || event?.altKey ) advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.ADVANTAGE;
|
||||
else if ( disadvantage || event?.ctrlKey || event?.metaKey ) advantageMode = CONFIG.Dice.D20Roll.ADV_MODE.DISADVANTAGE;
|
||||
return {isFF, advantageMode};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Damage Roll */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A standardized helper function for managing core 5e "d20 rolls"
|
||||
* A standardized helper function for managing core 5e damage rolls.
|
||||
*
|
||||
* Holding SHIFT, ALT, or CTRL when the attack is rolled will "fast-forward".
|
||||
* This chooses the default options of a normal attack with no bonus, Critical, or no bonus respectively
|
||||
*
|
||||
* @param {Array} parts The dice roll component parts, excluding the initial d20
|
||||
* @param {Actor} actor The Actor making the damage roll
|
||||
* @param {Object} data Actor or item data against which to parse the roll
|
||||
* @param {Event|object}[event The triggering event which initiated the roll
|
||||
* @param {string} rollMode A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {String} template The HTML template used to render the roll dialog
|
||||
* @param {String} title The dice roll UI window title
|
||||
* @param {Object} speaker The ChatMessage speaker to pass when creating the chat
|
||||
* @param {string} flavor Flavor text to use in the posted chat message
|
||||
* @param {boolean} allowCritical Allow the opportunity for a critical hit to be rolled
|
||||
* @param {Boolean} critical Flag this roll as a critical hit for the purposes of fast-forward rolls
|
||||
* @param {number} criticalBonusDice A number of bonus damage dice that are added for critical hits
|
||||
* @param {number} criticalMultiplier A critical hit multiplier which is applied to critical hits
|
||||
* @param {Boolean} fastForward Allow fast-forward advantage selection
|
||||
* @param {Function} onClose Callback for actions to take when the dialog form is closed
|
||||
* @param {Object} dialogOptions Modal dialog options
|
||||
* @param {boolean} chatMessage Automatically create a Chat Message for the result of this roll
|
||||
* @param {object} messageData Additional data which is applied to the created Chat Message, if any
|
||||
* @param {string[]} parts The dice roll component parts, excluding the initial d20
|
||||
* @param {object} [data] Actor or item data against which to parse the roll
|
||||
*
|
||||
* @return {Promise} A Promise which resolves once the roll workflow has completed
|
||||
* @param {boolean} [critical=false] Flag this roll as a critical hit for the purposes of fast-forward or default dialog action
|
||||
* @param {number} [criticalBonusDice=0] A number of bonus damage dice that are added for critical hits
|
||||
* @param {number} [criticalMultiplier=2] A critical hit multiplier which is applied to critical hits
|
||||
* @param {boolean} [multiplyNumeric=false] Multiply numeric terms by the critical multiplier
|
||||
* @param {boolean} [powerfulCritical=false] Apply the "powerful criticals" house rule to critical hits
|
||||
|
||||
* @param {boolean} [fastForward=false] Allow fast-forward advantage selection
|
||||
* @param {Event}[event] The triggering event which initiated the roll
|
||||
* @param {boolean} [allowCritical=true] Allow the opportunity for a critical hit to be rolled
|
||||
* @param {string} [template] The HTML template used to render the roll dialog
|
||||
* @param {string} [title] The dice roll UI window title
|
||||
* @param {object} [dialogOptions] Configuration dialog options
|
||||
*
|
||||
* @param {boolean} [chatMessage=true] Automatically create a Chat Message for the result of this roll
|
||||
* @param {object} [messageData={}] Additional data which is applied to the created Chat Message, if any
|
||||
* @param {string} [rollMode] A specific roll mode to apply as the default for the resulting roll
|
||||
* @param {object} [speaker] The ChatMessage speaker to pass when creating the chat
|
||||
* @param {string} [flavor] Flavor text to use in the posted chat message
|
||||
*
|
||||
* @return {Promise<DamageRoll|null>} The evaluated DamageRoll, or null if the workflow was canceled
|
||||
*/
|
||||
export async function damageRoll({parts, actor, data, event={}, rollMode=null, template, title, speaker, flavor,
|
||||
allowCritical=true, critical=false, criticalBonusDice=0, criticalMultiplier=2, fastForward=null,
|
||||
dialogOptions={}, chatMessage=true, messageData={}}={}) {
|
||||
export async function damageRoll({
|
||||
parts=[], data, // Roll creation
|
||||
critical=false, criticalBonusDice, criticalMultiplier, multiplyNumeric, powerfulCritical, // Damage customization
|
||||
fastForward=false, event, allowCritical=true, template, title, dialogOptions, // Dialog configuration
|
||||
chatMessage=true, messageData={}, rollMode, speaker, flavor, // Chat Message customization
|
||||
}={}) {
|
||||
|
||||
// Prepare Message Data
|
||||
messageData.flavor = flavor || title;
|
||||
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
||||
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
||||
parts = parts.concat(["@bonus"]);
|
||||
// Handle input arguments
|
||||
const defaultRollMode = rollMode || game.settings.get("core", "rollMode");
|
||||
|
||||
// Define inner roll function
|
||||
const _roll = function(parts, crit, form) {
|
||||
|
||||
// Optionally include a situational bonus
|
||||
if ( form ) {
|
||||
data['bonus'] = form.bonus.value;
|
||||
messageOptions.rollMode = form.rollMode.value;
|
||||
}
|
||||
if (!data["bonus"]) parts.pop();
|
||||
|
||||
// Create the damage roll
|
||||
let roll = new Roll(parts.join("+"), data);
|
||||
|
||||
// Modify the damage formula for critical hits
|
||||
if ( crit === true ) {
|
||||
roll.alter(criticalMultiplier, 0); // Multiply all dice
|
||||
if ( roll.terms[0] instanceof Die ) { // Add bonus dice for only the main dice term
|
||||
roll.terms[0].alter(1, criticalBonusDice);
|
||||
roll._formula = roll.formula;
|
||||
}
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
|
||||
}
|
||||
|
||||
// Execute the roll
|
||||
try {
|
||||
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) {
|
||||
console.error(err);
|
||||
ui.notifications.error(`Dice roll evaluation failed: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Create the Roll instance
|
||||
const roll = fastForward ? _roll(parts, critical) : await _damageRollDialog({
|
||||
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll
|
||||
// Construct the DamageRoll instance
|
||||
const formula = parts.join(" + ");
|
||||
const {isCritical, isFF} = _determineCriticalMode({critical, fastForward, event});
|
||||
const roll = new CONFIG.Dice.DamageRoll(formula, data, {
|
||||
flavor: flavor || title,
|
||||
critical: isCritical,
|
||||
criticalBonusDice,
|
||||
criticalMultiplier,
|
||||
multiplyNumeric,
|
||||
powerfulCritical
|
||||
});
|
||||
|
||||
// Create a Chat Message
|
||||
if ( roll && chatMessage ) roll.toMessage(messageData, messageOptions);
|
||||
return roll;
|
||||
// Prompt a Dialog to further configure the DamageRoll
|
||||
if ( !isFF ) {
|
||||
const configured = await roll.configureDialog({
|
||||
title,
|
||||
defaultRollMode: defaultRollMode,
|
||||
defaultCritical: isCritical,
|
||||
template,
|
||||
allowCritical
|
||||
}, dialogOptions);
|
||||
if ( configured === null ) return null;
|
||||
}
|
||||
|
||||
// Evaluate the configured roll
|
||||
await roll.evaluate({async: true});
|
||||
|
||||
// Create a Chat Message
|
||||
if ( speaker ) {
|
||||
console.warn(`You are passing the speaker argument to the damageRoll function directly which should instead be passed as an internal key of messageData`);
|
||||
messageData.speaker = speaker;
|
||||
}
|
||||
if ( roll && chatMessage ) await roll.toMessage(messageData);
|
||||
return roll;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Present a Dialog form which creates a damage roll once submitted
|
||||
* @return {Promise<Roll>}
|
||||
* @private
|
||||
* Determines whether this d20 roll should be fast-forwarded, and whether advantage or disadvantage should be applied
|
||||
* @returns {{isFF: boolean, isCritical: boolean}} Whether the roll is fast-forward, and whether it is a critical hit
|
||||
*/
|
||||
async function _damageRollDialog({template, title, parts, data, allowCritical, rollMode, dialogOptions, roll}={}) {
|
||||
|
||||
// Render modal dialog
|
||||
template = template || "systems/sw5e/templates/chat/roll-dialog.html";
|
||||
let dialogData = {
|
||||
formula: parts.join(" + "),
|
||||
data: data,
|
||||
rollMode: rollMode,
|
||||
rollModes: CONFIG.Dice.rollModes
|
||||
};
|
||||
const html = await renderTemplate(template, dialogData);
|
||||
|
||||
// Create the Dialog window
|
||||
return new Promise(resolve => {
|
||||
new Dialog({
|
||||
title: title,
|
||||
content: html,
|
||||
buttons: {
|
||||
critical: {
|
||||
condition: allowCritical,
|
||||
label: game.i18n.localize("SW5E.CriticalHit"),
|
||||
callback: html => resolve(roll(parts, true, html[0].querySelector("form")))
|
||||
},
|
||||
normal: {
|
||||
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
|
||||
callback: html => resolve(roll(parts, false, html[0].querySelector("form")))
|
||||
},
|
||||
},
|
||||
default: "normal",
|
||||
close: () => resolve(null)
|
||||
}, dialogOptions).render(true);
|
||||
});
|
||||
function _determineCriticalMode({event, critical=false, fastForward=false}={}) {
|
||||
const isFF = fastForward || (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||
if ( event?.altKey ) critical = true;
|
||||
return {isFF, isCritical: critical};
|
||||
}
|
||||
|
|
217
module/dice/d20-roll.js
Normal file
217
module/dice/d20-roll.js
Normal file
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
* A type of Roll specific to a d20-based check, save, or attack roll in the 5e system.
|
||||
* @param {string} formula The string formula to parse
|
||||
* @param {object} data The data object against which to parse attributes within the formula
|
||||
* @param {object} [options={}] Extra optional arguments which describe or modify the D20Roll
|
||||
* @param {number} [options.advantageMode] What advantage modifier to apply to the roll (none, advantage, disadvantage)
|
||||
* @param {number} [options.critical] The value of d20 result which represents a critical success
|
||||
* @param {number} [options.fumble] The value of d20 result which represents a critical failure
|
||||
* @param {(number)} [options.targetValue] Assign a target value against which the result of this roll should be compared
|
||||
* @param {boolean} [options.elvenAccuracy=false] Allow Elven Accuracy to modify this roll?
|
||||
* @param {boolean} [options.halflingLucky=false] Allow Halfling Luck to modify this roll?
|
||||
* @param {boolean} [options.reliableTalent=false] Allow Reliable Talent to modify this roll?
|
||||
*/
|
||||
// TODO: Check elven accuracy, halfling lucky, and reliable talent are required
|
||||
// Elven Accuracy is Supreme accuracy feat, Reliable Talent is operative's Reliable Talent Class Feat
|
||||
export default class D20Roll extends Roll {
|
||||
constructor(formula, data, options) {
|
||||
super(formula, data, options);
|
||||
if ( !((this.terms[0] instanceof Die) && (this.terms[0].faces === 20)) ) {
|
||||
throw new Error(`Invalid D20Roll formula provided ${this._formula}`);
|
||||
}
|
||||
this.configureModifiers();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Advantage mode of a 5e d20 roll
|
||||
* @enum {number}
|
||||
*/
|
||||
static ADV_MODE = {
|
||||
NORMAL: 0,
|
||||
ADVANTAGE: 1,
|
||||
DISADVANTAGE: -1,
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML template path used to configure evaluation of this Roll
|
||||
* @type {string}
|
||||
*/
|
||||
static EVALUATION_TEMPLATE = "systems/sw5e/templates/chat/roll-dialog.html";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A convenience reference for whether this D20Roll has advantage
|
||||
* @type {boolean}
|
||||
*/
|
||||
get hasAdvantage() {
|
||||
return this.options.advantageMode === D20Roll.ADV_MODE.ADVANTAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience reference for whether this D20Roll has disadvantage
|
||||
* @type {boolean}
|
||||
*/
|
||||
get hasDisadvantage() {
|
||||
return this.options.advantageMode === D20Roll.ADV_MODE.DISADVANTAGE;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* D20 Roll Methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Apply optional modifiers which customize the behavior of the d20term
|
||||
* @private
|
||||
*/
|
||||
configureModifiers() {
|
||||
const d20 = this.terms[0];
|
||||
d20.modifiers = [];
|
||||
|
||||
// Halfling Lucky
|
||||
if ( this.options.halflingLucky ) d20.modifiers.push("r1=1");
|
||||
|
||||
// Reliable Talent
|
||||
if ( this.options.reliableTalent ) d20.modifiers.push("min10");
|
||||
|
||||
// Handle Advantage or Disadvantage
|
||||
if ( this.hasAdvantage ) {
|
||||
d20.number = this.options.elvenAccuracy ? 3 : 2;
|
||||
d20.modifiers.push("kh");
|
||||
d20.options.advantage = true;
|
||||
}
|
||||
else if ( this.hasDisadvantage ) {
|
||||
d20.number = 2;
|
||||
d20.modifiers.push("kl");
|
||||
d20.options.disadvantage = true;
|
||||
}
|
||||
else d20.number = 1;
|
||||
|
||||
// Assign critical and fumble thresholds
|
||||
if ( this.options.critical ) d20.options.critical = this.options.critical;
|
||||
if ( this.options.fumble ) d20.options.fumble = this.options.fumble;
|
||||
if ( this.options.targetValue ) d20.options.target = this.options.targetValue;
|
||||
|
||||
// Re-compile the underlying formula
|
||||
this._formula = this.constructor.getFormula(this.terms);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
async toMessage(messageData={}, options={}) {
|
||||
|
||||
// Evaluate the roll now so we have the results available to determine whether reliable talent came into play
|
||||
if ( !this._evaluated ) await this.evaluate({async: true});
|
||||
|
||||
// Add appropriate advantage mode message flavor and sw5e roll flags
|
||||
messageData.flavor = messageData.flavor || this.options.flavor;
|
||||
if ( this.hasAdvantage ) messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
|
||||
else if ( this.hasDisadvantage ) messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
||||
|
||||
// Add reliable talent to the d20-term flavor text if it applied
|
||||
if ( this.options.reliableTalent ) {
|
||||
const d20 = this.dice[0];
|
||||
const isRT = d20.results.every(r => !r.active || (r.result < 10));
|
||||
const label = `(${game.i18n.localize("SW5E.FlagsReliableTalent")})`;
|
||||
if ( isRT ) d20.options.flavor = d20.options.flavor ? `${d20.options.flavor} (${label})` : label;
|
||||
}
|
||||
|
||||
// Record the preferred rollMode
|
||||
options.rollMode = options.rollMode ?? this.options.rollMode;
|
||||
return super.toMessage(messageData, options);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Configuration Dialog */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a Dialog prompt used to configure evaluation of an existing D20Roll instance.
|
||||
* @param {object} data Dialog configuration data
|
||||
* @param {string} [data.title] The title of the shown dialog window
|
||||
* @param {number} [data.defaultRollMode] The roll mode that the roll mode select element should default to
|
||||
* @param {number} [data.defaultAction] The button marked as default
|
||||
* @param {boolean} [data.chooseModifier] Choose which ability modifier should be applied to the roll?
|
||||
* @param {string} [data.defaultAbility] For tool rolls, the default ability modifier applied to the roll
|
||||
* @param {string} [data.template] A custom path to an HTML template to use instead of the default
|
||||
* @param {object} options Additional Dialog customization options
|
||||
* @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed
|
||||
*/
|
||||
async configureDialog({title, defaultRollMode, defaultAction=D20Roll.ADV_MODE.NORMAL, chooseModifier=false, defaultAbility, template}={}, options={}) {
|
||||
|
||||
// Render the Dialog inner HTML
|
||||
const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, {
|
||||
formula: `${this.formula} + @bonus`,
|
||||
defaultRollMode,
|
||||
rollModes: CONFIG.Dice.rollModes,
|
||||
chooseModifier,
|
||||
defaultAbility,
|
||||
abilities: CONFIG.SW5E.abilities
|
||||
});
|
||||
|
||||
let defaultButton = "normal";
|
||||
switch (defaultAction) {
|
||||
case D20Roll.ADV_MODE.ADVANTAGE: defaultButton = "advantage"; break;
|
||||
case D20Roll.ADV_MODE.DISADVANTAGE: defaultButton = "disadvantage"; break;
|
||||
}
|
||||
|
||||
// Create the Dialog window and await submission of the form
|
||||
return new Promise(resolve => {
|
||||
new Dialog({
|
||||
title,
|
||||
content,
|
||||
buttons: {
|
||||
advantage: {
|
||||
label: game.i18n.localize("SW5E.Advantage"),
|
||||
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.ADVANTAGE))
|
||||
},
|
||||
normal: {
|
||||
label: game.i18n.localize("SW5E.Normal"),
|
||||
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.NORMAL))
|
||||
},
|
||||
disadvantage: {
|
||||
label: game.i18n.localize("SW5E.Disadvantage"),
|
||||
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.DISADVANTAGE))
|
||||
}
|
||||
},
|
||||
default: defaultButton,
|
||||
close: () => resolve(null)
|
||||
}, options).render(true);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle submission of the Roll evaluation configuration Dialog
|
||||
* @param {jQuery} html The submitted dialog content
|
||||
* @param {number} advantageMode The chosen advantage mode
|
||||
* @private
|
||||
*/
|
||||
_onDialogSubmit(html, advantageMode) {
|
||||
const form = html[0].querySelector("form");
|
||||
|
||||
// Append a situational bonus term
|
||||
if ( form.bonus.value ) {
|
||||
const bonus = new Roll(form.bonus.value, this.data);
|
||||
if ( !(bonus.terms[0] instanceof OperatorTerm) ) this.terms.push(new OperatorTerm({operator: "+"}));
|
||||
this.terms = this.terms.concat(bonus.terms);
|
||||
}
|
||||
|
||||
// Customize the modifier
|
||||
if ( form.ability?.value ) {
|
||||
const abl = this.data.abilities[form.ability.value];
|
||||
this.terms.findSplice(t => t.term === "@mod", new NumericTerm({number: abl.mod}));
|
||||
this.options.flavor += ` (${CONFIG.SW5E.abilities[form.ability.value]})`;
|
||||
}
|
||||
|
||||
// Apply advantage or disadvantage
|
||||
this.options.advantageMode = advantageMode;
|
||||
this.options.rollMode = form.rollMode.value;
|
||||
this.configureModifiers();
|
||||
return this;
|
||||
}
|
||||
}
|
181
module/dice/damage-roll.js
Normal file
181
module/dice/damage-roll.js
Normal file
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* A type of Roll specific to a damage (or healing) roll in the 5e system.
|
||||
* @param {string} formula The string formula to parse
|
||||
* @param {object} data The data object against which to parse attributes within the formula
|
||||
* @param {object} [options={}] Extra optional arguments which describe or modify the DamageRoll
|
||||
* @param {number} [options.criticalBonusDice=0] A number of bonus damage dice that are added for critical hits
|
||||
* @param {number} [options.criticalMultiplier=2] A critical hit multiplier which is applied to critical hits
|
||||
* @param {boolean} [options.multiplyNumeric=false] Multiply numeric terms by the critical multiplier
|
||||
* @param {boolean} [options.powerfulCritical=false] Apply the "powerful criticals" house rule to critical hits
|
||||
*
|
||||
*/
|
||||
export default class DamageRoll extends Roll {
|
||||
constructor(formula, data, options) {
|
||||
super(formula, data, options);
|
||||
// For backwards compatibility, skip rolls which do not have the "critical" option defined
|
||||
if ( this.options.critical !== undefined ) this.configureDamage();
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML template path used to configure evaluation of this Roll
|
||||
* @type {string}
|
||||
*/
|
||||
static EVALUATION_TEMPLATE = "systems/sw5e/templates/chat/roll-dialog.html";
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A convenience reference for whether this DamageRoll is a critical hit
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isCritical() {
|
||||
return this.options.critical;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Damage Roll Methods */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Apply optional modifiers which customize the behavior of the d20term
|
||||
* @private
|
||||
*/
|
||||
configureDamage() {
|
||||
let flatBonus = 0;
|
||||
for ( let [i, term] of this.terms.entries() ) {
|
||||
|
||||
// Multiply dice terms
|
||||
if ( term instanceof DiceTerm ) {
|
||||
term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back
|
||||
term.number = term.options.baseNumber;
|
||||
if ( this.isCritical ) {
|
||||
let cm = this.options.criticalMultiplier ?? 2;
|
||||
|
||||
// Powerful critical - maximize damage and reduce the multiplier by 1
|
||||
if ( this.options.powerfulCritical ) {
|
||||
flatBonus += (term.number * term.faces);
|
||||
cm = Math.max(1, cm-1);
|
||||
}
|
||||
|
||||
// Alter the damage term
|
||||
let cb = (this.options.criticalBonusDice && (i === 0)) ? this.options.criticalBonusDice : 0;
|
||||
term.alter(cm, cb);
|
||||
term.options.critical = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Multiply numeric terms
|
||||
else if ( this.options.multiplyNumeric && (term instanceof NumericTerm) ) {
|
||||
term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back
|
||||
term.number = term.options.baseNumber;
|
||||
if ( this.isCritical ) {
|
||||
term.number *= (this.options.criticalMultiplier ?? 2);
|
||||
term.options.critical = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add powerful critical bonus
|
||||
if ( this.options.powerfulCritical && (flatBonus > 0) ) {
|
||||
this.terms.push(new OperatorTerm({operator: "+"}));
|
||||
this.terms.push(new NumericTerm({number: flatBonus}, {flavor: game.i18n.localize("SW5E.PowerfulCritical")}));
|
||||
}
|
||||
|
||||
// Re-compile the underlying formula
|
||||
this._formula = this.constructor.getFormula(this.terms);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
toMessage(messageData={}, options={}) {
|
||||
messageData.flavor = messageData.flavor || this.options.flavor;
|
||||
if ( this.isCritical ) {
|
||||
const label = game.i18n.localize("SW5E.CriticalHit");
|
||||
messageData.flavor = messageData.flavor ? `${messageData.flavor} (${label})` : label;
|
||||
}
|
||||
options.rollMode = options.rollMode ?? this.options.rollMode;
|
||||
return super.toMessage(messageData, options);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Configuration Dialog */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a Dialog prompt used to configure evaluation of an existing D20Roll instance.
|
||||
* @param {object} data Dialog configuration data
|
||||
* @param {string} [data.title] The title of the shown dialog window
|
||||
* @param {number} [data.defaultRollMode] The roll mode that the roll mode select element should default to
|
||||
* @param {string} [data.defaultCritical] Should critical be selected as default
|
||||
* @param {string} [data.template] A custom path to an HTML template to use instead of the default
|
||||
* @param {boolean} [data.allowCritical=true] Allow critical hit to be chosen as a possible damage mode
|
||||
* @param {object} options Additional Dialog customization options
|
||||
* @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed
|
||||
*/
|
||||
async configureDialog({title, defaultRollMode, defaultCritical=false, template, allowCritical=true}={}, options={}) {
|
||||
|
||||
// Render the Dialog inner HTML
|
||||
const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, {
|
||||
formula: `${this.formula} + @bonus`,
|
||||
defaultRollMode,
|
||||
rollModes: CONFIG.Dice.rollModes,
|
||||
});
|
||||
|
||||
// Create the Dialog window and await submission of the form
|
||||
return new Promise(resolve => {
|
||||
new Dialog({
|
||||
title,
|
||||
content,
|
||||
buttons: {
|
||||
critical: {
|
||||
condition: allowCritical,
|
||||
label: game.i18n.localize("SW5E.CriticalHit"),
|
||||
callback: html => resolve(this._onDialogSubmit(html, true))
|
||||
},
|
||||
normal: {
|
||||
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
|
||||
callback: html => resolve(this._onDialogSubmit(html, false))
|
||||
}
|
||||
},
|
||||
default: defaultCritical ? "critical" : "normal",
|
||||
close: () => resolve(null)
|
||||
}, options).render(true);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle submission of the Roll evaluation configuration Dialog
|
||||
* @param {jQuery} html The submitted dialog content
|
||||
* @param {boolean} isCritical Is the damage a critical hit?
|
||||
* @private
|
||||
*/
|
||||
_onDialogSubmit(html, isCritical) {
|
||||
const form = html[0].querySelector("form");
|
||||
|
||||
// Append a situational bonus term
|
||||
if ( form.bonus.value ) {
|
||||
const bonus = new Roll(form.bonus.value, this.data);
|
||||
if ( !(bonus.terms[0] instanceof OperatorTerm) ) this.terms.push(new OperatorTerm({operator: "+"}));
|
||||
this.terms = this.terms.concat(bonus.terms);
|
||||
}
|
||||
|
||||
// Apply advantage or disadvantage
|
||||
this.options.critical = isCritical;
|
||||
this.options.rollMode = form.rollMode.value;
|
||||
this.configureDamage();
|
||||
return this;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
static fromData(data) {
|
||||
const roll = super.fromData(data);
|
||||
roll._formula = this.getFormula(roll.terms);
|
||||
return roll;
|
||||
}
|
||||
}
|
15
module/dice/roll-dialog.js
Normal file
15
module/dice/roll-dialog.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @deprecated since 1.3.0
|
||||
* @ignore
|
||||
*/
|
||||
async function d20Dialog(data, options) {
|
||||
throw new Error(`The d20Dialog helper method is deprecated in favor of D20Roll#configureDialog`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.3.0
|
||||
* @ignore
|
||||
*/
|
||||
async function damageDialog(data, options) {
|
||||
throw new Error(`The damageDialog helper method is deprecated in favor of DamageRoll#configureDialog`);
|
||||
}
|
4
module/effects.js
vendored
4
module/effects.js
vendored
|
@ -10,13 +10,13 @@ export function onManageActiveEffect(event, owner) {
|
|||
const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null;
|
||||
switch ( a.dataset.action ) {
|
||||
case "create":
|
||||
return ActiveEffect.create({
|
||||
return owner.createEmbeddedDocuments("ActiveEffect", [{
|
||||
label: "New Effect",
|
||||
icon: "icons/svg/aura.svg",
|
||||
origin: owner.uuid,
|
||||
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
|
||||
disabled: li.dataset.effectType === "inactive"
|
||||
}, owner).create();
|
||||
}]);
|
||||
case "edit":
|
||||
return effect.sheet.render(true);
|
||||
case "delete":
|
||||
|
|
|
@ -2,7 +2,8 @@ import {simplifyRollFormula, d20Roll, damageRoll} from "../dice.js";
|
|||
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
||||
|
||||
/**
|
||||
* Override and extend the basic :class:`Item` implementation
|
||||
* Override and extend the basic Item implementation
|
||||
* @extends {Item}
|
||||
*/
|
||||
export default class Item5e extends Item {
|
||||
|
||||
|
@ -44,12 +45,15 @@ export default class Item5e extends Item {
|
|||
else if (this.data.type === "weapon") {
|
||||
const wt = itemData.weaponType;
|
||||
|
||||
// Melee weapons - Str or Dex if Finesse (PHB pg. 147)
|
||||
if ( ["simpleVW", "martialVW", "simpleLW", "martialLW"].includes(wt) ) {
|
||||
if (itemData.properties.fin === true) { // Finesse weapons
|
||||
return (actorData.abilities["dex"].mod >= actorData.abilities["str"].mod) ? "dex" : "str";
|
||||
}
|
||||
return "str";
|
||||
// Weapons using the powercasting modifier
|
||||
// No current SW5e weapons use this, but it's worth checking just in case
|
||||
if (["mpak", "rpak"].includes(itemData.actionType)) {
|
||||
return actorData.attributes.powercasting || "int";
|
||||
}
|
||||
|
||||
// Finesse weapons - Str or Dex (PHB pg. 147)
|
||||
else if (itemData.properties.fin === true) {
|
||||
return (actorData.abilities["dex"].mod >= actorData.abilities["str"].mod) ? "dex" : "str";
|
||||
}
|
||||
|
||||
// Ranged weapons - Dex (PH p.194)
|
||||
|
@ -144,7 +148,7 @@ export default class Item5e extends Item {
|
|||
get hasLimitedUses() {
|
||||
let chg = this.data.data.recharge || {};
|
||||
let uses = this.data.data.uses || {};
|
||||
return !!chg.value || (!!uses.per && (uses.max > 0));
|
||||
return !!chg.value || (uses.per && (uses.max > 0));
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -154,8 +158,8 @@ export default class Item5e extends Item {
|
|||
/**
|
||||
* Augment the basic Item data model with additional dynamic data.
|
||||
*/
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
|
||||
// Get the Item's data
|
||||
const itemData = this.data;
|
||||
|
@ -190,6 +194,7 @@ export default class Item5e extends Item {
|
|||
else labels.featType = game.i18n.localize("SW5E.Passive");
|
||||
}
|
||||
|
||||
// TODO: Something with all this
|
||||
// Species Items
|
||||
else if ( itemData.type === "species" ) {
|
||||
// labels.species = C.species[data.species];
|
||||
|
@ -268,34 +273,64 @@ export default class Item5e extends Item {
|
|||
|
||||
// Item Actions
|
||||
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();
|
||||
|
||||
// To Hit
|
||||
this.getAttackToHit();
|
||||
}
|
||||
|
||||
// Damage
|
||||
let dam = data.damage || {};
|
||||
if ( dam.parts ) {
|
||||
if (dam.parts) {
|
||||
labels.damage = dam.parts.map(d => d[0]).join(" + ").replace(/\+ -/g, "- ");
|
||||
labels.damageTypes = dam.parts.map(d => C.damageTypes[d[1]]).join(", ");
|
||||
}
|
||||
}
|
||||
|
||||
// if this item is owned, we prepareFinalAttributes() at the end of actor init
|
||||
if (!this.isOwned) this.prepareFinalAttributes();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Compute item attributes which might depend on prepared actor data.
|
||||
*/
|
||||
prepareFinalAttributes() {
|
||||
if ( this.data.data.hasOwnProperty("actionType") ) {
|
||||
// Saving throws
|
||||
this.getSaveDC();
|
||||
|
||||
// To Hit
|
||||
this.getAttackToHit();
|
||||
|
||||
// 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);
|
||||
}
|
||||
this.prepareMaxUses();
|
||||
|
||||
// Damage Label
|
||||
this.getDerivedDamageLabel();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Populate a label with the compiled and simplified damage formula
|
||||
* based on owned item actor data. This is only used for display
|
||||
* purposes and is not related to Item5e#rollDamage
|
||||
*
|
||||
* @returns {Array} array of objects with `formula` and `damageType`
|
||||
*/
|
||||
getDerivedDamageLabel() {
|
||||
const itemData = this.data.data;
|
||||
if ( !this.hasDamage || !itemData || !this.isOwned ) return [];
|
||||
|
||||
const rollData = this.getRollData();
|
||||
|
||||
const derivedDamage = itemData.damage?.parts?.map((damagePart) => ({
|
||||
formula: simplifyRollFormula(damagePart[0], rollData, { constantFirst: false }),
|
||||
damageType: damagePart[1],
|
||||
}));
|
||||
|
||||
this.labels.derivedDamage = derivedDamage
|
||||
|
||||
return derivedDamage;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -409,6 +444,31 @@ export default class Item5e extends Item {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Populates the max uses of an item.
|
||||
* If the item is an owned item and the `max` is not numeric, calculate based on actor data.
|
||||
*/
|
||||
prepareMaxUses() {
|
||||
const data = this.data.data;
|
||||
if (!data.uses?.max) return;
|
||||
let max = data.uses.max;
|
||||
|
||||
// if this is an owned item and the max is not numeric, we need to calculate it
|
||||
if (this.isOwned && !Number.isNumeric(max)) {
|
||||
if (this.actor.data === undefined) return;
|
||||
try {
|
||||
max = Roll.replaceFormulaData(max, this.actor.getRollData(), {missing: 0, warn: true});
|
||||
max = Roll.safeEval(max);
|
||||
} catch(e) {
|
||||
console.error('Problem preparing Max uses for', this.data.name, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data.uses.max = Number(max);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options
|
||||
* @param {boolean} [configureDialog] Display a configuration dialog for the item roll, if applicable?
|
||||
|
@ -419,16 +479,18 @@ export default class Item5e extends Item {
|
|||
*/
|
||||
async roll({configureDialog=true, rollMode, createMessage=true}={}) {
|
||||
let item = this;
|
||||
const id = this.data.data; // Item system data
|
||||
const actor = this.actor;
|
||||
const ad = actor.data.data; // Actor system data
|
||||
|
||||
// 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?
|
||||
// TODO: Possibly Mod this to not consume slots based on class?
|
||||
// We could use this for feats and architypes that let a character cast one slot every rest or so
|
||||
const requirePowerSlot = isPower && (id.level > 0) && CONFIG.SW5E.powerUpcastModes.includes(id.preparation.mode);
|
||||
|
||||
// Define follow-up actions resulting from the item usage
|
||||
|
@ -438,6 +500,8 @@ export default class Item5e extends Item {
|
|||
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
|
||||
let consumePowerLevel = null; // Consume a specific category of power slot
|
||||
if ( requirePowerSlot ) consumePowerLevel = id.preparation.mode === "pact" ? "pact" : `power${id.level}`;
|
||||
|
||||
// Display a configuration dialog to customize the usage
|
||||
const needsConfiguration = createMeasuredTemplate || consumeRecharge || consumeResource || consumePowerSlot || consumeUsage;
|
||||
|
@ -455,28 +519,28 @@ export default class Item5e extends Item {
|
|||
|
||||
// Handle power upcasting
|
||||
if ( requirePowerSlot ) {
|
||||
const slotLevel = configuration.level;
|
||||
const powerLevel = 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
|
||||
consumePowerLevel = `power${configuration.level}`;
|
||||
if (consumePowerSlot === false) consumePowerLevel = null;
|
||||
const upcastLevel = parseInt(configuration.level);
|
||||
if (upcastLevel !== id.level) {
|
||||
item = this.clone({"data.level": upcastLevel}, {keepId: true});
|
||||
item.data.update({_id: this.id}); // Retain the original ID (needed until 0.8.2+)
|
||||
item.prepareFinalAttributes(); // Power save DC, etc...
|
||||
}
|
||||
if ( consumePowerSlot ) consumePowerSlot = `power${powerLevel}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether the item can be used by testing for resource consumption
|
||||
const usage = item._getUsageUpdates({consumeRecharge, consumeResource, consumePowerSlot, consumeUsage, consumeQuantity});
|
||||
const usage = item._getUsageUpdates({consumeRecharge, consumeResource, consumePowerLevel, consumeUsage, consumeQuantity});
|
||||
if ( !usage ) return;
|
||||
|
||||
const {actorUpdates, itemUpdates, resourceUpdates} = usage;
|
||||
|
||||
// Commit pending data updates
|
||||
if ( !isObjectEmpty(itemUpdates) ) await item.update(itemUpdates);
|
||||
if ( !foundry.utils.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) ) {
|
||||
if ( !foundry.utils.isObjectEmpty(actorUpdates) ) await actor.update(actorUpdates);
|
||||
if ( !foundry.utils.isObjectEmpty(resourceUpdates) ) {
|
||||
const resource = actor.items.get(id.consume?.target);
|
||||
if ( resource ) await resource.update(resourceUpdates);
|
||||
}
|
||||
|
@ -499,12 +563,12 @@ export default class Item5e extends Item {
|
|||
* @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 {string|null} consumePowerLevel The category of power slot to consume, or null
|
||||
* @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}) {
|
||||
_getUsageUpdates({consumeQuantity, consumeRecharge, consumeResource, consumePowerLevel, consumeUsage}) {
|
||||
|
||||
// Reference item data
|
||||
const id = this.data.data;
|
||||
|
@ -529,8 +593,9 @@ export default class Item5e extends Item {
|
|||
}
|
||||
|
||||
// Consume Power Slots and Force/Tech Points
|
||||
if ( consumePowerSlot ) {
|
||||
const level = this.actor?.data.data.powers[consumePowerSlot];
|
||||
if ( consumePowerLevel ) {
|
||||
if ( Number.isNumeric(consumePowerLevel) ) consumePowerLevel = `power${consumePowerLevel}`;
|
||||
const level = this.actor?.data.data.powers[consumePowerLevel];
|
||||
const fp = this.actor.data.data.attributes.force.points;
|
||||
const tp = this.actor.data.data.attributes.tech.points;
|
||||
const powerCost = id.level + 1;
|
||||
|
@ -546,7 +611,7 @@ export default class Item5e extends Item {
|
|||
ui.notifications.warn(game.i18n.format("SW5E.PowerCastNoSlots", {name: this.name, level: label}));
|
||||
return false;
|
||||
}
|
||||
actorUpdates[`data.powers.${consumePowerSlot}.fvalue`] = Math.max(powers - 1, 0);
|
||||
actorUpdates[`data.powers.${consumePowerLevel}.fvalue`] = Math.max(powers - 1, 0);
|
||||
if (fp.temp >= powerCost) {
|
||||
actorUpdates["data.attributes.force.points.temp"] = fp.temp - powerCost;
|
||||
}else{
|
||||
|
@ -562,7 +627,7 @@ export default class Item5e extends Item {
|
|||
ui.notifications.warn(game.i18n.format("SW5E.PowerCastNoSlots", {name: this.name, level: label}));
|
||||
return false;
|
||||
}
|
||||
actorUpdates[`data.powers.${consumePowerSlot}.tvalue`] = Math.max(powers - 1, 0);
|
||||
actorUpdates[`data.powers.${consumePowerLevel}.tvalue`] = Math.max(powers - 1, 0);
|
||||
if (tp.temp >= powerCost) {
|
||||
actorUpdates["data.attributes.tech.points.temp"] = tp.temp - powerCost;
|
||||
}else{
|
||||
|
@ -701,11 +766,11 @@ export default class Item5e extends Item {
|
|||
*/
|
||||
async displayCard({rollMode, createMessage=true}={}) {
|
||||
|
||||
// Basic template rendering data
|
||||
// Render the chat card template
|
||||
const token = this.actor.token;
|
||||
const templateData = {
|
||||
actor: this.actor,
|
||||
tokenId: token ? `${token.scene._id}.${token.id}` : null,
|
||||
tokenId: token?.uuid || null,
|
||||
item: this.data,
|
||||
data: this.getChatData(),
|
||||
labels: this.labels,
|
||||
|
@ -715,17 +780,14 @@ export default class Item5e extends Item {
|
|||
isVersatile: this.isVersatile,
|
||||
isPower: this.data.type === "power",
|
||||
hasSave: this.hasSave,
|
||||
hasAreaTarget: this.hasAreaTarget
|
||||
hasAreaTarget: this.hasAreaTarget,
|
||||
isTool: this.data.type === "tool"
|
||||
};
|
||||
|
||||
// Render the chat card template
|
||||
const templateType = ["tool"].includes(this.data.type) ? this.data.type : "item";
|
||||
const template = `systems/sw5e/templates/chat/${templateType}-card.html`;
|
||||
const html = await renderTemplate(template, templateData);
|
||||
const html = await renderTemplate("systems/sw5e/templates/chat/item-card.html", templateData);
|
||||
|
||||
// Create the ChatMessage data object
|
||||
const chatData = {
|
||||
user: game.user._id,
|
||||
user: game.user.data._id,
|
||||
type: CONST.CHAT_MESSAGE_TYPES.OTHER,
|
||||
content: html,
|
||||
flavor: this.data.data.chatFlavor || this.name,
|
||||
|
@ -755,7 +817,7 @@ export default class Item5e extends Item {
|
|||
* @return {Object} An object of chat data to render
|
||||
*/
|
||||
getChatData(htmlOptions={}) {
|
||||
const data = duplicate(this.data.data);
|
||||
const data = foundry.utils.deepClone(this.data.data);
|
||||
const labels = this.labels;
|
||||
|
||||
// Rich text description
|
||||
|
@ -949,12 +1011,11 @@ export default class Item5e extends Item {
|
|||
}
|
||||
|
||||
// Elven Accuracy
|
||||
if ( ["weapon", "power"].includes(this.data.type) ) {
|
||||
if (flags.elvenAccuracy && ["dex", "int", "wis", "cha"].includes(this.abilityMod)) {
|
||||
rollConfig.elvenAccuracy = true;
|
||||
}
|
||||
if ( flags.elvenAccuracy && ["dex", "int", "wis", "cha"].includes(this.abilityMod) ) {
|
||||
rollConfig.elvenAccuracy = true;
|
||||
}
|
||||
|
||||
|
||||
// Apply Halfling Lucky
|
||||
if ( flags.halflingLucky ) rollConfig.halflingLucky = true;
|
||||
|
||||
|
@ -1189,11 +1250,17 @@ export default class Item5e extends Item {
|
|||
const parts = [`@mod`, "@prof"];
|
||||
const title = `${this.name} - ${game.i18n.localize("SW5E.ToolCheck")}`;
|
||||
|
||||
// Add global actor bonus
|
||||
const bonuses = getProperty(this.actor.data.data, "bonuses.abilities") || {};
|
||||
if ( bonuses.check ) {
|
||||
parts.push("@checkBonus");
|
||||
rollData.checkBonus = bonuses.check;
|
||||
}
|
||||
|
||||
// Compose the roll data
|
||||
const rollConfig = mergeObject({
|
||||
parts: parts,
|
||||
data: rollData,
|
||||
template: "systems/sw5e/templates/chat/tool-roll-dialog.html",
|
||||
title: title,
|
||||
speaker: ChatMessage.getSpeaker({actor: this.actor}),
|
||||
flavor: title,
|
||||
|
@ -1202,6 +1269,7 @@ export default class Item5e extends Item {
|
|||
top: options.event ? options.event.clientY - 80 : null,
|
||||
left: window.innerWidth - 710,
|
||||
},
|
||||
chooseModifier: true,
|
||||
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 }}
|
||||
|
@ -1221,7 +1289,7 @@ export default class Item5e extends Item {
|
|||
getRollData() {
|
||||
if ( !this.actor ) return null;
|
||||
const rollData = this.actor.getRollData();
|
||||
rollData.item = duplicate(this.data.data);
|
||||
rollData.item = foundry.utils.deepClone(this.data.data);
|
||||
|
||||
// Include an ability score modifier if one exists
|
||||
const abl = this.abilityMod;
|
||||
|
@ -1269,12 +1337,12 @@ export default class Item5e extends Item {
|
|||
if ( !( isTargetted || game.user.isGM || message.isAuthor ) ) return;
|
||||
|
||||
// Recover the actor for the chat card
|
||||
const actor = this._getChatCardActor(card);
|
||||
const actor = await this._getChatCardActor(card);
|
||||
if ( !actor ) return;
|
||||
|
||||
// Get the Item from stored flag data or by the item ID on the Actor
|
||||
const storedData = message.getFlag("sw5e", "itemData");
|
||||
const item = storedData ? this.createOwned(storedData, actor) : actor.getOwnedItem(card.dataset.itemId);
|
||||
const item = storedData ? new this(storedData, {parent: actor}) : actor.items.get(card.dataset.itemId);
|
||||
if ( !item ) {
|
||||
return ui.notifications.error(game.i18n.format("SW5E.ActionWarningNoItem", {item: card.dataset.itemId, name: actor.name}))
|
||||
}
|
||||
|
@ -1337,17 +1405,12 @@ export default class Item5e extends Item {
|
|||
* @return {Actor|null} The Actor entity or null
|
||||
* @private
|
||||
*/
|
||||
static _getChatCardActor(card) {
|
||||
static async _getChatCardActor(card) {
|
||||
|
||||
// Case 1 - a synthetic actor from a Token
|
||||
const tokenKey = card.dataset.tokenId;
|
||||
if (tokenKey) {
|
||||
const [sceneId, tokenId] = tokenKey.split(".");
|
||||
const scene = game.scenes.get(sceneId);
|
||||
if (!scene) return null;
|
||||
const tokenData = scene.getEmbeddedEntity("Token", tokenId);
|
||||
if (!tokenData) return null;
|
||||
const token = new Token(tokenData);
|
||||
if ( card.dataset.tokenId ) {
|
||||
const token = await fromUuid(card.dataset.tokenId);
|
||||
if ( !token ) return null;
|
||||
return token.actor;
|
||||
}
|
||||
|
||||
|
@ -1361,7 +1424,7 @@ export default class Item5e extends Item {
|
|||
/**
|
||||
* Get the Actor which is the author of a chat card
|
||||
* @param {HTMLElement} card The chat card being used
|
||||
* @return {Array.<Actor>} An Array of Actor entities, if any
|
||||
* @return {Actor[]} An Array of Actor entities, if any
|
||||
* @private
|
||||
*/
|
||||
static _getChatCardTargets(card) {
|
||||
|
@ -1372,14 +1435,174 @@ export default class Item5e extends Item {
|
|||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Factory Methods */
|
||||
/* Event Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
async _preCreate(data, options, user) {
|
||||
await super._preCreate(data, options, user);
|
||||
if ( !this.isEmbedded || (this.parent.type === "vehicle") ) return;
|
||||
const actorData = this.parent.data;
|
||||
const isNPC = this.parent.type === "npc";
|
||||
let updates;
|
||||
switch (data.type) {
|
||||
case "equipment":
|
||||
updates = this._onCreateOwnedEquipment(data, actorData, isNPC);
|
||||
break;
|
||||
case "weapon":
|
||||
updates = this._onCreateOwnedWeapon(data, actorData, isNPC);
|
||||
break;
|
||||
case "power":
|
||||
updates = this._onCreateOwnedPower(data, actorData, isNPC);
|
||||
break;
|
||||
}
|
||||
if (updates) return this.data.update(updates);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
_onCreate(data, options, userId) {
|
||||
super._onCreate(data, options, userId);
|
||||
|
||||
// The below options are only needed for character classes
|
||||
if ( userId !== game.user.id ) return;
|
||||
const isCharacterClass = this.parent && (this.parent.type !== "vehicle") && (this.type === "class");
|
||||
if ( !isCharacterClass ) return;
|
||||
|
||||
// Assign a new primary class
|
||||
const pc = this.parent.items.get(this.parent.data.data.details.originalClass);
|
||||
if ( !pc ) this.parent._assignPrimaryClass();
|
||||
|
||||
// Prompt to add new class features
|
||||
if (options.addFeatures === false) return;
|
||||
this.parent.getClassFeatures({
|
||||
className: this.name,
|
||||
archetypeName: this.data.data.archetype,
|
||||
level: this.data.data.levels
|
||||
}).then(features => {
|
||||
return this.parent.addEmbeddedItems(features, options.promptAddFeatures);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
_onUpdate(changed, options, userId) {
|
||||
super._onUpdate(changed, options, userId);
|
||||
|
||||
// The below options are only needed for character classes
|
||||
if ( userId !== game.user.id ) return;
|
||||
const isCharacterClass = this.parent && (this.parent.type !== "vehicle") && (this.type === "class");
|
||||
if ( !isCharacterClass ) return;
|
||||
|
||||
// Prompt to add new class features
|
||||
const addFeatures = changed["name"] || (changed.data && ["archetype", "levels"].some(k => k in changed.data));
|
||||
if ( !addFeatures || (options.addFeatures === false) ) return;
|
||||
this.parent.getClassFeatures({
|
||||
className: changed.name || this.name,
|
||||
archetypeName: changed.data?.archetype || this.data.data.archetype,
|
||||
level: changed.data?.levels || this.data.data.levels
|
||||
}).then(features => {
|
||||
return this.parent.addEmbeddedItems(features, options.promptAddFeatures);
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritdoc */
|
||||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
// Assign a new primary class
|
||||
if ( this.parent && (this.type === "class") && (userId === game.user.id) ) {
|
||||
if ( this.id !== this.parent.data.data.details.originalClass ) return;
|
||||
this.parent._assignPrimaryClass();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Pre-creation logic for the automatic configuration of owned equipment type Items
|
||||
* @private
|
||||
*/
|
||||
_onCreateOwnedEquipment(data, actorData, isNPC) {
|
||||
const updates = {};
|
||||
if ( foundry.utils.getProperty(data, "data.equipped") === undefined ) {
|
||||
updates["data.equipped"] = isNPC; // NPCs automatically equip equipment
|
||||
}
|
||||
if ( foundry.utils.getProperty(data, "data.proficient") === undefined ) {
|
||||
if ( isNPC ) {
|
||||
updates["data.proficient"] = true; // NPCs automatically have equipment proficiency
|
||||
} else {
|
||||
const armorProf = {
|
||||
"natural": true,
|
||||
"clothing": true,
|
||||
"light": "lgt",
|
||||
"medium": "med",
|
||||
"heavy": "hvy",
|
||||
"shield": "shl"
|
||||
}[data.data?.armor?.type]; // Player characters check proficiency
|
||||
const actorArmorProfs = actorData.data.traits?.armorProf?.value || [];
|
||||
updates["data.proficient"] = (armorProf === true) || actorArmorProfs.includes(armorProf);
|
||||
}
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Pre-creation logic for the automatic configuration of owned power type Items
|
||||
* @private
|
||||
*/
|
||||
_onCreateOwnedPower(data, actorData, isNPC) {
|
||||
const updates = {};
|
||||
updates["data.preparation.prepared"] = true; // Automatically prepare powers for everyone
|
||||
return updates;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Pre-creation logic for the automatic configuration of owned weapon type Items
|
||||
* @private
|
||||
*/
|
||||
_onCreateOwnedWeapon(data, actorData, isNPC) {
|
||||
const updates = {};
|
||||
if ( foundry.utils.getProperty(data, "data.equipped") === undefined ) {
|
||||
updates["data.equipped"] = isNPC; // NPCs automatically equip weapons
|
||||
}
|
||||
if ( foundry.utils.getProperty(data, "data.proficient") === undefined ) {
|
||||
if ( isNPC ) {
|
||||
updates["data.proficient"] = true; // NPCs automatically have equipment proficiency
|
||||
} else {
|
||||
// TODO: With the changes to make weapon proficiencies more verbose, this may need revising
|
||||
const weaponProf = {
|
||||
"natural": true,
|
||||
"simpleVW": "sim",
|
||||
"simpleB": "sim",
|
||||
"simpleLW": "sim",
|
||||
"martialVW": "mar",
|
||||
"martialB": "mar",
|
||||
"martialLW": "mar"
|
||||
}[data.data?.weaponType]; // Player characters check proficiency
|
||||
const actorWeaponProfs = actorData.data.traits?.weaponProf?.value || [];
|
||||
updates["data.proficient"] = (weaponProf === true) || actorWeaponProfs.includes(weaponProf);
|
||||
}
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Factory Methods */
|
||||
/* -------------------------------------------- */
|
||||
// TODO: Make work properly
|
||||
/**
|
||||
* Create a consumable power scroll Item from a power Item.
|
||||
* @param {Item5e} power The power to be made into a scroll
|
||||
* @return {Item5e} The created scroll consumable item
|
||||
* @private
|
||||
*/
|
||||
static async createScrollFromPower(power) {
|
||||
|
||||
|
@ -1388,7 +1611,7 @@ export default class Item5e extends Item {
|
|||
const {actionType, description, source, activation, duration, target, range, damage, save, level} = itemData.data;
|
||||
|
||||
// Get scroll data
|
||||
const scrollUuid = CONFIG.SW5E.powerScrollIds[level];
|
||||
const scrollUuid = `Compendium.${CONFIG.SW5E.sourcePacks.ITEMS}.${CONFIG.SW5E.powerScrollIds[level]}`;
|
||||
const scrollItem = await fromUuid(scrollUuid);
|
||||
const scrollData = scrollItem.data;
|
||||
delete scrollData._id;
|
||||
|
|
|
@ -18,9 +18,9 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||
width: 560,
|
||||
height: 400,
|
||||
classes: ["sw5e", "sheet", "item"],
|
||||
|
@ -32,7 +32,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
get template() {
|
||||
const path = "systems/sw5e/templates/items/";
|
||||
return `${path}/${this.item.data.type}.html`;
|
||||
|
@ -43,33 +43,39 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/** @override */
|
||||
async getData(options) {
|
||||
const data = super.getData(options);
|
||||
const itemData = data.data;
|
||||
data.labels = this.item.labels;
|
||||
data.config = CONFIG.SW5E;
|
||||
|
||||
// Item Type, Status, and Details
|
||||
data.itemType = game.i18n.localize(`ITEM.Type${data.item.type.titleCase()}`);
|
||||
data.itemStatus = this._getItemStatus(data.item);
|
||||
data.itemProperties = this._getItemProperties(data.item);
|
||||
data.isPhysical = data.item.data.hasOwnProperty("quantity");
|
||||
data.itemStatus = this._getItemStatus(itemData);
|
||||
data.itemProperties = this._getItemProperties(itemData);
|
||||
data.isPhysical = itemData.data.hasOwnProperty("quantity");
|
||||
|
||||
// Potential consumption targets
|
||||
data.abilityConsumptionTargets = this._getItemConsumptionTargets(data.item);
|
||||
data.abilityConsumptionTargets = this._getItemConsumptionTargets(itemData);
|
||||
|
||||
// Action Detail
|
||||
data.hasAttackRoll = this.item.hasAttack;
|
||||
data.isHealing = data.item.data.actionType === "heal";
|
||||
data.isFlatDC = getProperty(data.item.data, "save.scaling") === "flat";
|
||||
data.isLine = ["line", "wall"].includes(data.item.data.target?.type);
|
||||
data.isHealing = itemData.data.actionType === "heal";
|
||||
data.isFlatDC = getProperty(itemData, "data.save.scaling") === "flat";
|
||||
data.isLine = ["line", "wall"].includes(itemData.data.target?.type);
|
||||
|
||||
// Original maximum uses formula
|
||||
if (this.item._data.data?.uses?.max) data.data.uses.max = this.item._data.data.uses.max;
|
||||
const sourceMax = foundry.utils.getProperty(this.item.data._source, "data.uses.max");
|
||||
if ( sourceMax ) itemData.data.uses.max = sourceMax;
|
||||
|
||||
// Vehicles
|
||||
data.isCrewed = data.item.data.activation?.type === "crew";
|
||||
data.isMountable = this._isItemMountable(data.item);
|
||||
data.isCrewed = itemData.data.activation?.type === "crew";
|
||||
data.isMountable = this._isItemMountable(itemData);
|
||||
|
||||
// Prepare Active Effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
data.effects = prepareActiveEffectCategories(this.item.effects);
|
||||
|
||||
// Re-define the template data references (backwards compatible)
|
||||
data.item = itemData;
|
||||
data.data = itemData.data;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -102,9 +108,11 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
|
||||
// Attributes
|
||||
else if (consume.type === "attribute") {
|
||||
const attributes = Object.values(CombatTrackerConfig.prototype.getAttributeChoices())[0]; // Bit of a hack
|
||||
return attributes.reduce((obj, a) => {
|
||||
obj[a] = a;
|
||||
const attributes = TokenDocument.getTrackedAttributes(actor.data.data);
|
||||
attributes.bar.forEach(a => a.push("value"));
|
||||
return attributes.bar.concat(attributes.value).reduce((obj, a) => {
|
||||
let k = a.join(".");
|
||||
obj[k] = k;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
@ -186,6 +194,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
props.push(labels.armor);
|
||||
} else if (item.type === "feat") {
|
||||
props.push(labels.featType);
|
||||
//TODO: Work out these
|
||||
} else if (item.type === "species") {
|
||||
//props.push(labels.species);
|
||||
} else if (item.type === "archetype") {
|
||||
|
@ -238,7 +247,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
setPosition(position = {}) {
|
||||
if (!(this._minimized || position.height)) {
|
||||
position.height = this._tabs[0].active === "details" ? "auto" : this.options.height;
|
||||
|
@ -250,7 +259,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/* Form Submission */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
_getSubmitData(updateData = {}) {
|
||||
// Create the expanded update data object
|
||||
const fd = new FormDataExtended(this.form, { editors: this.editors });
|
||||
|
@ -268,17 +277,14 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if (this.isEditable) {
|
||||
html.find(".damage-control").click(this._onDamageControl.bind(this));
|
||||
html.find(".trait-selector.class-skills").click(this._onConfigureClassSkills.bind(this));
|
||||
html.find(".trait-selector.class-skills").click(this._onConfigureTraits.bind(this));
|
||||
html.find(".effect-control").click((ev) => {
|
||||
if (this.item.isOwned)
|
||||
return ui.notifications.warn(
|
||||
"Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update."
|
||||
);
|
||||
if (this.item.isOwned) return ui.notifications.warn("Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update.");
|
||||
onManageActiveEffect(ev, this.item);
|
||||
});
|
||||
}
|
||||
|
@ -307,7 +313,7 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
if (a.classList.contains("delete-damage")) {
|
||||
await this._onSubmit(event); // Submit any unsaved changes
|
||||
const li = a.closest(".damage-part");
|
||||
const damage = duplicate(this.item.data.data.damage);
|
||||
const damage = foundry.utils.deepClone(this.item.data.data.damage);
|
||||
damage.parts.splice(Number(li.dataset.damagePart), 1);
|
||||
return this.item.update({ "data.damage.parts": damage.parts });
|
||||
}
|
||||
|
@ -316,33 +322,39 @@ export default class ItemSheet5e extends ItemSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* Handle spawning the TraitSelector application for selection various options.
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onConfigureClassSkills(event) {
|
||||
_onConfigureTraits(event) {
|
||||
event.preventDefault();
|
||||
const skills = this.item.data.data.skills;
|
||||
const choices = skills.choices && skills.choices.length ? skills.choices : Object.keys(CONFIG.SW5E.skills);
|
||||
const a = event.currentTarget;
|
||||
const label = a.parentElement;
|
||||
|
||||
// Render the Trait Selector dialog
|
||||
new TraitSelector(this.item, {
|
||||
const options = {
|
||||
name: a.dataset.target,
|
||||
title: label.innerText,
|
||||
choices: Object.entries(CONFIG.SW5E.skills).reduce((obj, e) => {
|
||||
if (choices.includes(e[0])) obj[e[0]] = e[1];
|
||||
return obj;
|
||||
}, {}),
|
||||
minimum: skills.number,
|
||||
maximum: skills.number
|
||||
}).render(true);
|
||||
title: a.parentElement.innerText,
|
||||
choices: [],
|
||||
allowCustom: false
|
||||
};
|
||||
|
||||
switch(a.dataset.options) {
|
||||
case 'saves':
|
||||
options.choices = CONFIG.SW5E.abilities;
|
||||
options.valueKey = null;
|
||||
break;
|
||||
case 'skills':
|
||||
const skills = this.item.data.data.skills;
|
||||
const choiceSet = skills.choices && skills.choices.length ? skills.choices : Object.keys(CONFIG.SW5E.skills);
|
||||
options.choices = Object.fromEntries(Object.entries(CONFIG.SW5E.skills).filter(skill => choiceSet.includes(skill[0])));
|
||||
options.maximum = skills.number;
|
||||
break;
|
||||
}
|
||||
new TraitSelector(this.item, options).render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
/** @inheritdoc */
|
||||
async _onSubmit(...args) {
|
||||
if (this._tabs[0].active === "details") this.position.height = "auto";
|
||||
await super._onSubmit(...args);
|
||||
|
|
|
@ -6,11 +6,11 @@ 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});
|
||||
|
||||
// Migrate World Actors
|
||||
for await ( let a of game.actors.entities ) {
|
||||
for await ( let a of game.actors.contents ) {
|
||||
try {
|
||||
console.log(`Checking Actor entity ${a.name} for migration needs`);
|
||||
const updateData = await migrateActorData(a.data);
|
||||
if ( !isObjectEmpty(updateData) ) {
|
||||
if ( !foundry.utils.isObjectEmpty(updateData) ) {
|
||||
console.log(`Migrating Actor entity ${a.name}`);
|
||||
await a.update(updateData, {enforceTypes: false});
|
||||
}
|
||||
|
@ -21,10 +21,10 @@ export const migrateWorld = async function() {
|
|||
}
|
||||
|
||||
// Migrate World Items
|
||||
for ( let i of game.items.entities ) {
|
||||
for ( let i of game.items.contents ) {
|
||||
try {
|
||||
const updateData = migrateItemData(i.data);
|
||||
if ( !isObjectEmpty(updateData) ) {
|
||||
const updateData = migrateItemData(i.toObject());
|
||||
if ( !foundry.utils.isObjectEmpty(updateData) ) {
|
||||
console.log(`Migrating Item entity ${i.name}`);
|
||||
await i.update(updateData, {enforceTypes: false});
|
||||
}
|
||||
|
@ -35,12 +35,15 @@ export const migrateWorld = async function() {
|
|||
}
|
||||
|
||||
// Migrate Actor Override Tokens
|
||||
for ( let s of game.scenes.entities ) {
|
||||
for ( let s of game.scenes.contents ) {
|
||||
try {
|
||||
const updateData = await migrateSceneData(s.data);
|
||||
if ( !isObjectEmpty(updateData) ) {
|
||||
if ( !foundry.utils.isObjectEmpty(updateData) ) {
|
||||
console.log(`Migrating Scene entity ${s.name}`);
|
||||
await s.update(updateData, {enforceTypes: false});
|
||||
// If we do not do this, then synthetic token actors remain in cache
|
||||
// with the un-updated actorData.
|
||||
s.tokens.contents.forEach(t => t._actor = null);
|
||||
}
|
||||
} catch(err) {
|
||||
err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`;
|
||||
|
@ -77,40 +80,39 @@ export const migrateCompendium = async function(pack) {
|
|||
|
||||
// Begin by requesting server-side data model migration and get the migrated content
|
||||
await pack.migrate();
|
||||
const content = await pack.getContent();
|
||||
const documents = await pack.getDocuments();
|
||||
|
||||
// Iterate over compendium entries - applying fine-tuned migration functions
|
||||
for await ( let ent of content ) {
|
||||
for await ( let doc of documents ) {
|
||||
let updateData = {};
|
||||
try {
|
||||
switch (entity) {
|
||||
case "Actor":
|
||||
updateData = await migrateActorData(ent.data);
|
||||
updateData = await migrateActorData(doc.data);
|
||||
break;
|
||||
case "Item":
|
||||
updateData = migrateItemData(ent.data);
|
||||
updateData = migrateItemData(doc.toObject());
|
||||
break;
|
||||
case "Scene":
|
||||
updateData = await migrateSceneData(ent.data);
|
||||
updateData = await migrateSceneData(doc.data);
|
||||
break;
|
||||
}
|
||||
if ( isObjectEmpty(updateData) ) continue;
|
||||
if ( foundry.utils.isObjectEmpty(updateData) ) continue;
|
||||
|
||||
// Save the entry, if data was changed
|
||||
updateData["_id"] = ent._id;
|
||||
await pack.updateEntity(updateData);
|
||||
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
|
||||
await doc.update(updateData);
|
||||
console.log(`Migrated ${entity} entity ${doc.name} in Compendium ${pack.collection}`);
|
||||
}
|
||||
|
||||
// Handle migration failures
|
||||
catch(err) {
|
||||
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`;
|
||||
err.message = `Failed sw5e system migration for entity ${doc.name} in pack ${pack.collection}: ${err.message}`;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the original locked status for the pack
|
||||
pack.configure({locked: wasLocked});
|
||||
await pack.configure({locked: wasLocked});
|
||||
console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
|
||||
};
|
||||
|
||||
|
@ -128,34 +130,39 @@ export const migrateActorData = async function(actor) {
|
|||
const updateData = {};
|
||||
|
||||
// Actor Data Updates
|
||||
_migrateActorMovement(actor, updateData);
|
||||
_migrateActorSenses(actor, updateData);
|
||||
if(actor.data) {
|
||||
_migrateActorMovement(actor, updateData);
|
||||
_migrateActorSenses(actor, updateData);
|
||||
_migrateActorType(actor, updateData);
|
||||
}
|
||||
|
||||
// Migrate Owned Items
|
||||
if ( !!actor.items ) {
|
||||
let hasItemUpdates = false;
|
||||
const items = await actor.items.reduce(async (memo, i) => {
|
||||
const results = await memo;
|
||||
|
||||
// Migrate the Owned Item
|
||||
let itemUpdate = await migrateActorItemData(i, actor);
|
||||
const itemData = i instanceof CONFIG.Item.documentClass ? i.toObject() : i
|
||||
let itemUpdate = await migrateActorItemData(itemData, actor);
|
||||
|
||||
// Prepared, Equipped, and Proficient for NPC actors
|
||||
if ( actor.type === "npc" ) {
|
||||
if (getProperty(i.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
|
||||
if (getProperty(i.data, "equipped") === false) itemUpdate["data.equipped"] = true;
|
||||
if (getProperty(i.data, "proficient") === false) itemUpdate["data.proficient"] = true;
|
||||
if (getProperty(itemData.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
|
||||
if (getProperty(itemData.data, "equipped") === false) itemUpdate["data.equipped"] = true;
|
||||
if (getProperty(itemData.data, "proficient") === false) itemUpdate["data.proficient"] = true;
|
||||
}
|
||||
|
||||
// Update the Owned Item
|
||||
if ( !isObjectEmpty(itemUpdate) ) {
|
||||
hasItemUpdates = true;
|
||||
itemUpdate._id = itemData.id;
|
||||
console.log(`Migrating Actor ${actor.name}'s ${i.name}`);
|
||||
return [...results, mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false})];
|
||||
} else return [...results, i];
|
||||
results.push(expandObject(itemUpdate));
|
||||
}
|
||||
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
if ( hasItemUpdates ) updateData.items = items;
|
||||
if ( items.length > 0 ) updateData.items = items;
|
||||
}
|
||||
|
||||
// Update NPC data with new datamodel information
|
||||
|
@ -234,24 +241,33 @@ export const migrateActorItemData = async function(item, actor) {
|
|||
* @return {Object} The updateData to apply
|
||||
*/
|
||||
export const migrateSceneData = async function(scene) {
|
||||
const tokens = duplicate(scene.tokens);
|
||||
return {
|
||||
tokens: await Promise.all(tokens.map(async (t) => {
|
||||
if (!t.actorId || t.actorLink || !t.actorData.data) {
|
||||
const tokens = await Promise.all(scene.tokens.map(async token => {
|
||||
const t = token.toJSON();
|
||||
if (!t.actorId || t.actorLink) {
|
||||
t.actorData = {};
|
||||
return t;
|
||||
}
|
||||
const token = new Token(t);
|
||||
if ( !token.actor ) {
|
||||
else if (!game.actors.has(t.actorId)) {
|
||||
t.actorId = null;
|
||||
t.actorData = {};
|
||||
} else if ( !t.actorLink ) {
|
||||
const updateData = await migrateActorData(token.data.actorData);
|
||||
t.actorData = mergeObject(token.data.actorData, updateData);
|
||||
const actorData = duplicate(t.actorData);
|
||||
actorData.type = token.actor?.type;
|
||||
const update = migrateActorData(actorData);
|
||||
['items', 'effects'].forEach(embeddedName => {
|
||||
if (!update[embeddedName]?.length) return;
|
||||
const updates = new Map(update[embeddedName].map(u => [u._id, u]));
|
||||
t.actorData[embeddedName].forEach(original => {
|
||||
const update = updates.get(original._id);
|
||||
if (update) mergeObject(original, update);
|
||||
});
|
||||
delete update[embeddedName];
|
||||
});
|
||||
|
||||
mergeObject(t.actorData, update);
|
||||
}
|
||||
return t;
|
||||
}))
|
||||
};
|
||||
}));
|
||||
return {tokens};
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -420,6 +436,7 @@ function _migrateActorSenses(actor, updateData) {
|
|||
const ad = actor.data;
|
||||
if ( ad?.traits?.senses === undefined ) return;
|
||||
const original = ad.traits.senses || "";
|
||||
if ( typeof original !== "string" ) return;
|
||||
|
||||
// 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]+)?/;
|
||||
|
@ -447,6 +464,83 @@ function _migrateActorSenses(actor, updateData) {
|
|||
return updateData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the actor details.type string to object
|
||||
* @private
|
||||
*/
|
||||
function _migrateActorType(actor, updateData) {
|
||||
const ad = actor.data;
|
||||
const original = ad.details?.type;
|
||||
if ( typeof original !== "string" ) return;
|
||||
|
||||
// New default data structure
|
||||
let data = {
|
||||
"value": "",
|
||||
"subtype": "",
|
||||
"swarm": "",
|
||||
"custom": ""
|
||||
}
|
||||
|
||||
// Specifics
|
||||
// (Some of these have weird names, these need to be addressed individually)
|
||||
if (original === "force entity") {
|
||||
data.value = "force";
|
||||
data.subtype = "storm";
|
||||
} else if (original === "human") {
|
||||
data.value = "humanoid";
|
||||
data.subtype = "human";
|
||||
} else if (["humanoid (any)", "humanoid (Villainous"].includes(original)) {
|
||||
data.value = "humanoid";
|
||||
} else if (original === "tree") {
|
||||
data.value = "plant";
|
||||
data.subtype = "tree";
|
||||
} else if (original === "(humanoid) or Large (beast) force entity") {
|
||||
data.value = "force";
|
||||
} else if (original === "droid (appears human)") {
|
||||
data.value = "droid";
|
||||
} else {
|
||||
// Match the existing string
|
||||
const pattern = /^(?:swarm of (?<size>[\w\-]+) )?(?<type>[^(]+?)(?:\((?<subtype>[^)]+)\))?$/i;
|
||||
const match = original.trim().match(pattern);
|
||||
if (match) {
|
||||
|
||||
// Match a known creature type
|
||||
const typeLc = match.groups.type.trim().toLowerCase();
|
||||
const typeMatch = Object.entries(CONFIG.SW5E.creatureTypes).find(([k, v]) => {
|
||||
return (typeLc === k) ||
|
||||
(typeLc === game.i18n.localize(v).toLowerCase()) ||
|
||||
(typeLc === game.i18n.localize(`${v}Pl`).toLowerCase());
|
||||
});
|
||||
if (typeMatch) data.value = typeMatch[0];
|
||||
else {
|
||||
data.value = "custom";
|
||||
data.custom = match.groups.type.trim().titleCase();
|
||||
}
|
||||
data.subtype = match.groups.subtype?.trim().titleCase() || "";
|
||||
|
||||
// Match a swarm
|
||||
const isNamedSwarm = actor.name.startsWith(game.i18n.localize("SW5E.CreatureSwarm"));
|
||||
if (match.groups.size || isNamedSwarm) {
|
||||
const sizeLc = match.groups.size ? match.groups.size.trim().toLowerCase() : "tiny";
|
||||
const sizeMatch = Object.entries(CONFIG.SW5E.actorSizes).find(([k, v]) => {
|
||||
return (sizeLc === k) || (sizeLc === game.i18n.localize(v).toLowerCase());
|
||||
});
|
||||
data.swarm = sizeMatch ? sizeMatch[0] : "tiny";
|
||||
} else data.swarm = "";
|
||||
}
|
||||
|
||||
// No match found
|
||||
else {
|
||||
data.value = "custom";
|
||||
data.custom = original;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the actor data
|
||||
updateData["data.details.type"] = data;
|
||||
return updateData;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -456,19 +550,35 @@ function _migrateItemClassPowerCasting(item, updateData) {
|
|||
if (item.type === "class"){
|
||||
switch (item.name){
|
||||
case "Consular":
|
||||
updateData["data.powercasting"] = "consular";
|
||||
updateData["data.powercasting"] = {
|
||||
progression: "consular",
|
||||
ability: ""
|
||||
};
|
||||
break;
|
||||
case "Engineer":
|
||||
updateData["data.powercasting"] = "engineer";
|
||||
|
||||
updateData["data.powercasting"] = {
|
||||
progression: "engineer",
|
||||
ability: ""
|
||||
};
|
||||
break;
|
||||
case "Guardian":
|
||||
updateData["data.powercasting"] = "guardian";
|
||||
updateData["data.powercasting"] = {
|
||||
progression: "guardian",
|
||||
ability: ""
|
||||
};
|
||||
break;
|
||||
case "Scout":
|
||||
updateData["data.powercasting"] = "scout";
|
||||
updateData["data.powercasting"] = {
|
||||
progression: "scout",
|
||||
ability: ""
|
||||
};
|
||||
break;
|
||||
case "Sentinel":
|
||||
updateData["data.powercasting"] = "sentinel";
|
||||
updateData["data.powercasting"] = {
|
||||
progression: "sentinel",
|
||||
ability: ""
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -526,10 +636,14 @@ async function _migrateItemPower(item, actor, updateData) {
|
|||
|
||||
/**
|
||||
* Delete the old data.attuned boolean
|
||||
*
|
||||
* @param {object} item Item data to migrate
|
||||
* @param {object} updateData Existing update to expand upon
|
||||
* @return {object} The updateData to apply
|
||||
* @private
|
||||
*/
|
||||
function _migrateItemAttunement(item, updateData) {
|
||||
if ( item.data.attuned === undefined ) return;
|
||||
if ( item.data.data.attuned === undefined ) return updateData;
|
||||
updateData["data.attunement"] = CONFIG.SW5E.attunementTypes.NONE;
|
||||
updateData["data.-=attuned"] = null;
|
||||
return updateData;
|
||||
|
|
|
@ -19,7 +19,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
// Prepare template data
|
||||
const templateData = {
|
||||
t: templateShape,
|
||||
user: game.user._id,
|
||||
user: game.user.data._id,
|
||||
distance: target.value,
|
||||
direction: 0,
|
||||
x: 0,
|
||||
|
@ -45,10 +45,12 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
}
|
||||
|
||||
// Return the template constructed from the item data
|
||||
const template = new this(templateData);
|
||||
template.item = item;
|
||||
template.actorSheet = item.actor?.sheet || null;
|
||||
return template;
|
||||
const cls = CONFIG.MeasuredTemplate.documentClass;
|
||||
const template = new cls(templateData, {parent: canvas.scene});
|
||||
const object = new this(template);
|
||||
object.item = item;
|
||||
object.actorSheet = item.actor?.sheet || null;
|
||||
return object;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -108,14 +110,9 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
// Confirm the workflow (left-click)
|
||||
handlers.lc = event => {
|
||||
handlers.rc(event);
|
||||
|
||||
// Confirm final snapped position
|
||||
const destination = canvas.grid.getSnappedPosition(this.x, this.y, 2);
|
||||
this.data.x = destination.x;
|
||||
this.data.y = destination.y;
|
||||
|
||||
// Create the template
|
||||
canvas.scene.createEmbeddedEntity("MeasuredTemplate", this.data);
|
||||
const destination = canvas.grid.getSnappedPosition(this.data.x, this.data.y, 2);
|
||||
this.data.update(destination);
|
||||
canvas.scene.createEmbeddedDocuments("MeasuredTemplate", [this.data]);
|
||||
};
|
||||
|
||||
// Rotate the template by 3 degree increments (mouse-wheel)
|
||||
|
@ -124,7 +121,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
event.stopPropagation();
|
||||
let delta = canvas.grid.type > CONST.GRID_TYPES.SQUARE ? 30 : 15;
|
||||
let snap = event.shiftKey ? delta : 5;
|
||||
this.data.direction += (snap * Math.sign(event.deltaY));
|
||||
this.data.update({direction: this.data.direction + (snap * Math.sign(event.deltaY))});
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ export const registerSystemSettings = function() {
|
|||
scope: "world",
|
||||
config: false,
|
||||
type: String,
|
||||
default: ""
|
||||
default: game.system.data.version
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
99
module/token.js
Normal file
99
module/token.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Extend the base TokenDocument class to implement system-specific HP bar logic.
|
||||
* @extends {TokenDocument}
|
||||
*/
|
||||
export class TokenDocument5e extends TokenDocument {
|
||||
|
||||
/** @inheritdoc */
|
||||
getBarAttribute(...args) {
|
||||
const data = super.getBarAttribute(...args);
|
||||
if ( data && (data.attribute === "attributes.hp") ) {
|
||||
data.value += parseInt(getProperty(this.actor.data, "data.attributes.hp.temp") || 0);
|
||||
data.max += parseInt(getProperty(this.actor.data, "data.attributes.hp.tempmax") || 0);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Extend the base Token class to implement additional system-specific logic.
|
||||
* @extends {Token}
|
||||
*/
|
||||
export class Token5e extends Token {
|
||||
|
||||
/** @inheritdoc */
|
||||
_drawBar(number, bar, data) {
|
||||
if ( data.attribute === "attributes.hp" ) return this._drawHPBar(number, bar, data);
|
||||
return super._drawBar(number, bar, data);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Specialized drawing function for HP bars.
|
||||
* @param {number} number The Bar number
|
||||
* @param {PIXI.Graphics} bar The Bar container
|
||||
* @param {object} data Resource data for this bar
|
||||
* @private
|
||||
*/
|
||||
_drawHPBar(number, bar, data) {
|
||||
|
||||
// Extract health data
|
||||
let {value, max, temp, tempmax} = this.document.actor.data.data.attributes.hp;
|
||||
temp = Number(temp || 0);
|
||||
tempmax = Number(tempmax || 0);
|
||||
|
||||
// Differentiate between effective maximum and displayed maximum
|
||||
const effectiveMax = Math.max(0, max + tempmax);
|
||||
let displayMax = max + (tempmax > 0 ? tempmax : 0);
|
||||
|
||||
// Allocate percentages of the total
|
||||
const tempPct = Math.clamped(temp, 0, displayMax) / displayMax;
|
||||
const valuePct = Math.clamped(value, 0, effectiveMax) / displayMax;
|
||||
const colorPct = Math.clamped(value, 0, effectiveMax) / displayMax;
|
||||
|
||||
// Determine colors to use
|
||||
const blk = 0x000000;
|
||||
const hpColor = PIXI.utils.rgb2hex([(1-(colorPct/2)), colorPct, 0]);
|
||||
const c = CONFIG.SW5E.tokenHPColors;
|
||||
|
||||
// Determine the container size (logic borrowed from core)
|
||||
const w = this.w;
|
||||
let h = Math.max((canvas.dimensions.size / 12), 8);
|
||||
if ( this.data.height >= 2 ) h *= 1.6;
|
||||
const bs = Math.clamped(h / 8, 1, 2);
|
||||
const bs1 = bs+1;
|
||||
|
||||
// Overall bar container
|
||||
bar.clear()
|
||||
bar.beginFill(blk, 0.5).lineStyle(bs, blk, 1.0).drawRoundedRect(0, 0, w, h, 3);
|
||||
|
||||
// Temporary maximum HP
|
||||
if (tempmax > 0) {
|
||||
const pct = max / effectiveMax;
|
||||
bar.beginFill(c.tempmax, 1.0).lineStyle(1, blk, 1.0).drawRoundedRect(pct*w, 0, (1-pct)*w, h, 2);
|
||||
}
|
||||
|
||||
// Maximum HP penalty
|
||||
else if (tempmax < 0) {
|
||||
const pct = (max + tempmax) / max;
|
||||
bar.beginFill(c.negmax, 1.0).lineStyle(1, blk, 1.0).drawRoundedRect(pct*w, 0, (1-pct)*w, h, 2);
|
||||
}
|
||||
|
||||
// Health bar
|
||||
bar.beginFill(hpColor, 1.0).lineStyle(bs, blk, 1.0).drawRoundedRect(0, 0, valuePct*w, h, 2)
|
||||
|
||||
// Temporary hit points
|
||||
if ( temp > 0 ) {
|
||||
bar.beginFill(c.temp, 1.0).lineStyle(0).drawRoundedRect(bs1, bs1, (tempPct*w)-(2*bs1), h-(2*bs1), 1);
|
||||
}
|
||||
|
||||
// Set position
|
||||
let posY = (number === 0) ? (this.h - h) : 0;
|
||||
bar.position.set(0, posY);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -102,7 +102,7 @@ body {
|
|||
font-size: 13px;
|
||||
color: #191813;
|
||||
}
|
||||
.sw5e input[type="text"]
|
||||
.sw5e input[type="text"],
|
||||
.sw5e select {
|
||||
height: calc(100% - 2px);
|
||||
border: 1px solid #7a7971;
|
||||
|
|
|
@ -791,6 +791,9 @@ body.dark-theme .sw5e.sheet.actor .swalt-sheet .tab.notes section > input {
|
|||
color: #E81111;
|
||||
border-bottom: 2px solid #0d99cc;
|
||||
}
|
||||
body.dark-theme .sw5e.sheet.actor.npc .swalt-sheet header div.creature-type:hover {
|
||||
border-color: #E81111;
|
||||
}
|
||||
body.dark-theme .sw5e.sheet.actor.npc .swalt-sheet header .experience {
|
||||
color: #4f4f4f;
|
||||
}
|
||||
|
|
|
@ -206,7 +206,8 @@ input[type="password"],
|
|||
input[type="date"],
|
||||
input[type="time"],
|
||||
select,
|
||||
textarea {
|
||||
textarea,
|
||||
.roundTransition {
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
@ -216,7 +217,8 @@ input[type="password"]:hover,
|
|||
input[type="date"]:hover,
|
||||
input[type="time"]:hover,
|
||||
select:hover,
|
||||
textarea:hover {
|
||||
textarea:hover,
|
||||
.roundTransition:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type="text"]:focus,
|
||||
|
@ -225,7 +227,8 @@ input[type="password"]:focus,
|
|||
input[type="date"]:focus,
|
||||
input[type="time"]:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
textarea:focus,
|
||||
.roundTransition:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
input[type=range] {
|
||||
|
@ -552,7 +555,7 @@ input[type="reset"]:disabled {
|
|||
padding-bottom: 4px;
|
||||
}
|
||||
.sidebar-tab .directory-list .folder > .folder-header {
|
||||
line-height: default;
|
||||
line-height: initial;
|
||||
padding: 0 0 0 8px;
|
||||
position: relative;
|
||||
border: none;
|
||||
|
@ -858,6 +861,7 @@ input[type="reset"]:disabled {
|
|||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
.sw5e.sheet.actor .swalt-sheet header .summary .proficiency {
|
||||
line-height: 26px;
|
||||
|
@ -895,7 +899,7 @@ input[type="reset"]:disabled {
|
|||
.sw5e.sheet.actor .swalt-sheet header .attributes .attribute-value .value-number {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
padding: 0px 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.sw5e.sheet.actor .swalt-sheet header .attributes .attribute-value .value-number:last-child {
|
||||
text-align: left;
|
||||
|
@ -1435,7 +1439,7 @@ input[type="reset"]:disabled {
|
|||
display: block;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding: 0px 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.sw5e.sheet.actor .swalt-sheet .tab.attributes .traits-resources section.resources .resource-items .resource .attribute-value .value-number:last-child {
|
||||
text-align: left;
|
||||
|
@ -1588,7 +1592,7 @@ input[type="reset"]:disabled {
|
|||
display: block;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
padding: 0px 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.sw5e.sheet.actor .swalt-sheet .tab.force-powerbook .resource-items .resource .attribute-value .value-number:last-child,
|
||||
.sw5e.sheet.actor .swalt-sheet .tab.tech-powerbook .resource-items .resource .attribute-value .value-number:last-child {
|
||||
|
@ -1686,13 +1690,35 @@ input[type="reset"]:disabled {
|
|||
.sw5e.sheet.actor.npc .swalt-sheet header h1.character-name {
|
||||
align-self: auto;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header .npc-size {
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header .npc-size,
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header .creature-type {
|
||||
font-family: 'Russo One';
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header div.creature-type {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 1px 4px;
|
||||
border: 1px solid transparent;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header div.creature-type span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header div.creature-type .config-button {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 2em;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header div.creature-type:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .swalt-sheet header .attributes {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
|
|
@ -778,6 +778,9 @@ body.light-theme .sw5e.sheet.actor .swalt-sheet .tab.notes section > input {
|
|||
color: #c40f0f;
|
||||
border-bottom: 2px solid #0d99cc;
|
||||
}
|
||||
body.light-theme .sw5e.sheet.actor.npc .swalt-sheet header div.creature-type:hover {
|
||||
border-color: #c40f0f;
|
||||
}
|
||||
body.light-theme .sw5e.sheet.actor.npc .swalt-sheet header .experience {
|
||||
color: #4f4f4f;
|
||||
}
|
||||
|
|
163
sw5e.css
163
sw5e.css
|
@ -91,9 +91,7 @@
|
|||
/* Tags */
|
||||
}
|
||||
.sw5e .window-content {
|
||||
background: linear-gradient(90deg, #afc6d6 0%, #D6D6D6 30%, #D6D6D6 70%, #afc6d6);
|
||||
font-size: 13px;
|
||||
color: #191813;
|
||||
}
|
||||
.sw5e input[type="text"],
|
||||
.sw5e input[type="number"],
|
||||
|
@ -114,6 +112,8 @@
|
|||
.sw5e select:disabled,
|
||||
.sw5e textarea:disabled {
|
||||
color: #4b4a44;
|
||||
border: 1px solid transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
.sw5e input:disabled:hover,
|
||||
.sw5e select:disabled:hover,
|
||||
|
@ -129,24 +129,6 @@
|
|||
background: rgba(0, 0, 0, 0.1);
|
||||
border: 2px groove #eeede0;
|
||||
}
|
||||
.sw5e label.checkbox {
|
||||
flex: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.sw5e label.checkbox > input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 2px 0 0;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
.sw5e label.checkbox.right > input[type="checkbox"] {
|
||||
margin: 0 0 0 2px;
|
||||
}
|
||||
.sw5e .form-group label {
|
||||
flex: 2;
|
||||
color: #4b4a44;
|
||||
|
@ -179,11 +161,12 @@
|
|||
.sw5e .form-group .form-fields > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.sw5e .form-group.stacked label {
|
||||
.sw5e .form-group.stacked > label {
|
||||
flex: 0 0 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.sw5e .form-group.stacked label.checkbox {
|
||||
.sw5e .form-group.stacked label.checkbox,
|
||||
.sw5e .form-group.stacked label.radio {
|
||||
flex: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -207,6 +190,26 @@
|
|||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
/* ----------------------------------------- */
|
||||
/* Hit Dice Config Sheet Specifically */
|
||||
/* ----------------------------------------- */
|
||||
.sw5e.hd-config .form-group button.increment,
|
||||
.sw5e.hd-config .form-group button.decrement {
|
||||
flex: 0 0 1rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
.sw5e.hd-config .form-group button.decrement {
|
||||
margin-right: 0;
|
||||
}
|
||||
.sw5e.hd-config .form-group span.sep {
|
||||
margin: 0;
|
||||
}
|
||||
.sw5e.hd-config .form-group input {
|
||||
flex: 0 0 2rem;
|
||||
text-align: center;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
/* ----------------------------------------- */
|
||||
/* Entity Sheets Specifically */
|
||||
/* ----------------------------------------- */
|
||||
.sw5e.sheet {
|
||||
|
@ -499,16 +502,60 @@
|
|||
/* ----------------------------------------- */
|
||||
/* Trait Selector
|
||||
/* ----------------------------------------- */
|
||||
#trait-selector .trait-list {
|
||||
.trait-selector .trait-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#trait-selector input[type="text"] {
|
||||
.trait-selector input[type="text"] {
|
||||
height: 24px;
|
||||
margin: 2px;
|
||||
}
|
||||
/* ----------------------------------------- */
|
||||
/* Actor Type Config Sheet Specifically */
|
||||
/* ----------------------------------------- */
|
||||
.actor-type .trait-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.actor-type .trait-list li {
|
||||
flex-basis: 50%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.actor-type .trait-list li.form-group {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
.actor-type label.radio {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.actor-type label.radio > input[type="radio"] {
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
.actor-type li.custom-type input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
/* ----------------------------------------- */
|
||||
/* Add Feature Prompt Specifically */
|
||||
/* ----------------------------------------- */
|
||||
.sw5e.select-items-prompt .dialog-content {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.sw5e.select-items-prompt .items-list {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.sw5e.select-items-prompt .item-name > label,
|
||||
.sw5e.select-items-prompt .item-image,
|
||||
.sw5e.select-items-prompt input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sw5e.select-items-prompt .item-name > label {
|
||||
align-items: center;
|
||||
}
|
||||
/* ----------------------------------------- */
|
||||
/* HUD
|
||||
/* ----------------------------------------- */
|
||||
.placeable-hud .control-icon {
|
||||
|
@ -574,6 +621,9 @@
|
|||
/* Powerbook */
|
||||
/* ----------------------------------------- */
|
||||
/* ----------------------------------------- */
|
||||
/* Features Tab */
|
||||
/* ----------------------------------------- */
|
||||
/* ----------------------------------------- */
|
||||
/* TinyMCE */
|
||||
/* ----------------------------------------- */
|
||||
}
|
||||
|
@ -627,10 +677,12 @@
|
|||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement h4.attribute-name {
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement h4.attribute-name,
|
||||
.sw5e.sheet.actor .sheet-header .attributes .hit-dice h4.attribute-name {
|
||||
position: relative;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement .config-button {
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement .config-button,
|
||||
.sw5e.sheet.actor .sheet-header .attributes .hit-dice .config-button {
|
||||
position: absolute;
|
||||
display: none;
|
||||
right: 0;
|
||||
|
@ -638,7 +690,8 @@
|
|||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement:hover .config-button {
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement:hover .config-button,
|
||||
.sw5e.sheet.actor .sheet-header .attributes .hit-dice:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes input.temphp {
|
||||
|
@ -1113,6 +1166,9 @@
|
|||
.sw5e.sheet.actor .powerbook-empty .item-controls {
|
||||
flex: 1;
|
||||
}
|
||||
.sw5e.sheet.actor .features i.original-class {
|
||||
color: #4b4a44;
|
||||
}
|
||||
.sw5e.sheet.actor .editor {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
@ -1229,16 +1285,16 @@
|
|||
.sw5e.sheet.item .sheet-body .smalltable td:nth-child(even) {
|
||||
width: 150px;
|
||||
margin: 0.5em 0.5em;
|
||||
padding: 0px 10px 0px 10px;
|
||||
padding: 0 10px 0 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .smalltable thead {
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .smalltable th {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-weight: bold;
|
||||
|
@ -1252,12 +1308,12 @@
|
|||
.sw5e.sheet.item .sheet-body .smalltable th:nth-child(even) {
|
||||
width: 150px;
|
||||
margin: 0.5em 0.5em;
|
||||
padding: 0px 10px 0px 10px;
|
||||
padding: 0 10px 0 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .medtable table {
|
||||
width: 500px;
|
||||
border: 0px;
|
||||
border: 0;
|
||||
margin: 0.5em 0.5em;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .medtable td:nth-child(odd) {
|
||||
|
@ -1268,16 +1324,16 @@
|
|||
.sw5e.sheet.item .sheet-body .medtable td:nth-child(even) {
|
||||
width: 450px;
|
||||
margin: 0.5em 0.5em;
|
||||
padding: 0px 10px 0px 0px;
|
||||
padding: 0 10px 0 0;
|
||||
text-align: left;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .medtable thead {
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .medtable th {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-weight: bold;
|
||||
|
@ -1290,8 +1346,8 @@
|
|||
text-align: left;
|
||||
}
|
||||
.sw5e.sheet.item .sheet-body .classtable blockquote {
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
background-color: #bdc8cc;
|
||||
width: 600px;
|
||||
}
|
||||
|
@ -1305,8 +1361,8 @@
|
|||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
margin: 0.5em 0;
|
||||
|
@ -1316,7 +1372,7 @@
|
|||
.sw5e.sheet.item .sheet-body .classtable thead {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-style: normal;
|
||||
|
@ -1325,7 +1381,7 @@
|
|||
.sw5e.sheet.item .sheet-body .classtable th {
|
||||
color: #000000;
|
||||
text-shadow: none;
|
||||
border-bottom: 0px;
|
||||
border-bottom: 0;
|
||||
background-color: #bdc8cc;
|
||||
text-transform: none;
|
||||
font-style: normal;
|
||||
|
@ -1360,7 +1416,7 @@
|
|||
width: 100%;
|
||||
line-height: 18px;
|
||||
margin-bottom: 15px;
|
||||
border: 0 0 0 0;
|
||||
border: 0;
|
||||
border-bottom: none;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
@ -1745,7 +1801,7 @@
|
|||
text-align: right;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.sw5e.sheet.actor.character .resource .attribute-value input {
|
||||
.sw5e.sheet.actor.character .resource .attribute-value > input {
|
||||
flex: 0 0 25%;
|
||||
}
|
||||
.sw5e.sheet.actor.character .resource .attribute-value label.recharge {
|
||||
|
@ -1755,6 +1811,7 @@
|
|||
font-size: 11px;
|
||||
text-align: center;
|
||||
color: #4b4a44;
|
||||
align-items: center;
|
||||
}
|
||||
.sw5e.sheet.actor.character .resource .attribute-value label.recharge input[type="checkbox"] {
|
||||
height: 14px;
|
||||
|
@ -1853,6 +1910,26 @@
|
|||
.sw5e.sheet.actor.npc .summary {
|
||||
font-size: 18px;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .summary li.creature-type {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 1em;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .summary li.creature-type span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .summary li.creature-type .config-button {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 2em;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .summary li.creature-type:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
.sw5e.sheet.actor.vehicle .features .item-controls {
|
||||
flex: 0 0 68px;
|
||||
}
|
||||
|
|
53
sw5e.js
53
sw5e.js
|
@ -12,12 +12,13 @@ import { SW5E } from "./module/config.js";
|
|||
import { registerSystemSettings } from "./module/settings.js";
|
||||
import { preloadHandlebarsTemplates } from "./module/templates.js";
|
||||
import { _getInitiativeFormula } from "./module/combat.js";
|
||||
import { measureDistances, getBarAttribute } from "./module/canvas.js";
|
||||
import { measureDistances } from "./module/canvas.js";
|
||||
|
||||
// Import Entities
|
||||
// Import Documents
|
||||
import Actor5e from "./module/actor/entity.js";
|
||||
import Item5e from "./module/item/entity.js";
|
||||
import CharacterImporter from "./module/characterImporter.js";
|
||||
import { TokenDocument5e, Token5e } from "./module/token.js"
|
||||
|
||||
// Import Applications
|
||||
import AbilityTemplate from "./module/pixi/ability-template.js";
|
||||
|
@ -45,6 +46,9 @@ import * as migrations from "./module/migration.js";
|
|||
/* Foundry VTT Initialization */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
// Keep on while migrating to Foundry version 0.8
|
||||
CONFIG.debug.hooks = true;
|
||||
|
||||
Hooks.once("init", function() {
|
||||
console.log(`SW5e | Initializing SW5E System\n${SW5E.ASCII}`);
|
||||
|
||||
|
@ -61,7 +65,8 @@ Hooks.once("init", function() {
|
|||
ItemSheet5e,
|
||||
ShortRestDialog,
|
||||
TraitSelector,
|
||||
ActorMovementConfig
|
||||
ActorMovementConfig,
|
||||
ActorSensesConfig
|
||||
},
|
||||
canvas: {
|
||||
AbilityTemplate
|
||||
|
@ -71,6 +76,8 @@ Hooks.once("init", function() {
|
|||
entities: {
|
||||
Actor5e,
|
||||
Item5e,
|
||||
TokenDocument5e,
|
||||
Token5e,
|
||||
},
|
||||
macros: macros,
|
||||
migrations: migrations,
|
||||
|
@ -79,8 +86,10 @@ Hooks.once("init", function() {
|
|||
|
||||
// Record Configuration Values
|
||||
CONFIG.SW5E = SW5E;
|
||||
CONFIG.Actor.entityClass = Actor5e;
|
||||
CONFIG.Item.entityClass = Item5e;
|
||||
CONFIG.Actor.documentClass = Actor5e;
|
||||
CONFIG.Item.documentClass = Item5e;
|
||||
CONFIG.Token.documentClass = TokenDocument5e;
|
||||
CONFIG.Token.objectClass = Token5e;
|
||||
CONFIG.time.roundTime = 6;
|
||||
CONFIG.fontFamilies = [
|
||||
"Engli-Besh",
|
||||
|
@ -88,6 +97,9 @@ Hooks.once("init", function() {
|
|||
"Russo One"
|
||||
];
|
||||
|
||||
CONFIG.Dice.DamageRoll = dice.DamageRoll;
|
||||
CONFIG.Dice.D20Roll = dice.D20Roll;
|
||||
|
||||
// 5e cone RAW should be 53.13 degrees
|
||||
CONFIG.MeasuredTemplate.defaults.angle = 53.13;
|
||||
|
||||
|
@ -100,7 +112,11 @@ Hooks.once("init", function() {
|
|||
|
||||
// Patch Core Functions
|
||||
CONFIG.Combat.initiative.formula = "1d20 + @attributes.init.mod + @attributes.init.prof + @attributes.init.bonus";
|
||||
Combat.prototype._getInitiativeFormula = _getInitiativeFormula;
|
||||
Combatant.prototype._getInitiativeFormula = _getInitiativeFormula;
|
||||
|
||||
// Register Roll Extensions
|
||||
CONFIG.Dice.rolls.push(dice.D20Roll);
|
||||
CONFIG.Dice.rolls.push(dice.DamageRoll);
|
||||
|
||||
// Register sheet application classes
|
||||
Actors.unregisterSheet("core", ActorSheet);
|
||||
|
@ -124,11 +140,11 @@ Hooks.once("init", function() {
|
|||
makeDefault: false,
|
||||
label: "SW5E.SheetClassNPCOld"
|
||||
});
|
||||
Actors.registerSheet("sw5e", ActorSheet5eStarship, {
|
||||
types: ["starship"],
|
||||
makeDefault: true,
|
||||
label: "SW5E.SheetClassStarship"
|
||||
});
|
||||
// Actors.registerSheet("sw5e", ActorSheet5eStarship, {
|
||||
// types: ["starship"],
|
||||
// makeDefault: true,
|
||||
// label: "SW5E.SheetClassStarship"
|
||||
// });
|
||||
Actors.registerSheet('sw5e', ActorSheet5eVehicle, {
|
||||
types: ['vehicle'],
|
||||
makeDefault: true,
|
||||
|
@ -142,7 +158,7 @@ Hooks.once("init", function() {
|
|||
});
|
||||
|
||||
// Preload Handlebars Templates
|
||||
preloadHandlebarsTemplates();
|
||||
return preloadHandlebarsTemplates();
|
||||
});
|
||||
|
||||
|
||||
|
@ -191,7 +207,6 @@ Hooks.once("setup", function() {
|
|||
});
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Once the entire VTT framework is initialized, check to see if we should perform a data migration
|
||||
*/
|
||||
|
@ -203,12 +218,12 @@ Hooks.once("ready", function() {
|
|||
// Determine whether a system migration is required and feasible
|
||||
if ( !game.user.isGM ) return;
|
||||
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
|
||||
const NEEDS_MIGRATION_VERSION = "1.2.4.R1-A5";
|
||||
const NEEDS_MIGRATION_VERSION = "1.3.0.R1-A6";
|
||||
// Check for R1 SW5E versions
|
||||
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5";
|
||||
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A6";
|
||||
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
||||
const needsMigration = currentVersion && (isNewerVersion(SW5E_NEEDS_MIGRATION_VERSION, currentVersion) || isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion));
|
||||
if ( !needsMigration ) return;
|
||||
if (!needsMigration && needsMigration !== "") return;
|
||||
|
||||
// Perform the migration
|
||||
if ( currentVersion && isNewerVersion(COMPATIBLE_MIGRATION_VERSION, currentVersion) ) {
|
||||
|
@ -223,13 +238,9 @@ Hooks.once("ready", function() {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
Hooks.on("canvasInit", function() {
|
||||
|
||||
// Extend Diagonal Measurement
|
||||
canvas.grid.diagonalRule = game.settings.get("sw5e", "diagonalMovement");
|
||||
SquareGrid.prototype.measureDistances = measureDistances;
|
||||
|
||||
// Extend Token Resource Bars
|
||||
Token.prototype.getBarAttribute = getBarAttribute;
|
||||
});
|
||||
|
||||
|
||||
|
@ -272,7 +283,7 @@ Hooks.on("renderRollTableDirectory", (app, html, data)=> {
|
|||
Hooks.on("ActorSheet5eCharacterNew", (app, html, data) => {
|
||||
console.log("renderSwaltSheet");
|
||||
});
|
||||
// TODO I should remove this
|
||||
// FIXME: This helper is needed for the vehicle sheet. It should probably be refactored.
|
||||
Handlebars.registerHelper('getProperty', function (data, property) {
|
||||
return getProperty(data, property);
|
||||
});
|
||||
|
|
54
system.json
54
system.json
|
@ -2,7 +2,7 @@
|
|||
"name": "sw5e",
|
||||
"title": "SW 5th Edition",
|
||||
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.",
|
||||
"version": "1.2.4.R1-A5",
|
||||
"version": "1.3.3.R1-A6",
|
||||
"author": "Dev Team",
|
||||
"scripts": [],
|
||||
"esmodules": ["sw5e.js"],
|
||||
|
@ -50,18 +50,6 @@
|
|||
"path": "./packs/packs/conditions.db",
|
||||
"entity": "JournalEntry"
|
||||
},
|
||||
{
|
||||
"name": "deployments",
|
||||
"label": "Deployments",
|
||||
"path": "./packs/packs/deployments.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "deploymentfeatures",
|
||||
"label": "Deployment Features",
|
||||
"path": "./packs/packs/deploymentfeatures.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "enhanceditems",
|
||||
"label": "Enhanced Items",
|
||||
|
@ -121,36 +109,6 @@
|
|||
"label": "Species Traits",
|
||||
"path": "./packs/packs/speciestraits.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "starshiparmor",
|
||||
"label": "Starship Armor",
|
||||
"path": "./packs/packs/starshiparmor.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "starshipequipment",
|
||||
"label": "Starship Equipment",
|
||||
"path": "./packs/packs/starshipequipment.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "starshipfeatures",
|
||||
"label": "Starship Features",
|
||||
"path": "./packs/packs/starshipfeatures.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "starshipmodifications",
|
||||
"label": "Starship Modifications",
|
||||
"path": "./packs/packs/starshipmodifications.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "starshipweapons",
|
||||
"label": "Starship Weapons",
|
||||
"path": "./packs/packs/starshipweapons.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "tables",
|
||||
|
@ -164,12 +122,6 @@
|
|||
"path": "./packs/packs/techpowers.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "ventures",
|
||||
"label": "Ventures",
|
||||
"path": "./packs/packs/ventures.db",
|
||||
"entity": "Item"
|
||||
},
|
||||
{
|
||||
"name": "weapons",
|
||||
"label": "Weapons",
|
||||
|
@ -199,8 +151,8 @@
|
|||
"gridUnits": "ft",
|
||||
"primaryTokenAttribute": "attributes.hp",
|
||||
"secondaryTokenAttribute": null,
|
||||
"minimumCoreVersion": "0.7.7",
|
||||
"compatibleCoreVersion": "0.7.9",
|
||||
"minimumCoreVersion": "0.8.3",
|
||||
"compatibleCoreVersion": "0.8.6",
|
||||
"url": "https://github.com/unrealkakeman89/sw5e",
|
||||
"manifest": "https://raw.githubusercontent.com/unrealkakeman89/sw5e/master/system.json",
|
||||
"download": "https://github.com/unrealkakeman89/sw5e/archive/master.zip"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"Actor": {
|
||||
"types": ["character", "npc", "starship", "vehicle"],
|
||||
"types": ["character", "npc", "vehicle"],
|
||||
"templates": {
|
||||
"common": {
|
||||
"abilities": {
|
||||
|
@ -333,6 +333,7 @@
|
|||
},
|
||||
"details": {
|
||||
"background": "",
|
||||
"originalClass": "",
|
||||
"xp": {
|
||||
"value": 0,
|
||||
"min": 0,
|
||||
|
@ -382,7 +383,12 @@
|
|||
"npc": {
|
||||
"templates": ["common", "creature"],
|
||||
"details": {
|
||||
"type": "",
|
||||
"type": {
|
||||
"value": "",
|
||||
"subtype": "",
|
||||
"swarm": "",
|
||||
"custom": ""
|
||||
},
|
||||
"environment": "",
|
||||
"cr": 1,
|
||||
"powerForceLevel": 0,
|
||||
|
@ -880,13 +886,17 @@
|
|||
"archetype": "",
|
||||
"hitDice": "d6",
|
||||
"hitDiceUsed": 0,
|
||||
"saves": [],
|
||||
"skills": {
|
||||
"number": 2,
|
||||
"choices": [],
|
||||
"value": []
|
||||
},
|
||||
"source": "",
|
||||
"powercasting": "none"
|
||||
"powercasting": {
|
||||
"progression": "none",
|
||||
"ability": ""
|
||||
}
|
||||
},
|
||||
"classfeature": {
|
||||
"templates": ["itemDescription", "activatedEffect", "action"],
|
||||
|
|
|
@ -63,7 +63,10 @@
|
|||
|
||||
{{!-- HIT DICE / SHORT & LONG REST BUTTONS --}}
|
||||
<section>
|
||||
<h1>{{ localize "SW5E.HitDice" }}</h1>
|
||||
<h1>
|
||||
{{ localize "SW5E.HitDice" }}
|
||||
<a class="config-button" data-action="hit-dice" title="{{localize 'SW5E.HitDiceConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</h1>
|
||||
<div class="attribute-value multiple">
|
||||
<span class="value-number">{{data.attributes.hd}}</span>
|
||||
<span class="value-separator">/</span>
|
||||
|
@ -147,4 +150,5 @@
|
|||
<section class="tab biography" data-group="primary" data-tab="biography">
|
||||
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-biography.html"}}
|
||||
</section>
|
||||
</section>
|
||||
</form>
|
|
@ -137,4 +137,5 @@
|
|||
<section class="tab biography" data-group="primary" data-tab="biography">
|
||||
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-biography.html"}}
|
||||
</section>
|
||||
</section>
|
||||
</form>
|
|
@ -1,4 +1,4 @@
|
|||
<li class="item group-grid-fav-items" data-item-id="{{item._id}}">
|
||||
<li class="item group-grid-fav-items" data-item-id="{{item.data._id}}">
|
||||
<div class="item-name rollable">
|
||||
<div class="item-image" style="background-image: url({{item.img}})"></div>
|
||||
<h4>{{item.name}}</h4>
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
<div class="panel">
|
||||
<section class="additional-info">
|
||||
<h1 class="section-titles">Description</h1>
|
||||
{{editor content=data.details.description.value target="data.details.description.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.description.value target="data.details.description.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section>
|
||||
<h1 class="section-titles">Background</h1>
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
<span class="npc-size">{{lookup config.actorSizes data.traits.size}}</span>
|
||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}"
|
||||
placeholder="{{ localize 'SW5E.Alignment' }}" />
|
||||
<input type="text" name="data.details.type" value="{{data.details.type}}"
|
||||
placeholder="{{ localize 'SW5E.Type' }}" />
|
||||
<div class="creature-type roundTransition">
|
||||
<span title="{{labels.type}}">{{labels.type}}</span>
|
||||
<a class="config-button" data-action="type" title="{{localize 'SW5E.CreatureTypeConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
<input type="text" name="data.details.source" value="{{data.details.source}}"
|
||||
placeholder="{{ localize 'SW5E.Source' }}" />
|
||||
</div>
|
||||
|
@ -90,7 +92,7 @@
|
|||
<ol>
|
||||
{{#each data.abilities as |ability id|}}
|
||||
<li class="ability {{#if ability.proficient}}proficient{{/if}}" data-ability="{{id}}">
|
||||
<h2 class="ability-name rollable">{{ability.label}}</h4>
|
||||
<h2 class="ability-name rollable">{{ability.label}}</h2>
|
||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="text"
|
||||
value="{{ability.value}}" data-dtype="Number" placeholder="10" />
|
||||
<div class="ability-modifiers">
|
||||
|
@ -111,18 +113,20 @@
|
|||
<section class="skills">
|
||||
<h1>{{localize "SW5E.Skills"}}</h1>
|
||||
<ol>
|
||||
{{#each data.skills as |skill s|}}
|
||||
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}"
|
||||
data-dtype="Number" />
|
||||
<button class="proficiency-toggle skill-proficiency"
|
||||
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
||||
<span class="skill-name rollable">{{skill.label}}</span>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
{{!-- <input class="skill-bonus" name="data.skills.{{s}}.bonus" type="text" value="{{numberFormat skill.bonus decimals=0 sign=true}}" data-dtype="Number" placeholder="0" title="Misc. Modifier"/> --}}
|
||||
{{!-- <span class="skill-passive">({{skill.passive}})</span> --}}
|
||||
</li>
|
||||
{{#each config.skills as |label s|}}
|
||||
{{#with (lookup ../data.skills s) as |skill|}}
|
||||
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}"
|
||||
data-dtype="Number" />
|
||||
<button class="proficiency-toggle skill-proficiency"
|
||||
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
||||
<span class="skill-name rollable">{{label}}</span>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
{{!-- <input class="skill-bonus" name="data.skills.{{s}}.bonus" type="text" value="{{numberFormat skill.bonus decimals=0 sign=true}}" data-dtype="Number" placeholder="0" title="Misc. Modifier"/> --}}
|
||||
{{!-- <span class="skill-passive">({{skill.passive}})</span> --}}
|
||||
</li>
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
</ol>
|
||||
</section>
|
||||
|
@ -191,7 +195,7 @@
|
|||
<div class="panel">
|
||||
<section>
|
||||
<h1>{{localize "SW5E.Biography"}}</h1>
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<div class="panel additional-info">
|
||||
<section><h1 class="section-titles biopage">{{localize "SW5E.PersonalityTraits" }}</h1>
|
||||
{{editor content=data.details.trait target="data.details.trait" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.trait target="data.details.trait" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section><h1 class="section-titles biopage">{{localize "SW5E.Ideals" }}</h1>
|
||||
{{editor content=data.details.ideal target="data.details.ideal" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.ideal target="data.details.ideal" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section><h1 class="section-titles biopage">{{localize "SW5E.Bonds" }}</h1>
|
||||
{{editor content=data.details.bond target="data.details.bond" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.bond target="data.details.bond" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section><h1 class="section-titles biopage">{{localize "SW5E.Flaws" }}</h1>
|
||||
{{editor content=data.details.flaw target="data.details.flaw" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.flaw target="data.details.flaw" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
</div>
|
||||
<div class="panel background">
|
||||
<section><h1 class="section-titles biopage">{{localize "SW5E.Description" }}</h1>
|
||||
{{editor content=data.details.description.value target="data.details.description.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.description.value target="data.details.description.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section><h1 class="section-titles">{{localize "SW5E.Background" }}</h1>
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
</div>
|
|
@ -5,7 +5,7 @@
|
|||
<ol>
|
||||
{{#each data.abilities as |ability id|}}
|
||||
<li class="ability {{#if ability.proficient}}proficient{{/if}}" data-ability="{{id}}">
|
||||
<h2 class="ability-name rollable">{{ability.label}}</h4>
|
||||
<h2 class="ability-name rollable">{{ability.label}}</h2>
|
||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="text"
|
||||
value="{{ability.value}}" data-dtype="Number" placeholder="10" />
|
||||
<div class="ability-modifiers">
|
||||
|
@ -26,18 +26,20 @@
|
|||
<section class="skills">
|
||||
<h1>{{localize "SW5E.Skills" }}</h1>
|
||||
<ol>
|
||||
{{#each data.skills as |skill s|}}
|
||||
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}"
|
||||
data-dtype="Number" />
|
||||
<button class="proficiency-toggle skill-proficiency"
|
||||
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
||||
<span class="skill-name rollable">{{skill.label}}</span>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
{{!-- <input class="skill-bonus" name="data.skills.{{s}}.bonus" type="text" value="{{numberFormat skill.bonus decimals=0 sign=true}}" data-dtype="Number" placeholder="0" title="Misc. Modifier"/> --}}
|
||||
{{!-- <span class="skill-passive">({{skill.passive}})</span> --}}
|
||||
</li>
|
||||
{{#each config.skills as |label s|}}
|
||||
{{#with (lookup ../data.skills s) as |skill|}}
|
||||
<li class="skill {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}"
|
||||
data-dtype="Number" />
|
||||
<button class="proficiency-toggle skill-proficiency"
|
||||
title="{{skill.hover}}">{{{skill.icon}}}</button>
|
||||
<span class="skill-name rollable">{{label}}</span>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
{{!-- <input class="skill-bonus" name="data.skills.{{s}}.bonus" type="text" value="{{numberFormat skill.bonus decimals=0 sign=true}}" data-dtype="Number" placeholder="0" title="Misc. Modifier"/> --}}
|
||||
{{!-- <span class="skill-passive">({{skill.passive}})</span> --}}
|
||||
</li>
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
</ol>
|
||||
</section>
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<!-- {{item.data.hitDice}} -->
|
||||
</div>
|
||||
<div class="item-detail player-class-levels">
|
||||
<strong>Level {{item.data.levels}}</strong> <a class="increment-class-level"><i style="color:#c40f0f; text-shadow: 0px 1px 2px gray;" class="fas fa-arrow-up"></i></a> <a class="decrement-class-level"><i style="color:#c40f0f; text-shadow: 0px 1px 2px gray" class="fas fa-arrow-down"></i></a>
|
||||
<strong>Level {{item.data.levels}}</strong> <a class="increment-class-level"><i style="color:#c40f0f; text-shadow: 0 1px 2px gray;" class="fas fa-arrow-up"></i></a> <a class="decrement-class-level"><i style="color:#c40f0f; text-shadow: 0 1px 2px gray" class="fas fa-arrow-down"></i></a>
|
||||
<!-- {{#if ../owner}}
|
||||
<a class="item-control item-create" title="{{localize 'SW5E.LevelAdd'}}"
|
||||
{{#each item.data.levels as |v k|}}data-{{k}}="{{v}}" {{/each}}>
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<!-- {{item.data.hitDice}} -->
|
||||
</div>
|
||||
<div class="item-detail player-class-levels">
|
||||
<strong>Level {{item.data.levels}}</strong> <a class="increment-class-level"><i style="color:#c40f0f; text-shadow: 0px 1px 2px gray;" class="fas fa-arrow-up"></i></a> <a class="decrement-class-level"><i style="color:#c40f0f; text-shadow: 0px 1px 2px gray" class="fas fa-arrow-down"></i></a>
|
||||
<strong>Level {{item.data.levels}}</strong> <a class="increment-class-level"><i style="color:#c40f0f; text-shadow: 0 1px 2px gray;" class="fas fa-arrow-up"></i></a> <a class="decrement-class-level"><i style="color:#c40f0f; text-shadow: 0 1px 2px gray" class="fas fa-arrow-down"></i></a>
|
||||
<!-- {{#if ../owner}}
|
||||
<a class="item-control item-create" title="{{localize 'SW5E.LevelAdd'}}"
|
||||
{{#each item.data.levels as |v k|}}data-{{k}}="{{v}}" {{/each}}>
|
||||
|
|
|
@ -3,31 +3,29 @@
|
|||
<input type="text" name="data.details.notesname" value="{{data.details.notesname}}"
|
||||
placeholder="{{localize 'SW5E.Journal'}}" />
|
||||
|
||||
{{editor content=data.details.notes.value target="data.details.notes.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes.value target="data.details.notes.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section>
|
||||
<input type="text" name="data.details.notes1name" value="{{data.details.notes1name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}"/>
|
||||
{{editor content=data.details.notes1.value target="data.details.notes1.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes1.value target="data.details.notes1.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<input type="text" name="data.details.notes2name" value="{{data.details.notes2name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes2.value target="data.details.notes2.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes2.value target="data.details.notes2.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<input type="text" name="data.details.notes3name" value="{{data.details.notes3name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes3.value target="data.details.notes3.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes3.value target="data.details.notes3.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<input type="text" name="data.details.notes4name" value="{{data.details.notes4name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes4.value target="data.details.notes4.value" button=true owner=owner editable=editable}}
|
||||
</section>
|
||||
|
||||
{{editor content=data.details.notes4.value target="data.details.notes4.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
</div>
|
|
@ -9,28 +9,28 @@
|
|||
<input type="text" name="data.details.notesname" value="{{data.details.notesname}}"
|
||||
placeholder="{{localize 'SW5E.Journal'}}" />
|
||||
|
||||
{{editor content=data.details.notes.value target="data.details.notes.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes.value target="data.details.notes.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
<section>
|
||||
<input type="text" name="data.details.notes1name" value="{{data.details.notes1name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes1.value target="data.details.notes1.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes1.value target="data.details.notes1.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<input type="text" name="data.details.notes2name" value="{{data.details.notes2name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes2.value target="data.details.notes2.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes2.value target="data.details.notes2.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<input type="text" name="data.details.notes3name" value="{{data.details.notes3name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes3.value target="data.details.notes3.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes3.value target="data.details.notes3.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<input type="text" name="data.details.notes4name" value="{{data.details.notes4name}}"
|
||||
placeholder="{{localize 'SW5E.AdditionalNotes'}}" />
|
||||
{{editor content=data.details.notes4.value target="data.details.notes4.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.notes4.value target="data.details.notes4.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</section>
|
|
@ -1,194 +1,193 @@
|
|||
<section class="resources">
|
||||
<section class="resource-items">
|
||||
{{#each resources as |res|}}
|
||||
<div class="resource">
|
||||
<h1>
|
||||
<input name="data.resources.{{res.name}}.label" type="text" value="{{res.label}}"
|
||||
placeholder="{{res.placeholder}}" />
|
||||
</h1>
|
||||
<div class="attribute-value">
|
||||
<input name="data.resources.{{res.name}}.value" type="text" value="{{res.value}}" data-dtype="Number"
|
||||
placeholder="0" class="value-number" />
|
||||
<span class="value-separator">/</span>
|
||||
<input name="data.resources.{{res.name}}.max" type="text" value="{{res.max}}" data-dtype="Number"
|
||||
placeholder="0" class="value-number" />
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox"
|
||||
{{checked res.sr}} />
|
||||
</label>
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationLR" }} <input name="data.resources.{{res.name}}.lr" type="checkbox"
|
||||
{{checked res.lr}} />
|
||||
</label>
|
||||
</footer>
|
||||
</div>
|
||||
{{/each}}
|
||||
<section class="resource-items">
|
||||
{{#each resources as |res|}}
|
||||
<div class="resource">
|
||||
<h1>
|
||||
<input name="data.resources.{{res.name}}.label" type="text" value="{{res.label}}"
|
||||
placeholder="{{res.placeholder}}" />
|
||||
</h1>
|
||||
<div class="attribute-value">
|
||||
<input name="data.resources.{{res.name}}.value" type="text" value="{{res.value}}" data-dtype="Number"
|
||||
placeholder="0" class="value-number" />
|
||||
<span class="value-separator">/</span>
|
||||
<input name="data.resources.{{res.name}}.max" type="text" value="{{res.max}}" data-dtype="Number"
|
||||
placeholder="0" class="value-number" />
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox"
|
||||
{{checked res.sr}} />
|
||||
</label>
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationLR" }} <input name="data.resources.{{res.name}}.lr" type="checkbox"
|
||||
{{checked res.lr}} />
|
||||
</label>
|
||||
</footer>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
</section>
|
||||
<section class="counters">
|
||||
<div class="counter">
|
||||
<h4 class="death-save rollable" data-action="rollDeathSave">{{ localize "SW5E.DeathSave" }}</h4>
|
||||
<div class="counter-value">
|
||||
<div class="death-success">
|
||||
<i class="fas fa-check"></i>
|
||||
<input type="text" name="data.attributes.death.success" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.death.success}}" />
|
||||
</div>
|
||||
<div class="death-fail">
|
||||
<i class="fas fa-times"></i>
|
||||
<input type="text" name="data.attributes.death.failure" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.death.failure}}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter">
|
||||
<h4>{{ localize "SW5E.Exhaustion" }}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.exhaustion" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.exhaustion}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter">
|
||||
<h4>{{ localize "SW5E.Inspiration" }}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="checkbox" name="data.attributes.inspiration" data-dtype="Boolean"
|
||||
{{checked data.attributes.inspiration}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="traits">
|
||||
<label>
|
||||
{{localize "SW5E.Size"}}
|
||||
<select class="actor-size" name="data.traits.size">
|
||||
{{#select data.traits.size}}
|
||||
{{#each config.actorSizes as |label size|}}
|
||||
<option value="{{size}}">{{label}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</label>
|
||||
{{#if isNPC}}
|
||||
</section>
|
||||
<section class="counters">
|
||||
<div class="counter">
|
||||
<h4 class="death-save rollable" data-action="rollDeathSave">{{ localize "SW5E.DeathSave" }}</h4>
|
||||
<div class="counter-value">
|
||||
<div class="death-success">
|
||||
<i class="fas fa-check"></i>
|
||||
<input type="text" name="data.attributes.death.success" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.death.success}}" />
|
||||
</div>
|
||||
<div class="death-fail">
|
||||
<i class="fas fa-times"></i>
|
||||
<input type="text" name="data.attributes.death.failure" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.death.failure}}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter">
|
||||
<h4>{{ localize "SW5E.Exhaustion" }}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.exhaustion" data-dtype="Number" placeholder="0"
|
||||
value="{{data.attributes.exhaustion}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter">
|
||||
<h4>{{ localize "SW5E.Inspiration" }}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="checkbox" name="data.attributes.inspiration" data-dtype="Boolean"
|
||||
{{checked data.attributes.inspiration}} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="traits">
|
||||
<label>
|
||||
{{localize "SW5E.Powercasting"}}
|
||||
<select class="powercasting" name="data.attributes.powercasting">
|
||||
{{#select data.attributes.powercasting}}
|
||||
{{#each config.powerMaxLevel as |id class|}}
|
||||
<option value="{{class}}">{{class}}</option>
|
||||
{{localize "SW5E.Size"}}
|
||||
<select class="actor-size" name="data.traits.size">
|
||||
{{#select data.traits.size}}
|
||||
{{#each config.actorSizes as |label size|}}
|
||||
<option value="{{size}}">{{label}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</label>
|
||||
{{#if isNPC}}
|
||||
<label>
|
||||
{{localize "SW5E.Powercasting"}}
|
||||
<select class="powercasting" name="data.attributes.powercasting">
|
||||
{{#select data.attributes.powercasting}}
|
||||
{{#each config.powerMaxLevel as |id class|}}
|
||||
<option value="{{class}}">{{class}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</label>
|
||||
{{/if}}
|
||||
<label class="{{#unless data.traits.senses}}inactive{{/unless}}">
|
||||
{{#unless isVehicle}}
|
||||
<label>{{localize "SW5E.Senses"}}</label>
|
||||
<a class="config-button" data-action="senses" title="{{localize 'SW5E.SensesConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
<ul class="traits-list">
|
||||
{{#each senses as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</label>
|
||||
{{/if}}
|
||||
<label class="{{#unless data.traits.senses}}inactive{{/unless}}">
|
||||
</ul>
|
||||
{{/unless}}
|
||||
</label>
|
||||
<div class="languages">
|
||||
<label data-options="share-languages" class="languages">{{localize "SW5E.Languages"}}</label>
|
||||
<a class="trait-selector" data-options="languages" data-target="data.traits.languages">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.languages.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{#unless isVehicle}}
|
||||
<label>{{localize "SW5E.Senses"}}</label>
|
||||
<a class="config-button" data-action="senses" title="{{localize 'SW5E.SensesConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
<ul class="traits-list">
|
||||
{{#each senses as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<div class="form-group ">
|
||||
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
||||
<a class="config-button" data-action="flags" title="{{localize 'SW5E.SpecialTraits'}}"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</label>
|
||||
<div class="languages">
|
||||
<label data-options="share-languages" class="languages">{{localize "SW5E.Languages"}}</label>
|
||||
<a class="trait-selector" data-options="languages" data-target="data.traits.languages">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.languages.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
|
||||
<div>
|
||||
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
||||
<a class="trait-selector" data-options="armorProficiencies" data-target="data.traits.armorProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.armorProf.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{#unless isVehicle}}
|
||||
<div class="form-group ">
|
||||
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
||||
<a class="config-button" data-action="flags" title="{{localize 'SW5E.SpecialTraits'}}"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
||||
<a class="trait-selector" data-options="toolProficiencies" data-target="data.traits.toolProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.toolProf.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||
<a class="trait-selector" data-options="weaponProficiencies" data-target="data.traits.weaponProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.weaponProf.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.DamImm"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.di">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.di.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.DamRes"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dr">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.dr.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.DamVuln"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dv">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.dv.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>{{localize "SW5E.TraitArmorProf"}}</label>
|
||||
<a class="trait-selector" data-options="armorProficiencies" data-target="data.traits.armorProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.armorProf.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.TraitToolProf"}}</label>
|
||||
<a class="trait-selector" data-options="toolProficiencies" data-target="data.traits.toolProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.toolProf.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.TraitWeaponProf"}}</label>
|
||||
<a class="trait-selector" data-options="weaponProficiencies" data-target="data.traits.weaponProf">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.weaponProf.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.DamImm"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.di">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.di.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.DamRes"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dr">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.dr.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{localize "SW5E.DamVuln"}}</label>
|
||||
<a class="trait-selector" data-options="damageResistanceTypes" data-target="data.traits.dv">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.dv.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>{{localize "SW5E.ConImm"}}</label>
|
||||
<a class="trait-selector" data-options="conditionTypes" data-target="data.traits.ci">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.ci.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>{{#if isCharacter}}
|
||||
|
||||
{{/if}}
|
||||
<ul class="passives"></ul>
|
||||
</section>
|
||||
<div>
|
||||
<label>{{localize "SW5E.ConImm"}}</label>
|
||||
<a class="trait-selector" data-options="conditionTypes" data-target="data.traits.ci">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<ul class="traits-list">
|
||||
{{#each data.traits.ci.selected as |v k|}}
|
||||
<li>{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="passives"></ul>
|
||||
</section>
|
||||
</section>
|
|
@ -104,7 +104,7 @@
|
|||
<ol>
|
||||
{{#each data.abilities as |ability id|}}
|
||||
<li class="ability {{#if ability.proficient}}proficient{{/if}}" data-ability="{{id}}">
|
||||
<h2 class="ability-name rollable">{{ability.label}}</h4>
|
||||
<h2 class="ability-name rollable">{{ability.label}}</h2>
|
||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="text"
|
||||
value="{{ability.value}}" data-dtype="Number" placeholder="10" />
|
||||
<div class="ability-modifiers">
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||
button=true owner=owner editable=editable}}
|
||||
button=true owner=owner editable=editable rollData=rollData}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -60,8 +60,11 @@
|
|||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{ localize "SW5E.HitDice" }}</h4>
|
||||
<li class="attribute hit-dice">
|
||||
<h4 class="attribute-name box-title">
|
||||
{{ localize "SW5E.HitDice" }}
|
||||
<a class="config-button" data-action="hit-dice" title="{{localize 'SW5E.HitDiceConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<label class="hit-dice">{{data.attributes.hd}} <span class="sep"> / </span> {{data.details.level}}</label>
|
||||
</div>
|
||||
|
@ -143,15 +146,17 @@
|
|||
|
||||
{{!-- Skills --}}
|
||||
<ul class="skills-list">
|
||||
{{#each data.skills as |skill s|}}
|
||||
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}" data-dtype="Number"/>
|
||||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||
<h4 class="skill-name rollable">{{skill.label}}</h4>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
<span class="skill-passive">({{skill.passive}})</span>
|
||||
</li>
|
||||
{{#each config.skills as |label s|}}
|
||||
{{#with (lookup ../data.skills s) as |skill|}}
|
||||
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}" data-dtype="Number"/>
|
||||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||
<h4 class="skill-name rollable">{{label}}</h4>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
<span class="skill-passive">({{skill.passive}})</span>
|
||||
</li>
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
|
@ -166,16 +171,16 @@
|
|||
placeholder="{{res.placeholder}}" />
|
||||
</h4>
|
||||
<div class="attribute-value">
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationSR" }} <input name="data.resources.{{res.name}}.sr" type="checkbox" {{checked res.sr}}/>
|
||||
<label class="recharge checkbox flexcol">
|
||||
<span>{{ localize "SW5E.AbbreviationSR" }}</span> <input name="data.resources.{{res.name}}.sr" type="checkbox" {{checked res.sr}}/>
|
||||
</label>
|
||||
|
||||
<input name="data.resources.{{res.name}}.value" type="number" value="{{res.value}}" placeholder="0"/>
|
||||
<span class="sep"> / </span>
|
||||
<input name="data.resources.{{res.name}}.max" type="number" value="{{res.max}}" placeholder="0"/>
|
||||
|
||||
<label class="recharge checkbox">
|
||||
{{ localize "SW5E.AbbreviationLR" }} <input name="data.resources.{{res.name}}.lr" type="checkbox" {{checked res.lr}}/>
|
||||
<label class="recharge checkbox flexcol">
|
||||
<span>{{ localize "SW5E.AbbreviationLR" }}</span> <input name="data.resources.{{res.name}}.lr" type="checkbox" {{checked res.lr}}/>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -251,7 +256,7 @@
|
|||
<textarea name="data.details.flaw">{{data.details.flaw}}</textarea>
|
||||
</div>
|
||||
<div class="biography">
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{{!-- Sheet Body --}}
|
||||
<section class="sheet-body">
|
||||
<div class="tab biography">
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -27,8 +27,9 @@
|
|||
<li>
|
||||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}" placeholder="{{ localize 'SW5E.Alignment' }}"/>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.type" value="{{data.details.type}}" placeholder="{{ localize 'SW5E.Type' }}"/>
|
||||
<li class="creature-type">
|
||||
<span title="{{labels.type}}">{{labels.type}}</span>
|
||||
<a class="config-button" data-action="type" title="{{localize 'SW5E.CreatureTypeConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.source" value="{{data.details.source}}" placeholder="{{ localize 'SW5E.Source' }}"/>
|
||||
|
@ -108,15 +109,17 @@
|
|||
|
||||
{{!-- Skills --}}
|
||||
<ul class="skills-list">
|
||||
{{#each data.skills as |skill s|}}
|
||||
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}" data-dtype="Number"/>
|
||||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||
<h4 class="skill-name rollable">{{skill.label}}</h4>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
<span class="skill-passive">({{skill.passive}})</span>
|
||||
</li>
|
||||
{{#each config.skills as |label s|}}
|
||||
{{#with (lookup ../data.skills s) as |skill|}}
|
||||
<li class="skill flexrow {{#if skill.value}}proficient{{/if}}" data-skill="{{s}}">
|
||||
<input type="hidden" name="data.skills.{{s}}.value" value="{{skill.value}}" data-dtype="Number"/>
|
||||
<a class="proficiency-toggle skill-proficiency" title="{{skill.hover}}">{{{skill.icon}}}</a>
|
||||
<h4 class="skill-name rollable">{{label}}</h4>
|
||||
<span class="skill-ability">{{skill.ability}}</span>
|
||||
<span class="skill-mod">{{numberFormat skill.total decimals=0 sign=true}}</span>
|
||||
<span class="skill-passive">({{skill.passive}})</span>
|
||||
</li>
|
||||
{{/with}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
|
@ -172,7 +175,7 @@
|
|||
|
||||
{{!-- Biography Tab --}}
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable}}
|
||||
{{editor content=data.details.biography.value target="data.details.biography.value" button=true owner=owner editable=editable rollData=rollData}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -38,7 +38,10 @@
|
|||
<li class="item flexrow {{#if isDepleted}}depleted{{/if}}" data-item-id="{{item._id}}">
|
||||
<div class="item-name flexrow rollable">
|
||||
<div class="item-image" style="background-image: url('{{item.img}}')"></div>
|
||||
<h4>{{item.name}}</h4>
|
||||
<h4>
|
||||
{{item.name}}
|
||||
{{#if item.isOriginalClass}} <i class="original-class fas fa-sun" title="{{localize 'SW5E.ClassOriginal'}}"></i>{{/if}}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
{{#if section.hasActions}}
|
||||
|
@ -52,7 +55,6 @@
|
|||
<input type="text" value="{{item.data.uses.value}}" placeholder="0"/>/ {{item.data.uses.max}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="item-detail item-action">
|
||||
{{#if item.data.activation.type }}
|
||||
{{item.labels.activation}}
|
||||
|
@ -106,7 +108,7 @@
|
|||
{{#if section.columns}}
|
||||
{{#each section.columns}}
|
||||
<div class="item-detail {{css}}">
|
||||
{{#with (getProperty item property)}}
|
||||
{{#with (lookup item property)}}
|
||||
{{#if ../editable}}
|
||||
<input type="text" value="{{this}}" placeholder="—"
|
||||
data-dtype="{{../editable}}">
|
||||
|
|
|
@ -28,6 +28,4 @@
|
|||
placeholder="Additional Notes" />
|
||||
{{editor content=data.details.notes4.value target="data.details.notes4.value" button=true owner=owner editable=editable}}
|
||||
</section>
|
||||
|
||||
</section>
|
||||
<!-- </div> -->
|
|
@ -1,7 +1,7 @@
|
|||
<div class="inventory-filters powerbook-filters flexrow">
|
||||
<div class="form-group powercasting-ability">
|
||||
{{#unless isNPC}}
|
||||
<label>{{localize "SW5E.PowerAbility"}}</label>
|
||||
<label>{{localize "SW5E.Powercasting"}}</label>
|
||||
{{else}}
|
||||
<label>{{localize "SW5E.Level"}}</label>
|
||||
<input class="powercasting-level" type="text" name="data.details.powerLevel"
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||
button=true owner=owner editable=editable}}
|
||||
button=true owner=owner editable=editable rollData=rollData}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
<section class="form-body">
|
||||
<p class="notes">{{localize 'SW5E.FlagsInstructions'}}</p>
|
||||
|
||||
<h3 class="form-header">{{localize "SW5E.ItemTypeClass"}}</h3>
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.ClassMakeOriginal"}}</label>
|
||||
<select name="data.details.originalClass" data-dtype="String">
|
||||
{{selectOptions classes selected=actor.data.data.details.originalClass}}
|
||||
</select>
|
||||
<p class="notes">{{localize "SW5E.ClassMakeOriginalHint"}}</p>
|
||||
</div>
|
||||
|
||||
{{#each flags as |fs section|}}
|
||||
<h3 class="form-header">{{localize section}}</h3>
|
||||
{{#each fs as |flag key|}}
|
||||
|
|
38
templates/apps/actor-type.html
Normal file
38
templates/apps/actor-type.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
<form autocomplete="off">
|
||||
<input type="text" name="preview" value="{{preview}}" disabled>
|
||||
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.CreatureType" }}</label>
|
||||
<ul class="trait-list">
|
||||
{{#each types as |type key|}}
|
||||
<li>
|
||||
<label class="radio">
|
||||
<input type="radio" name="value" value="{{key}}" data-dtype="String" {{checked type.chosen}}/>
|
||||
{{type.label}}
|
||||
</label>
|
||||
</li>
|
||||
{{/each}}
|
||||
<li class="custom-type form-group">
|
||||
<input type="radio" name="value" value="custom" title="{{custom.label}}" {{checked custom.chosen}}/>
|
||||
<label>{{custom.label}}</label>
|
||||
<input type="text" name="custom" value="{{custom.value}}"/>
|
||||
</li>
|
||||
<li class="subtype form-group">
|
||||
<label>{{ localize "SW5E.CreatureTypeSelectorSubtype" }}</label>
|
||||
<input type="text" name="subtype" value="{{subtype}}"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="swarm-size form-group">
|
||||
<label>{{ localize "SW5E.CreatureSwarmSize" }}</label>
|
||||
<select name="swarm">
|
||||
{{selectOptions sizes selected=swarm blank="" localize=true}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="submit" value="1">
|
||||
<i class="far fa-save"></i> {{ localize "SW5E.TraitSave"}}
|
||||
</button>
|
||||
</form>
|
||||
|
23
templates/apps/hit-dice-config.html
Normal file
23
templates/apps/hit-dice-config.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<form autocomplete="off">
|
||||
<p class="notes">{{ localize "SW5E.HitDiceConfigHint" }}</p>
|
||||
{{#each classes}}
|
||||
<div class="form-group">
|
||||
<label>{{this.name}} ({{this.diceDenom}})</label>
|
||||
<div class="form-fields">
|
||||
<button class="decrement" type="button">-</button>
|
||||
<input title="{{ localize 'SW5E.HitDiceRemaining' }}" class="current" name="{{this.classItemId}}" type="number" value="{{this.currentHitDice}}" />
|
||||
<span class="sep">/</span>
|
||||
<input title="{{ localize 'SW5E.HitDiceMax' }}" class="max" type="number" value="{{this.maxHitDice}}" disabled />
|
||||
<button class="increment" type="button">+</button>
|
||||
|
||||
<button class="roll-hd" data-hd-denom="{{this.diceDenom}}" {{#unless this.canRoll}}disabled{{/unless}}>
|
||||
<i class="fas fa-dice-d20"></i> {{ localize "Roll" }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="dialog-buttons">
|
||||
<button type="reset" name="reset"><i class="fas fa-redo"></i> {{ localize "SETTINGS.5eUndoChanges" }}</button>
|
||||
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "SETTINGS.Save"}}</button>
|
||||
</div>
|
||||
</form>
|
18
templates/apps/select-items-promt.html
Normal file
18
templates/apps/select-items-promt.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<form>
|
||||
<p class="hint">{{hint}}</p>
|
||||
|
||||
<ul class="items-list">
|
||||
{{#each items}}
|
||||
<li class="item flexrow">
|
||||
<div class="item-name flexrow">
|
||||
<div class="item-image" style="background-image:url({{data.img}})" data-item-id="{{id}}"></div>
|
||||
|
||||
<label class="flexrow">
|
||||
<h4>{{data.name}}</h4>
|
||||
<input type="checkbox" checked name="{{id}}" />
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</form>
|
|
@ -1,4 +1,4 @@
|
|||
<div class="sw5e chat-card item-card" data-actor-id="{{actor._id}}" data-item-id="{{item._id}}"
|
||||
<div class="sw5e chat-card item-card" data-actor-id="{{actor.data._id}}" data-item-id="{{item._id}}"
|
||||
{{#if tokenId}}data-token-id="{{tokenId}}"{{/if}} {{#if isPower}}data-power-level="{{item.data.level}}"{{/if}}>
|
||||
<header class="card-header flexrow">
|
||||
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/>
|
||||
|
@ -39,6 +39,11 @@
|
|||
{{#if hasAreaTarget}}
|
||||
<button data-action="placeTemplate">{{ localize "SW5E.PlaceTemplate" }}</button>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if isTool}}
|
||||
<button data-action="toolCheck" data-ability="{{data.ability.value}}">{{ localize "SW5E.Use" }} {{item.name}}</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<footer class="card-footer">
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
<label>{{ localize "SW5E.Formula" }}</label>
|
||||
<input type="text" name="formula" value="{{formula}}" disabled/>
|
||||
</div>
|
||||
{{#if chooseModifier}}
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.AbilityModifier" }}</label>
|
||||
<select name="ability">
|
||||
{{selectOptions abilities selected=defaultAbility}}
|
||||
</select>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.RollSituationalBonus" }}</label>
|
||||
<input type="text" name="bonus" value="" placeholder="{{ localize 'SW5E.RollExample' }}"/>
|
||||
|
@ -10,11 +18,7 @@
|
|||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.RollMode" }}</label>
|
||||
<select name="rollMode">
|
||||
{{#select rollMode}}
|
||||
{{#each rollModes as |label mode|}}
|
||||
<option value="{{mode}}">{{localize label}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
{{selectOptions rollModes selected=defaultRollMode localize=true}}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
|
@ -1,18 +0,0 @@
|
|||
<div class="sw5e chat-card item-card" data-actor-id="{{actor._id}}" data-item-id="{{item._id}}" {{#if tokenId}}data-token-id="{{tokenId}}"{{/if}}>
|
||||
<header class="card-header flexrow">
|
||||
<img src="{{item.img}}" title="{{item.name}}" width="36" height="36"/>
|
||||
<h3 class="item-name">{{item.name}}</h3>
|
||||
</header>
|
||||
|
||||
<div class="card-content">{{{data.description.value}}}</div>
|
||||
|
||||
<div class="card-buttons">
|
||||
<button data-action="toolCheck" data-ability="{{data.ability.value}}">{{ localize "SW5E.Use" }} {{item.name}}</button>
|
||||
</div>
|
||||
|
||||
<footer class="card-footer">
|
||||
{{#each data.properties}}
|
||||
<span>{{this}}</span>
|
||||
{{/each}}
|
||||
</footer>
|
||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||
<form>
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.Formula" }}</label>
|
||||
<input type="text" name="formula" value="{{formula}}" disabled/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.AbilityModifier" }}</label>
|
||||
<select name="ability">
|
||||
{{#select data.item.ability}}
|
||||
{{#each config.abilities as |ability a|}}
|
||||
<option value="{{a}}">{{ability}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.RollSituationalBonus" }}</label>
|
||||
<input type="text" name="bonus" value="" placeholder="{{ localize 'SW5E.RollExample' }}"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.RollMode" }}</label>
|
||||
<select name="rollMode">
|
||||
{{#select rollMode}}
|
||||
{{#each rollModes as |label mode|}}
|
||||
<option value="{{mode}}">{{localize label}}</option>
|
||||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
|
@ -68,5 +68,6 @@
|
|||
{{localize 'SW5E.ItemContainerWeightless'}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -98,11 +98,11 @@
|
|||
|
||||
<div class="form-group" style="width: 60%;">
|
||||
<strong style="color:#4b4a44; font-size: 11px">{{ localize "SW5E.CapacityMultiplier" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.capx.value" value="{{data.capx.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.capx.value" value="{{data.capx.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.DmgRed" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.attributes.dr" value="{{data.attributes.dr}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.attributes.dr" value="{{data.attributes.dr}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.RegenerationRateCoefficient" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.regrateco.value" value="{{data.regrateco.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.regrateco.value" value="{{data.regrateco.value}}" />
|
||||
</div>
|
||||
|
||||
{{!-- Starship Equipment Properties --}}
|
||||
|
@ -112,15 +112,15 @@
|
|||
|
||||
<div class="form-group" style="width: 100%;">
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.CentStorageCapacity" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.cscap.value" value="{{data.cscap.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.cscap.value" value="{{data.cscap.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.SysStorageCapacity" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.sscap.value" value="{{data.sscap.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.sscap.value" value="{{data.sscap.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.FuelCostsMod" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.fuelcostsmod.value" value="{{data.fuelcostsmod.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.fuelcostsmod.value" value="{{data.fuelcostsmod.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.PowerDiceRecovery" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.powdicerec.value" value="{{data.powdicerec.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.powdicerec.value" value="{{data.powdicerec.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.HyperdriveClass" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.hdclass.value" value="{{data.hdclass.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.hdclass.value" value="{{data.hdclass.value}}" />
|
||||
</div>
|
||||
|
||||
{{!-- Armor Class --}}
|
||||
|
|
|
@ -103,11 +103,11 @@
|
|||
|
||||
<div class="form-group" style="width: 60%;">
|
||||
<strong style="color:#4b4a44; font-size: 11px">{{ localize "SW5E.CapacityMultiplier" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.capx.value" value="{{data.capx.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.capx.value" value="{{data.capx.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.DmgRed" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.attributes.dr" value="{{data.attributes.dr}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.attributes.dr" value="{{data.attributes.dr}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.RegenerationRateCoefficient" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.regrateco.value" value="{{data.regrateco.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.regrateco.value" value="{{data.regrateco.value}}" />
|
||||
</div>
|
||||
|
||||
{{!-- Starship Equipment Properties --}}
|
||||
|
@ -117,15 +117,15 @@
|
|||
|
||||
<div class="form-group" style="width: 100%;">
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.CentStorageCapacity" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.cscap.value" value="{{data.cscap.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.cscap.value" value="{{data.cscap.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.SysStorageCapacity" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.sscap.value" value="{{data.sscap.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.sscap.value" value="{{data.sscap.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.FuelCostsMod" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.fuelcostsmod.value" value="{{data.fuelcostsmod.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.fuelcostsmod.value" value="{{data.fuelcostsmod.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.PowerDiceRecovery" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.powdicerec.value" value="{{data.powdicerec.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.powdicerec.value" value="{{data.powdicerec.value}}" />
|
||||
<strong style="color:#4b4a44; font-size: 11px;">{{ localize "SW5E.HyperdriveClass" }}</strong>
|
||||
<input style="min-width: 5px; max-width: 35px; padding: none;" type="text" name="data.hdclass.value" value="{{data.hdclass.value}}" />
|
||||
<input style="min-width: 5px; max-width: 35px; padding: 0;" type="text" name="data.hdclass.value" value="{{data.hdclass.value}}" />
|
||||
</div>
|
||||
|
||||
{{!-- Armor Class --}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue