forked from GitHub-Mirrors/foundry-sw5e
Merge branch 'Develop'
This commit is contained in:
commit
1047d71e60
52 changed files with 3117 additions and 1552 deletions
26
lang/en.json
26
lang/en.json
|
@ -47,7 +47,7 @@
|
|||
"SW5E.AbilityUseHint": "Configure how you would like to use the {name} {type}.",
|
||||
"SW5E.AbilityUseUnavailableHint": "There are no uses of this item remaining!",
|
||||
"SW5E.AbilityUseChargedHint": "This {type} is charged and ready to use!",
|
||||
"SW5E.AbilityUseRechargedHint": "This {type} is depleted and must be recharged!",
|
||||
"SW5E.AbilityUseRechargeHint": "This {type} is depleted and must be recharged!",
|
||||
"SW5E.AbilityUseNormalHint": "This {type} has {value} of {max} uses per {per} remaining.",
|
||||
"SW5E.AbilityUseConsumableChargeHint": "Using this {type} will consume 1 charge of {value} remaining.",
|
||||
"SW5E.AbilityUseConsumableQuantityHint": "Using this {type} will consume 1 quantity of {quantity} remaining",
|
||||
|
@ -85,6 +85,11 @@
|
|||
"SW5E.AlignmentBN": "Balanced Neutral",
|
||||
"SW5E.Archetypes": "Archetypes",
|
||||
"SW5E.Appearance": "Appearance",
|
||||
"SW5E.Attunement": "Attunement",
|
||||
"SW5E.AttunementNone": "Attunement Not Required",
|
||||
"SW5E.AttunementRequired": "Attunement Required",
|
||||
"SW5E.AttunementAttuned": "Attuned",
|
||||
"SW5E.Attuned": "Attuned",
|
||||
"SW5E.ArmorClass": "Armor Class",
|
||||
"SW5E.AC": "AC",
|
||||
"SW5E.ArmorProperties": "Armor Properties",
|
||||
|
@ -122,7 +127,6 @@
|
|||
"SW5E.AttackPl": "Attacks",
|
||||
"SW5E.AttackRoll": "Attack Roll",
|
||||
"SW5E.Attributes": "Attributes",
|
||||
"SW5E.Attuned": "Attuned",
|
||||
"SW5E.Background": "Background",
|
||||
"SW5E.Biography": "Biography",
|
||||
"SW5E.Bonds": "Bonds",
|
||||
|
@ -189,7 +193,8 @@
|
|||
"SW5E.ConsumeWarningNoSource": "The designated {type} source that {name} consumes no longer exists!",
|
||||
"SW5E.ConsumeWarningNoQuantity": "{name} has run out of its designated {type}!",
|
||||
"SW5E.ConsumeWarningZeroAttribute": "{name} has run out of its designated attribute resource pool!",
|
||||
|
||||
"SW5E.ConsumeResource": "Consume Resource?",
|
||||
"SW5E.ConsumeRecharge": "Consume Recharge?",
|
||||
"SW5E.ConsumableAmmunition": "Ammunition",
|
||||
"SW5E.ConsumableFood": "Food",
|
||||
"SW5E.ConsumablePoison": "Poison",
|
||||
|
@ -390,8 +395,6 @@
|
|||
"SW5E.FlagsReliableTalentHint": "Rogues Reliable Talent Feature.",
|
||||
"SW5E.FlagsRemarkableAthlete": "Remarkable Athlete.",
|
||||
"SW5E.FlagsRemarkableAthleteHint": "Half-Proficiency (rounded-up) to physical Ability Checks and Initiative.",
|
||||
"SW5E.FlagsCritThreshold": "Critical Hit Threshold",
|
||||
"SW5E.FlagsCritThresholdHint": "Allow for expanded critical range; for example Improved or Superior Critical",
|
||||
"SW5E.FlagsWeaponCritThreshold": "Weapon Critical Hit Threshold",
|
||||
"SW5E.FlagsWeaponCritThresholdHint": "An expanded critical hit threshold for weapon attacks.",
|
||||
"SW5E.FlagsPowerCritThreshold": "Power Critical Hit Threshold",
|
||||
|
@ -649,7 +652,7 @@
|
|||
"SW5E.RollMode": "Roll Mode",
|
||||
"SW5E.RollSituationalBonus": "Situational Bonus?",
|
||||
"SW5E.Save": "Save",
|
||||
|
||||
"SW5E.Movement": "Movement",
|
||||
"SW5E.MovementConfig": "Configure Movement Speed",
|
||||
"SW5E.MovementConfigHint": "Configure the movement speed and special movement attributes of this creature.",
|
||||
"SW5E.MovementWalk": "Walk",
|
||||
|
@ -659,7 +662,14 @@
|
|||
"SW5E.MovementFly": "Fly",
|
||||
"SW5E.MovementSwim": "Swim",
|
||||
"SW5E.MovementUnits": "Units",
|
||||
|
||||
"SW5E.Senses": "Senses",
|
||||
"SW5E.SensesConfig": "Configure Senses",
|
||||
"SW5E.SensesConfigHint": "Configure any special sensory perception abilities that this actor possesses.",
|
||||
"SW5E.SenseDarkvision": "Darkvision",
|
||||
"SW5E.SenseBlindsight": "Blindsight",
|
||||
"SW5E.SenseTremorsense": "Tremorsense",
|
||||
"SW5E.SenseTruesight": "Truesight",
|
||||
"SW5E.SenseSpecial": "Special Senses",
|
||||
"SW5E.SheetClassCharacter": "Default Character Sheet",
|
||||
"SW5E.SheetClassCharacterOld": "Old Character Sheet",
|
||||
"SW5E.SheetClassNPC": "Default NPC Sheet",
|
||||
|
@ -728,7 +738,7 @@
|
|||
"SW5E.PowerAtWill": "At-Will",
|
||||
"SW5E.PowerCastConsume": "Consume Power Slot?",
|
||||
"SW5E.PowerCastHint": "Configure how you would like to cast the",
|
||||
"SW5E.PowerCastNoSlots": "You have no available power slots to cast this power",
|
||||
"SW5E.PowerCastNoSlots": "You have no available {level} power slots with which to cast {name}",
|
||||
"SW5E.PowerCastUpcast": "Cast at Level",
|
||||
"SW5E.PowercasterLevel": "Powercaster Level",
|
||||
"SW5E.PowerCastingHeader": "Power Casting",
|
||||
|
|
|
@ -69,12 +69,29 @@
|
|||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attributes {
|
||||
input.temphp {
|
||||
width: 48%;
|
||||
// Movement Configuration
|
||||
.movement {
|
||||
h4.attribute-name {
|
||||
position: relative;
|
||||
}
|
||||
.config-button {
|
||||
position: absolute;
|
||||
display: none;
|
||||
right: 0;
|
||||
top: 1px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
&:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary HP
|
||||
input.temphp {
|
||||
width: 48%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,7 +358,7 @@
|
|||
margin: 0 0 3px 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.configure-flags {
|
||||
.config-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
@ -428,12 +445,8 @@
|
|||
&.rollable .item-image:hover {
|
||||
background-image: url("../../icons/svg/d20-black.svg") !important;
|
||||
}
|
||||
i.attuned {
|
||||
color: @colorTan;
|
||||
}
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
i.attuned { color: @colorTan; }
|
||||
i.not-attuned { color: @colorCrimson; }
|
||||
}
|
||||
|
||||
// Item uses
|
||||
|
@ -473,7 +486,8 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
&:last-child { border-right: none; }
|
||||
&.item-action {flex: 0 0 100px}
|
||||
&.item-action {flex: 0 0 100px}
|
||||
&.attunement {flex: 0 0 24px}
|
||||
}
|
||||
.item-weight {
|
||||
flex: 0 0 60px;
|
||||
|
@ -576,26 +590,23 @@
|
|||
.powercasting-ability {
|
||||
flex: 0 0 240px;
|
||||
margin: 0;
|
||||
|
||||
input, span {
|
||||
flex: 0 0 32px;
|
||||
label, span {
|
||||
flex: none;
|
||||
}
|
||||
input {
|
||||
flex: 0 0 28px;
|
||||
text-align: center;
|
||||
}
|
||||
select {
|
||||
margin: 0 5px;
|
||||
flex: 0 0 150px;
|
||||
}
|
||||
h3.power-dc {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.power-slots,
|
||||
.power-comps {
|
||||
flex: 0 0 75px;
|
||||
padding-right: 5px;
|
||||
text-align: right;
|
||||
flex: none;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
color: @colorTan;
|
||||
border-right: 1px solid @colorFaint;
|
||||
|
@ -612,9 +623,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.power-uses {
|
||||
padding-right: 8px;
|
||||
text-align: right !important;
|
||||
.powerbook .power-uses {
|
||||
padding-right: 5px;
|
||||
text-align: right;
|
||||
color: @colorTan;
|
||||
}
|
||||
|
||||
.power-school, .power-action, .power-target {
|
||||
|
@ -663,5 +675,6 @@
|
|||
padding-right: 8px;
|
||||
margin-bottom: 4px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
}
|
|
@ -178,11 +178,14 @@
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
// Rollable Links
|
||||
// Rollable Titles
|
||||
.editable .rollable:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.editable h4.rollable:hover,
|
||||
.editable .rollable:hover > h4 {
|
||||
color: #000;
|
||||
text-shadow: 0 0 10px red;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Separators
|
||||
|
@ -306,6 +309,7 @@
|
|||
/* ----------------------------------------- */
|
||||
|
||||
.filter-list {
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -382,6 +386,30 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
// Item Name
|
||||
.item-name {
|
||||
flex: 2;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
h3, h4 {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// Control Buttons
|
||||
.item-controls {
|
||||
flex: 0 0 60px;
|
||||
justify-content: space-between;
|
||||
a {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
// Individual Item
|
||||
.item {
|
||||
align-items: center;
|
||||
|
@ -419,32 +447,13 @@
|
|||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.item-name {
|
||||
h3 {
|
||||
padding-left: 5px;
|
||||
//.modesto();
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// Item Name
|
||||
.item-name {
|
||||
flex: 2;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Control Buttons
|
||||
.item-controls {
|
||||
flex: 0 0 60px;
|
||||
justify-content: space-between;
|
||||
a {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------- */
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
|
||||
span {
|
||||
border-right: 2px groove #FFF;
|
||||
padding: 0 5px 0 0;
|
||||
padding: 0 3px 0 0;
|
||||
font-size: 10px;
|
||||
|
||||
&:last-child {
|
||||
|
|
|
@ -31,11 +31,4 @@
|
|||
.summary {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.powercasting-ability {
|
||||
label {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6,33 +6,3 @@
|
|||
@import "character.less";
|
||||
@import "npc.less";
|
||||
@import "vehicle.less";
|
||||
|
||||
// TODO: Remove number styling after 0.7.x
|
||||
input[type="number"] {
|
||||
width: calc(100% - 2px);
|
||||
min-width: 20px;
|
||||
height: 26px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 1px 3px;
|
||||
margin: 0;
|
||||
color: #191813;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
text-align: inherit;
|
||||
line-height: inherit;
|
||||
border: 1px solid #7a7971;
|
||||
border-radius: 3px;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
-moz-appearance: textfield;
|
||||
&:focus {
|
||||
box-shadow: 0 0 5px red;
|
||||
}
|
||||
}
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
|
@ -77,6 +77,9 @@ html {
|
|||
|
||||
body {
|
||||
.openSans(13px, 400);
|
||||
background-image: url('./ui/SW5e-logo.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { d20Roll, damageRoll } from "../dice.js";
|
||||
import ShortRestDialog from "../apps/short-rest.js";
|
||||
import LongRestDialog from "../apps/long-rest.js";
|
||||
import AbilityUseDialog from "../apps/ability-use-dialog.js";
|
||||
import AbilityTemplate from "../pixi/ability-template.js";
|
||||
import {SW5E} from '../config.js';
|
||||
|
||||
/**
|
||||
|
@ -163,8 +161,8 @@ export default class Actor5e extends Actor {
|
|||
}
|
||||
|
||||
// Acquire archetype features
|
||||
const subConfig = clsConfig.archetypes[archetypeName] || {};
|
||||
for ( let [l, f] of Object.entries(subConfig.features || {}) ) {
|
||||
const archConfig = clsConfig.archetypes[archetypeName] || {};
|
||||
for ( let [l, f] of Object.entries(archConfig.features || {}) ) {
|
||||
l = parseInt(l);
|
||||
if ( (l <= level) && (l > priorLevel) ) ids = ids.concat(f);
|
||||
}
|
||||
|
@ -207,7 +205,7 @@ export default class Actor5e extends Actor {
|
|||
const updateData = expandObject(u);
|
||||
const config = {
|
||||
className: updateData.name || item.data.name,
|
||||
archetypeName: updateData.data.archetype || item.data.data.archetype,
|
||||
archetypeName: getProperty(updateData, "data.archetype") || item.data.data.archetype,
|
||||
level: getProperty(updateData, "data.levels"),
|
||||
priorLevel: item ? item.data.data.levels : 0
|
||||
}
|
||||
|
@ -356,16 +354,8 @@ export default class Actor5e extends Actor {
|
|||
const data = actorData.data;
|
||||
data.attributes.powerdc = data.attributes.powercasting ? data.abilities[data.attributes.powercasting].dc : 10;
|
||||
|
||||
// Apply powercasting DC to any power items which use it
|
||||
for ( let i of this.items ) {
|
||||
const save = i.data.data.save;
|
||||
if ( save?.ability ) {
|
||||
if ( save.scaling === "power" ) save.dc = data.attributes.powerdc;
|
||||
else if ( save.scaling !== "flat" ) save.dc = data.abilities[save.scaling]?.dc ?? 10;
|
||||
const ability = CONFIG.SW5E.abilities[save.ability];
|
||||
i.labels.save = game.i18n.format("SW5E.SaveDC", {dc: save.dc || "", ability});
|
||||
}
|
||||
}
|
||||
// Compute ability save DCs that depend on the calling actor
|
||||
this.items.forEach(i => i.getSaveDC());
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -546,20 +536,69 @@ export default class Actor5e extends Actor {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async createOwnedItem(itemData, options) {
|
||||
async createEmbeddedEntity(embeddedName, itemData, options={}) {
|
||||
|
||||
// Assume NPCs are always proficient with weapons and always have powers prepared
|
||||
if ( !this.hasPlayerOwner ) {
|
||||
let t = itemData.type;
|
||||
let initial = {};
|
||||
if ( t === "weapon" ) initial["data.proficient"] = true;
|
||||
if ( ["weapon", "equipment"].includes(t) ) initial["data.equipped"] = true;
|
||||
if ( t === "power" ) initial["data.prepared"] = true;
|
||||
mergeObject(itemData, initial);
|
||||
}
|
||||
return super.createOwnedItem(itemData, options);
|
||||
// Pre-creation steps for owned items
|
||||
if ( embeddedName === "OwnedItem" ) this._preCreateOwnedItem(itemData, options);
|
||||
|
||||
// Standard embedded entity creation
|
||||
return super.createEmbeddedEntity(embeddedName, itemData, options);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A temporary shim function which will eventually (in core fvtt version 0.8.0+) be migrated to the new abstraction layer
|
||||
* @param itemData
|
||||
* @param options
|
||||
* @private
|
||||
*/
|
||||
_preCreateOwnedItem(itemData, options) {
|
||||
if ( this.data.type === "vehicle" ) return;
|
||||
const isNPC = this.data.type === 'npc';
|
||||
let initial = {};
|
||||
switch ( itemData.type ) {
|
||||
case "weapon":
|
||||
initial["data.equipped"] = isNPC; // NPCs automatically equip weapons
|
||||
let hasWeaponProf = isNPC; // NPCs automatically have weapon proficiency
|
||||
if ( !isNPC ) {
|
||||
const weaponProf = {
|
||||
"natural": true,
|
||||
"simpleVW": "sim",
|
||||
"simpleB": "sim",
|
||||
"simpleLW": "sim",
|
||||
"martialVW": "mar",
|
||||
"martialB": "mar",
|
||||
"martialLW": "mar"
|
||||
}[itemData.data?.weaponType];
|
||||
const actorWeaponProfs = this.data.data.traits?.weaponProf?.value || [];
|
||||
hasWeaponProf = (weaponProf === true) || actorWeaponProfs.includes(weaponProf);
|
||||
}
|
||||
initial["data.proficient"] = hasWeaponProf;
|
||||
break;
|
||||
case "equipment":
|
||||
initial["data.equipped"] = isNPC; // NPCs automatically equip equipment
|
||||
let hasEquipmentProf = isNPC; // NPCs automatically have equipment proficiency
|
||||
if ( !isNPC ) {
|
||||
const armorProf = {
|
||||
"natural": true,
|
||||
"clothing": true,
|
||||
"light": "lgt",
|
||||
"medium": "med",
|
||||
"heavy": "hvy",
|
||||
"shield": "shl"
|
||||
}[itemData.data?.armor?.type];
|
||||
const actorArmorProfs = this.data.data.traits?.armorProf?.value || [];
|
||||
hasEquipmentProf = (armorProf === true) || actorArmorProfs.includes(armorProf);
|
||||
}
|
||||
initial["data.proficient"] = hasEquipmentProf;
|
||||
break;
|
||||
case "power":
|
||||
initial["data.prepared"] = true; // NPCs automatically prepare powers
|
||||
break;
|
||||
}
|
||||
mergeObject(itemData, initial);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Gameplay Mechanics */
|
||||
|
@ -600,77 +639,16 @@ export default class Actor5e extends Actor {
|
|||
"data.attributes.hp.temp": tmp - dt,
|
||||
"data.attributes.hp.value": dh
|
||||
};
|
||||
return this.update(updates);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Cast a Power, consuming a power slot of a certain level
|
||||
* @param {Item5e} item The power being cast by the actor
|
||||
* @param {Event} event The originating user interaction which triggered the cast
|
||||
*/
|
||||
async usePower(item, {configureDialog=true}={}) {
|
||||
if ( item.data.type !== "power" ) throw new Error("Wrong Item type");
|
||||
const itemData = item.data.data;
|
||||
|
||||
// Configure powercasting data
|
||||
let lvl = itemData.level;
|
||||
const usesSlots = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||
const limitedUses = !!itemData.uses.per;
|
||||
let consumeSlot = `power${lvl}`;
|
||||
let consumeUse = false;
|
||||
let placeTemplate = false;
|
||||
|
||||
// Configure power slot consumption and measured template placement from the form
|
||||
if ( configureDialog && (usesSlots || item.hasAreaTarget || limitedUses) ) {
|
||||
const usage = await AbilityUseDialog.create(item);
|
||||
if ( usage === null ) return;
|
||||
|
||||
// Determine consumption preferences
|
||||
consumeSlot = Boolean(usage.get("consumeSlot"));
|
||||
consumeUse = Boolean(usage.get("consumeUse"));
|
||||
placeTemplate = Boolean(usage.get("placeTemplate"));
|
||||
|
||||
// Determine the cast power level
|
||||
const isPact = usage.get('level') === 'pact';
|
||||
const lvl = isPact ? this.data.data.powers.pact.level : parseInt(usage.get("level"));
|
||||
if ( lvl !== item.data.data.level ) {
|
||||
const upcastData = mergeObject(item.data, {"data.level": lvl}, {inplace: false});
|
||||
item = item.constructor.createOwned(upcastData, this);
|
||||
}
|
||||
|
||||
// Denote the power slot being consumed
|
||||
if ( consumeSlot ) consumeSlot = isPact ? "pact" : `power${lvl}`;
|
||||
}
|
||||
|
||||
// Update Actor data
|
||||
if ( usesSlots && consumeSlot && (lvl > 0) ) {
|
||||
const slots = parseInt(this.data.data.powers[consumeSlot]?.value);
|
||||
if ( slots === 0 || Number.isNaN(slots) ) {
|
||||
return ui.notifications.error(game.i18n.localize("SW5E.PowerCastNoSlots"));
|
||||
}
|
||||
await this.update({
|
||||
[`data.powers.${consumeSlot}.value`]: Math.max(slots - 1, 0)
|
||||
});
|
||||
}
|
||||
|
||||
// Update Item data
|
||||
if ( limitedUses && consumeUse ) {
|
||||
const uses = parseInt(itemData.uses.value || 0);
|
||||
if ( uses <= 0 ) ui.notifications.warn(game.i18n.format("SW5E.ItemNoUses", {name: item.name}));
|
||||
await item.update({"data.uses.value": Math.max(parseInt(item.data.data.uses.value || 0) - 1, 0)})
|
||||
}
|
||||
|
||||
// Initiate ability template placement workflow if selected
|
||||
if ( placeTemplate && item.hasAreaTarget ) {
|
||||
const template = AbilityTemplate.fromItem(item);
|
||||
if ( template ) template.drawPreview();
|
||||
if ( this.sheet.rendered ) this.sheet.minimize();
|
||||
}
|
||||
|
||||
// Invoke the Item roll
|
||||
return item.roll();
|
||||
// Delegate damage application to a hook
|
||||
// TODO replace this in the future with a better modifyTokenAttribute function in the core
|
||||
const allowed = Hooks.call("modifyTokenAttribute", {
|
||||
attribute: "attributes.hp",
|
||||
value: amount,
|
||||
isDelta: false,
|
||||
isBar: true
|
||||
}, updates);
|
||||
return allowed !== false ? this.update(updates) : this;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -989,7 +967,7 @@ export default class Actor5e extends Actor {
|
|||
// Adjust actor data
|
||||
await cls.update({"data.hitDiceUsed": cls.data.data.hitDiceUsed + 1});
|
||||
const hp = this.data.data.attributes.hp;
|
||||
const dhp = Math.min(hp.max - hp.value, roll.total);
|
||||
const dhp = Math.min(hp.max + (hp.tempmax ?? 0) - hp.value, roll.total);
|
||||
await this.update({"data.attributes.hp.value": hp.value + dhp});
|
||||
return roll;
|
||||
}
|
||||
|
@ -1437,4 +1415,18 @@ export default class Actor5e extends Actor {
|
|||
console.warn(`The Actor5e#getPowerDC(ability) method has been deprecated in favor of Actor5e#data.data.abilities[ability].dc`);
|
||||
return this.data.data.abilities[ability]?.dc;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Cast a Power, consuming a power slot of a certain level
|
||||
* @param {Item5e} item The power being cast by the actor
|
||||
* @param {Event} event The originating user interaction which triggered the cast
|
||||
* @deprecated since sw5e 1.2.0
|
||||
*/
|
||||
async usePower(item, {configureDialog=true}={}) {
|
||||
console.warn(`The Actor5e#usePower method has been deprecated in favor of Item5e#roll`);
|
||||
if ( item.data.type !== "power" ) throw new Error("Wrong Item type");
|
||||
return item.roll();
|
||||
}
|
||||
}
|
||||
|
|
833
module/actor/sheets/newSheet/base.js
Normal file
833
module/actor/sheets/newSheet/base.js
Normal file
|
@ -0,0 +1,833 @@
|
|||
import Item5e from "../../../item/entity.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import {SW5E} from '../../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
|
||||
/**
|
||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||
* This sheet is an Abstract layer which is not used.
|
||||
* @extends {ActorSheet}
|
||||
*/
|
||||
export default class ActorSheet5e extends ActorSheet {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
/**
|
||||
* Track the set of item filters which are applied
|
||||
* @type {Set}
|
||||
*/
|
||||
this._filters = {
|
||||
inventory: new Set(),
|
||||
powerbook: new Set(),
|
||||
features: new Set(),
|
||||
effects: new Set()
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
scrollY: [
|
||||
".inventory .inventory-list",
|
||||
".features .inventory-list",
|
||||
".powerbook .inventory-list",
|
||||
".effects .inventory-list"
|
||||
],
|
||||
tabs: [{navSelector: ".tabs", contentSelector: ".sheet-body", initial: "description"}]
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get template() {
|
||||
if ( !game.user.isGM && this.actor.limited ) return "systems/sw5e/templates/actors/newActor/expanded-limited-sheet.html";
|
||||
return `systems/sw5e/templates/actors/newActor/${this.actor.data.type}-sheet.html`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData() {
|
||||
|
||||
// Basic data
|
||||
let isOwner = this.entity.owner;
|
||||
const data = {
|
||||
owner: isOwner,
|
||||
limited: this.entity.limited,
|
||||
options: this.options,
|
||||
editable: this.isEditable,
|
||||
cssClass: isOwner ? "editable" : "locked",
|
||||
isCharacter: this.entity.data.type === "character",
|
||||
isNPC: this.entity.data.type === "npc",
|
||||
isVehicle: this.entity.data.type === 'vehicle',
|
||||
config: CONFIG.SW5E,
|
||||
};
|
||||
|
||||
// The Actor and its Items
|
||||
data.actor = duplicate(this.actor.data);
|
||||
data.items = this.actor.items.map(i => {
|
||||
i.data.labels = i.labels;
|
||||
return i.data;
|
||||
});
|
||||
data.items.sort((a, b) => (a.sort || 0) - (b.sort || 0));
|
||||
data.data = data.actor.data;
|
||||
data.labels = this.actor.labels || {};
|
||||
data.filters = this._filters;
|
||||
|
||||
// Ability Scores
|
||||
for ( let [a, abl] of Object.entries(data.actor.data.abilities)) {
|
||||
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||
abl.label = CONFIG.SW5E.abilities[a];
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (data.actor.data.skills) {
|
||||
for ( let [s, skl] of Object.entries(data.actor.data.skills)) {
|
||||
skl.ability = CONFIG.SW5E.abilityAbbreviations[skl.ability];
|
||||
skl.icon = this._getProficiencyIcon(skl.value);
|
||||
skl.hover = CONFIG.SW5E.proficiencyLevels[skl.value];
|
||||
skl.label = CONFIG.SW5E.skills[s];
|
||||
}
|
||||
}
|
||||
|
||||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
|
||||
// Senses
|
||||
data.senses = this._getSenses(data.actor);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
|
||||
// Prepare owned items
|
||||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
data.effects = prepareActiveEffectCategories(this.entity.effects);
|
||||
|
||||
// Return data to the sheet
|
||||
return data
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare the display of movement speed data for the Actor
|
||||
* @param {object} actorData
|
||||
* @returns {{primary: string, special: string}}
|
||||
* @private
|
||||
*/
|
||||
_getMovementSpeed(actorData) {
|
||||
const movement = actorData.data.attributes.movement || {};
|
||||
const speeds = [
|
||||
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
|
||||
[movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`],
|
||||
[movement.fly, `${game.i18n.localize("SW5E.MovementFly")} ${movement.fly}` + (movement.hover ? ` (${game.i18n.localize("SW5E.MovementHover")})` : "")],
|
||||
[movement.swim, `${game.i18n.localize("SW5E.MovementSwim")} ${movement.swim}`]
|
||||
].filter(s => !!s[0]).sort((a, b) => b[0] - a[0]);
|
||||
return {
|
||||
primary: `${movement.walk || 0} ${movement.units}`,
|
||||
special: speeds.length ? speeds.map(s => s[1]).join(", ") : ""
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_getSenses(actorData) {
|
||||
const senses = actorData.data.attributes.senses || {};
|
||||
const tags = {};
|
||||
for ( let [k, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||
const v = senses[k] ?? 0
|
||||
if ( v === 0 ) continue;
|
||||
tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`;
|
||||
}
|
||||
if ( !!senses.special ) tags["special"] = senses.special;
|
||||
return tags;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
||||
* @param {object} traits The raw traits data object from the actor data
|
||||
* @private
|
||||
*/
|
||||
_prepareTraits(traits) {
|
||||
const map = {
|
||||
"dr": CONFIG.SW5E.damageResistanceTypes,
|
||||
"di": CONFIG.SW5E.damageResistanceTypes,
|
||||
"dv": CONFIG.SW5E.damageResistanceTypes,
|
||||
"ci": CONFIG.SW5E.conditionTypes,
|
||||
"languages": CONFIG.SW5E.languages,
|
||||
"armorProf": CONFIG.SW5E.armorProficiencies,
|
||||
"weaponProf": CONFIG.SW5E.weaponProficiencies,
|
||||
"toolProf": CONFIG.SW5E.toolProficiencies
|
||||
};
|
||||
for ( let [t, choices] of Object.entries(map) ) {
|
||||
const trait = traits[t];
|
||||
if ( !trait ) continue;
|
||||
let values = [];
|
||||
if ( trait.value ) {
|
||||
values = trait.value instanceof Array ? trait.value : [trait.value];
|
||||
}
|
||||
trait.selected = values.reduce((obj, t) => {
|
||||
obj[t] = choices[t];
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
// Add custom entry
|
||||
if ( trait.custom ) {
|
||||
trait.custom.split(";").forEach((c, i) => trait.selected[`custom${i+1}`] = c.trim());
|
||||
}
|
||||
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Insert a power into the powerbook object when rendering the character sheet
|
||||
* @param {Object} data The Actor data being prepared
|
||||
* @param {Array} powers The power data being prepared
|
||||
* @private
|
||||
*/
|
||||
_preparePowerbook(data, powers) {
|
||||
const owner = this.actor.owner;
|
||||
const levels = data.data.powers;
|
||||
const powerbook = {};
|
||||
|
||||
// Define some mappings
|
||||
const sections = {
|
||||
"atwill": -20,
|
||||
"innate": -10,
|
||||
"pact": 0.5
|
||||
};
|
||||
|
||||
// Label power slot uses headers
|
||||
const useLabels = {
|
||||
"-20": "-",
|
||||
"-10": "-",
|
||||
"0": "∞"
|
||||
};
|
||||
|
||||
// Format a powerbook entry for a certain indexed level
|
||||
const registerSection = (sl, i, label, {prepMode="prepared", value, max, override}={}) => {
|
||||
powerbook[i] = {
|
||||
order: i,
|
||||
label: label,
|
||||
usesSlots: i > 0,
|
||||
canCreate: owner,
|
||||
canPrepare: (data.actor.type === "character") && (i >= 1),
|
||||
powers: [],
|
||||
uses: useLabels[i] || value || 0,
|
||||
slots: useLabels[i] || max || 0,
|
||||
override: override || 0,
|
||||
dataset: {"type": "power", "level": prepMode in sections ? 1 : i, "preparation.mode": prepMode},
|
||||
prop: sl
|
||||
};
|
||||
};
|
||||
|
||||
// Determine the maximum power level which has a slot
|
||||
const maxLevel = Array.fromRange(10).reduce((max, i) => {
|
||||
if ( i === 0 ) return max;
|
||||
const level = levels[`power${i}`];
|
||||
if ( (level.max || level.override ) && ( i > max ) ) max = i;
|
||||
return max;
|
||||
}, 0);
|
||||
|
||||
// Level-based powercasters have cantrips and leveled slots
|
||||
if ( maxLevel > 0 ) {
|
||||
registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
|
||||
for (let lvl = 1; lvl <= maxLevel; lvl++) {
|
||||
const sl = `power${lvl}`;
|
||||
registerSection(sl, lvl, CONFIG.SW5E.powerLevels[lvl], levels[sl]);
|
||||
}
|
||||
}
|
||||
|
||||
// Pact magic users have cantrips and a pact magic section
|
||||
if ( levels.pact && levels.pact.max ) {
|
||||
if ( !powerbook["0"] ) registerSection("power0", 0, CONFIG.SW5E.powerLevels[0]);
|
||||
const l = levels.pact;
|
||||
const config = CONFIG.SW5E.powerPreparationModes.pact;
|
||||
registerSection("pact", sections.pact, config, {
|
||||
prepMode: "pact",
|
||||
value: l.value,
|
||||
max: l.max,
|
||||
override: l.override
|
||||
});
|
||||
}
|
||||
|
||||
// Iterate over every power item, adding powers to the powerbook by section
|
||||
powers.forEach(power => {
|
||||
const mode = power.data.preparation.mode || "prepared";
|
||||
let s = power.data.level || 0;
|
||||
const sl = `power${s}`;
|
||||
|
||||
// Specialized powercasting modes (if they exist)
|
||||
if ( mode in sections ) {
|
||||
s = sections[mode];
|
||||
if ( !powerbook[s] ){
|
||||
const l = levels[mode] || {};
|
||||
const config = CONFIG.SW5E.powerPreparationModes[mode];
|
||||
registerSection(mode, s, config, {
|
||||
prepMode: mode,
|
||||
value: l.value,
|
||||
max: l.max,
|
||||
override: l.override
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sections for higher-level powers which the caster "should not" have, but power items exist for
|
||||
else if ( !powerbook[s] ) {
|
||||
registerSection(sl, s, CONFIG.SW5E.powerLevels[s], {levels: levels[sl]});
|
||||
}
|
||||
|
||||
// Add the power to the relevant heading
|
||||
powerbook[s].powers.push(power);
|
||||
});
|
||||
|
||||
// Sort the powerbook by section level
|
||||
const sorted = Object.values(powerbook);
|
||||
sorted.sort((a, b) => a.order - b.order);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Determine whether an Owned Item will be shown based on the current set of filters
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
_filterItems(items, filters) {
|
||||
return items.filter(item => {
|
||||
const data = item.data;
|
||||
|
||||
// Action usage
|
||||
for ( let f of ["action", "bonus", "reaction"] ) {
|
||||
if ( filters.has(f) ) {
|
||||
if ((data.activation && (data.activation.type !== f))) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Power-specific filters
|
||||
if ( filters.has("ritual") ) {
|
||||
if (data.components.ritual !== true) return false;
|
||||
}
|
||||
if ( filters.has("concentration") ) {
|
||||
if (data.components.concentration !== true) return false;
|
||||
}
|
||||
if ( filters.has("prepared") ) {
|
||||
if ( data.level === 0 || ["innate", "always"].includes(data.preparation.mode) ) return true;
|
||||
if ( this.actor.data.type === "npc" ) return true;
|
||||
return data.preparation.prepared;
|
||||
}
|
||||
|
||||
// Equipment-specific filters
|
||||
if ( filters.has("equipped") ) {
|
||||
if ( data.equipped !== true ) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get the font-awesome icon used to display a certain level of skill proficiency
|
||||
* @private
|
||||
*/
|
||||
_getProficiencyIcon(level) {
|
||||
const icons = {
|
||||
0: '<i class="far fa-circle"></i>',
|
||||
0.5: '<i class="fas fa-adjust"></i>',
|
||||
1: '<i class="fas fa-check"></i>',
|
||||
2: '<i class="fas fa-check-double"></i>'
|
||||
};
|
||||
return icons[level];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Activate event listeners using the prepared sheet HTML
|
||||
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
|
||||
*/
|
||||
activateListeners(html) {
|
||||
|
||||
// Activate Item Filters
|
||||
const filterLists = html.find(".filter-list");
|
||||
filterLists.each(this._initializeFilterItemList.bind(this));
|
||||
filterLists.on("click", ".filter-item", this._onToggleFilter.bind(this));
|
||||
|
||||
// Item summaries
|
||||
html.find('.item .item-name.rollable h4').click(event => this._onItemSummary(event));
|
||||
|
||||
// Editable Only Listeners
|
||||
if ( this.isEditable ) {
|
||||
|
||||
// Input focus and update
|
||||
const inputs = html.find("input");
|
||||
inputs.focus(ev => ev.currentTarget.select());
|
||||
inputs.addBack().find('[data-dtype="Number"]').change(this._onChangeInputDelta.bind(this));
|
||||
|
||||
// Ability Proficiency
|
||||
html.find('.ability-proficiency').click(this._onToggleAbilityProficiency.bind(this));
|
||||
|
||||
// Toggle Skill Proficiency
|
||||
html.find('.skill-proficiency').on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
||||
|
||||
// Trait Selector
|
||||
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
|
||||
|
||||
// Configure Special Flags
|
||||
html.find('.config-button').click(this._onConfigMenu.bind(this));
|
||||
|
||||
// Owned Item management
|
||||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
||||
html.find('.item-edit').click(this._onItemEdit.bind(this));
|
||||
html.find('.item-delete').click(this._onItemDelete.bind(this));
|
||||
html.find('.item-uses input').click(ev => ev.target.select()).change(this._onUsesChange.bind(this));
|
||||
html.find('.slot-max-override').click(this._onPowerSlotOverride.bind(this));
|
||||
|
||||
// Active Effect management
|
||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.entity));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
if ( this.actor.owner ) {
|
||||
|
||||
// Ability Checks
|
||||
html.find('.ability-name').click(this._onRollAbilityTest.bind(this));
|
||||
|
||||
|
||||
// Roll Skill Checks
|
||||
html.find('.skill-name').click(this._onRollSkillCheck.bind(this));
|
||||
|
||||
// Item Rolling
|
||||
html.find('.item .item-image').click(event => this._onItemRoll(event));
|
||||
html.find('.item .item-recharge').click(event => this._onItemRecharge(event));
|
||||
}
|
||||
|
||||
// Otherwise remove rollable classes
|
||||
else {
|
||||
html.find(".rollable").each((i, el) => el.classList.remove("rollable"));
|
||||
}
|
||||
|
||||
// Handle default listeners last so system listeners are triggered first
|
||||
super.activateListeners(html);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Iinitialize Item list filters by activating the set of filters which are currently applied
|
||||
* @private
|
||||
*/
|
||||
_initializeFilterItemList(i, ul) {
|
||||
const set = this._filters[ul.dataset.filter];
|
||||
const filters = ul.querySelectorAll(".filter-item");
|
||||
for ( let li of filters ) {
|
||||
if ( set.has(li.dataset.filter) ) li.classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle input changes to numeric form fields, allowing them to accept delta-typed inputs
|
||||
* @param event
|
||||
* @private
|
||||
*/
|
||||
_onChangeInputDelta(event) {
|
||||
const input = event.target;
|
||||
const value = input.value;
|
||||
if ( ["+", "-"].includes(value[0]) ) {
|
||||
let delta = parseFloat(value);
|
||||
input.value = getProperty(this.actor.data, input.name) + delta;
|
||||
} else if ( value[0] === "=" ) {
|
||||
input.value = value.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onConfigMenu(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget;
|
||||
switch ( button.dataset.action ) {
|
||||
case "movement":
|
||||
new ActorMovementConfig(this.object).render(true);
|
||||
break;
|
||||
case "flags":
|
||||
new ActorSheetFlags(this.object).render(true);
|
||||
break;
|
||||
case "senses":
|
||||
new ActorSensesConfig(this.object).render(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle cycling proficiency in a Skill
|
||||
* @param {Event} event A click or contextmenu event which triggered the handler
|
||||
* @private
|
||||
*/
|
||||
_onCycleSkillProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = $(event.currentTarget).siblings('input[type="hidden"]');
|
||||
|
||||
// Get the current level and the array of levels
|
||||
const level = parseFloat(field.val());
|
||||
const levels = [0, 1, 0.5, 2];
|
||||
let idx = levels.indexOf(level);
|
||||
|
||||
// Toggle next level - forward on click, backwards on right
|
||||
if ( event.type === "click" ) {
|
||||
field.val(levels[(idx === levels.length - 1) ? 0 : idx + 1]);
|
||||
} else if ( event.type === "contextmenu" ) {
|
||||
field.val(levels[(idx === 0) ? levels.length - 1 : idx - 1]);
|
||||
}
|
||||
|
||||
// Update the field value and save the form
|
||||
this._onSubmit(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onDropActor(event, data) {
|
||||
const canPolymorph = game.user.isGM || (this.actor.owner && game.settings.get('sw5e', 'allowPolymorphing'));
|
||||
if ( !canPolymorph ) return false;
|
||||
|
||||
// Get the target actor
|
||||
let sourceActor = null;
|
||||
if (data.pack) {
|
||||
const pack = game.packs.find(p => p.collection === data.pack);
|
||||
sourceActor = await pack.getEntity(data.id);
|
||||
} else {
|
||||
sourceActor = game.actors.get(data.id);
|
||||
}
|
||||
if ( !sourceActor ) return;
|
||||
|
||||
// Define a function to record polymorph settings for future use
|
||||
const rememberOptions = html => {
|
||||
const options = {};
|
||||
html.find('input').each((i, el) => {
|
||||
options[el.name] = el.checked;
|
||||
});
|
||||
const settings = mergeObject(game.settings.get('sw5e', 'polymorphSettings') || {}, options);
|
||||
game.settings.set('sw5e', 'polymorphSettings', settings);
|
||||
return settings;
|
||||
};
|
||||
|
||||
// Create and render the Dialog
|
||||
return new Dialog({
|
||||
title: game.i18n.localize('SW5E.PolymorphPromptTitle'),
|
||||
content: {
|
||||
options: game.settings.get('sw5e', 'polymorphSettings'),
|
||||
i18n: SW5E.polymorphSettings,
|
||||
isToken: this.actor.isToken
|
||||
},
|
||||
default: 'accept',
|
||||
buttons: {
|
||||
accept: {
|
||||
icon: '<i class="fas fa-check"></i>',
|
||||
label: game.i18n.localize('SW5E.PolymorphAcceptSettings'),
|
||||
callback: html => this.actor.transformInto(sourceActor, rememberOptions(html))
|
||||
},
|
||||
wildshape: {
|
||||
icon: '<i class="fas fa-paw"></i>',
|
||||
label: game.i18n.localize('SW5E.PolymorphWildShape'),
|
||||
callback: html => this.actor.transformInto(sourceActor, {
|
||||
keepBio: true,
|
||||
keepClass: true,
|
||||
keepMental: true,
|
||||
mergeSaves: true,
|
||||
mergeSkills: true,
|
||||
transformTokens: rememberOptions(html).transformTokens
|
||||
})
|
||||
},
|
||||
polymorph: {
|
||||
icon: '<i class="fas fa-pastafarianism"></i>',
|
||||
label: game.i18n.localize('SW5E.Polymorph'),
|
||||
callback: html => this.actor.transformInto(sourceActor, {
|
||||
transformTokens: rememberOptions(html).transformTokens
|
||||
})
|
||||
},
|
||||
cancel: {
|
||||
icon: '<i class="fas fa-times"></i>',
|
||||
label: game.i18n.localize('Cancel')
|
||||
}
|
||||
}
|
||||
}, {
|
||||
classes: ['dialog', 'sw5e'],
|
||||
width: 600,
|
||||
template: 'systems/sw5e/templates/apps/polymorph-prompt.html'
|
||||
}).render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
|
||||
// Create a Consumable power scroll on the Inventory tab
|
||||
if ( (itemData.type === "power") && (this._tabs[0].active === "inventory") ) {
|
||||
const scroll = await Item5e.createScrollFromPower(itemData);
|
||||
itemData = scroll.data;
|
||||
}
|
||||
|
||||
// Create the owned item as normal
|
||||
return super._onDropItemCreate(itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle enabling editing for a power slot override value
|
||||
* @param {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
async _onPowerSlotOverride (event) {
|
||||
const span = event.currentTarget.parentElement;
|
||||
const level = span.dataset.level;
|
||||
const override = this.actor.data.data.powers[level].override || span.dataset.slots;
|
||||
const input = document.createElement("INPUT");
|
||||
input.type = "text";
|
||||
input.name = `data.powers.${level}.override`;
|
||||
input.value = override;
|
||||
input.placeholder = span.dataset.slots;
|
||||
input.dataset.dtype = "Number";
|
||||
|
||||
// Replace the HTML
|
||||
const parent = span.parentElement;
|
||||
parent.removeChild(span);
|
||||
parent.appendChild(input);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Change the uses amount of an Owned Item within the Actor
|
||||
* @param {Event} event The triggering click event
|
||||
* @private
|
||||
*/
|
||||
async _onUsesChange(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
const uses = Math.clamped(0, parseInt(event.target.value), item.data.data.uses.max);
|
||||
event.target.value = uses;
|
||||
return item.update({ 'data.uses.value': uses });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling of an item from the Actor sheet, obtaining the Item instance and dispatching to it's roll method
|
||||
* @private
|
||||
*/
|
||||
_onItemRoll(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
return item.roll();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle attempting to recharge an item usage by rolling a recharge check
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onItemRecharge(event) {
|
||||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
return item.rollRecharge();
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling of an item from the Actor sheet, obtaining the Item instance and dispatching to it's roll method
|
||||
* @private
|
||||
*/
|
||||
_onItemSummary(event) {
|
||||
event.preventDefault();
|
||||
let li = $(event.currentTarget).parents(".item"),
|
||||
item = this.actor.getOwnedItem(li.data("item-id")),
|
||||
chatData = item.getChatData({secrets: this.actor.owner});
|
||||
|
||||
// Toggle summary
|
||||
if ( li.hasClass("expanded") ) {
|
||||
let summary = li.children(".item-summary");
|
||||
summary.slideUp(200, () => summary.remove());
|
||||
} else {
|
||||
let div = $(`<div class="item-summary">${chatData.description.value}</div>`);
|
||||
let props = $(`<div class="item-properties"></div>`);
|
||||
chatData.properties.forEach(p => props.append(`<span class="tag">${p}</span>`));
|
||||
div.append(props);
|
||||
li.append(div.hide());
|
||||
div.slideDown(200);
|
||||
}
|
||||
li.toggleClass("expanded");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onItemCreate(event) {
|
||||
event.preventDefault();
|
||||
const header = event.currentTarget;
|
||||
const type = header.dataset.type;
|
||||
const itemData = {
|
||||
name: game.i18n.format("SW5E.ItemNew", {type: type.capitalize()}),
|
||||
type: type,
|
||||
data: duplicate(header.dataset)
|
||||
};
|
||||
delete itemData.data["type"];
|
||||
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle editing an existing Owned Item for the Actor
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onItemEdit(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
const item = this.actor.getOwnedItem(li.dataset.itemId);
|
||||
item.sheet.render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle deleting an existing Owned Item for the Actor
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget.closest(".item");
|
||||
this.actor.deleteOwnedItem(li.dataset.itemId);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling an Ability check, either a test or a saving throw
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onRollAbilityTest(event) {
|
||||
event.preventDefault();
|
||||
let ability = event.currentTarget.parentElement.dataset.ability;
|
||||
this.actor.rollAbility(ability, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling a Skill check
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onRollSkillCheck(event) {
|
||||
event.preventDefault();
|
||||
const skill = event.currentTarget.parentElement.dataset.skill;
|
||||
this.actor.rollSkill(skill, {event: event});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle toggling Ability score proficiency level
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
_onToggleAbilityProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = event.currentTarget.previousElementSibling;
|
||||
this.actor.update({[field.name]: 1 - parseInt(field.value)});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle toggling of filters to display a different set of owned items
|
||||
* @param {Event} event The click event which triggered the toggle
|
||||
* @private
|
||||
*/
|
||||
_onToggleFilter(event) {
|
||||
event.preventDefault();
|
||||
const li = event.currentTarget;
|
||||
const set = this._filters[li.parentElement.dataset.filter];
|
||||
const filter = li.dataset.filter;
|
||||
if ( set.has(filter) ) set.delete(filter);
|
||||
else set.add(filter);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onTraitSelector(event) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
const label = a.parentElement.querySelector("label");
|
||||
const choices = CONFIG.SW5E[a.dataset.options];
|
||||
const options = { name: a.dataset.target, title: label.innerText, choices };
|
||||
new TraitSelector(this.actor, options).render(true)
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
||||
|
||||
// Add button to revert polymorph
|
||||
if ( !this.actor.isPolymorphed || this.actor.isToken ) return buttons;
|
||||
buttons.unshift({
|
||||
label: 'SW5E.PolymorphRestoreTransformation',
|
||||
class: "restore-transformation",
|
||||
icon: "fas fa-backward",
|
||||
onclick: ev => this.actor.revertOriginalForm()
|
||||
});
|
||||
return buttons;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
import ActorSheet5e from "../base.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for NPC type characters in the SW5E system.
|
||||
|
@ -16,7 +16,6 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "npc"],
|
||||
width: 600,
|
||||
width: 800,
|
||||
tabs: [{
|
||||
navSelector: ".root-tabs",
|
||||
|
|
385
module/actor/sheets/newSheet/vehicle.js
Normal file
385
module/actor/sheets/newSheet/vehicle.js
Normal file
|
@ -0,0 +1,385 @@
|
|||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for Vehicle type actors.
|
||||
* Extends the base ActorSheet5e class.
|
||||
* @type {ActorSheet5e}
|
||||
*/
|
||||
export default class ActorSheet5eVehicle extends ActorSheet5e {
|
||||
/**
|
||||
* Define default rendering options for the Vehicle sheet.
|
||||
* @returns {Object}
|
||||
*/
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||
width: 605,
|
||||
height: 680
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Creates a new cargo entry for a vehicle Actor.
|
||||
*/
|
||||
static get newCargo() {
|
||||
return {
|
||||
name: '',
|
||||
quantity: 1
|
||||
};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Compute the total weight of the vehicle's cargo.
|
||||
* @param {Number} totalWeight The cumulative item weight from inventory items
|
||||
* @param {Object} actorData The data object for the Actor being rendered
|
||||
* @returns {{max: number, value: number, pct: number}}
|
||||
* @private
|
||||
*/
|
||||
_computeEncumbrance(totalWeight, actorData) {
|
||||
|
||||
// Compute currency weight
|
||||
const totalCoins = Object.values(actorData.data.currency).reduce((acc, denom) => acc + denom, 0);
|
||||
totalWeight += totalCoins / CONFIG.SW5E.encumbrance.currencyPerWeight;
|
||||
|
||||
// Vehicle weights are an order of magnitude greater.
|
||||
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
||||
|
||||
// Compute overall encumbrance
|
||||
const max = actorData.data.attributes.capacity.cargo;
|
||||
const pct = Math.clamped((totalWeight * 100) / max, 0, 100);
|
||||
return {value: totalWeight.toNearest(0.1), max, pct};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare items that are mounted to a vehicle and require one or more crew
|
||||
* to operate.
|
||||
* @private
|
||||
*/
|
||||
_prepareCrewedItem(item) {
|
||||
|
||||
// Determine crewed status
|
||||
const isCrewed = item.data.crewed;
|
||||
item.toggleClass = isCrewed ? 'active' : '';
|
||||
item.toggleTitle = game.i18n.localize(`SW5E.${isCrewed ? 'Crewed' : 'Uncrewed'}`);
|
||||
|
||||
// Handle crew actions
|
||||
if (item.type === 'feat' && item.data.activation.type === 'crew') {
|
||||
item.crew = item.data.activation.cost;
|
||||
item.cover = game.i18n.localize(`SW5E.${item.data.cover ? 'CoverTotal' : 'None'}`);
|
||||
if (item.data.cover === .5) item.cover = '½';
|
||||
else if (item.data.cover === .75) item.cover = '¾';
|
||||
else if (item.data.cover === null) item.cover = '—';
|
||||
if (item.crew < 1 || item.crew === null) item.crew = '—';
|
||||
}
|
||||
|
||||
// Prepare vehicle weapons
|
||||
if (item.type === 'equipment' || item.type === 'weapon') {
|
||||
item.threshold = item.data.hp.dt ? item.data.hp.dt : '—';
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_getMovementSpeed(actorData) {
|
||||
return {primary: "", special: ""};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Organize Owned Items for rendering the Vehicle sheet.
|
||||
* @private
|
||||
*/
|
||||
_prepareItems(data) {
|
||||
const cargoColumns = [{
|
||||
label: game.i18n.localize('SW5E.Quantity'),
|
||||
css: 'item-qty',
|
||||
property: 'quantity',
|
||||
editable: 'Number'
|
||||
}];
|
||||
|
||||
const equipmentColumns = [{
|
||||
label: game.i18n.localize('SW5E.Quantity'),
|
||||
css: 'item-qty',
|
||||
property: 'data.quantity'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.AC'),
|
||||
css: 'item-ac',
|
||||
property: 'data.armor.value'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.HP'),
|
||||
css: 'item-hp',
|
||||
property: 'data.hp.value',
|
||||
editable: 'Number'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Threshold'),
|
||||
css: 'item-threshold',
|
||||
property: 'threshold'
|
||||
}];
|
||||
|
||||
const features = {
|
||||
actions: {
|
||||
label: game.i18n.localize('SW5E.ActionPl'),
|
||||
items: [],
|
||||
crewable: true,
|
||||
dataset: {type: 'feat', 'activation.type': 'crew'},
|
||||
columns: [{
|
||||
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||
css: 'item-crew',
|
||||
property: 'crew'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Cover'),
|
||||
css: 'item-cover',
|
||||
property: 'cover'
|
||||
}]
|
||||
},
|
||||
equipment: {
|
||||
label: game.i18n.localize('SW5E.ItemTypeEquipment'),
|
||||
items: [],
|
||||
crewable: true,
|
||||
dataset: {type: 'equipment', 'armor.type': 'vehicle'},
|
||||
columns: equipmentColumns
|
||||
},
|
||||
passive: {
|
||||
label: game.i18n.localize('SW5E.Features'),
|
||||
items: [],
|
||||
dataset: {type: 'feat'}
|
||||
},
|
||||
reactions: {
|
||||
label: game.i18n.localize('SW5E.ReactionPl'),
|
||||
items: [],
|
||||
dataset: {type: 'feat', 'activation.type': 'reaction'}
|
||||
},
|
||||
weapons: {
|
||||
label: game.i18n.localize('SW5E.ItemTypeWeaponPl'),
|
||||
items: [],
|
||||
crewable: true,
|
||||
dataset: {type: 'weapon', 'weapon-type': 'siege'},
|
||||
columns: equipmentColumns
|
||||
}
|
||||
};
|
||||
|
||||
const cargo = {
|
||||
crew: {
|
||||
label: game.i18n.localize('SW5E.VehicleCrew'),
|
||||
items: data.data.cargo.crew,
|
||||
css: 'cargo-row crew',
|
||||
editableName: true,
|
||||
dataset: {type: 'crew'},
|
||||
columns: cargoColumns
|
||||
},
|
||||
passengers: {
|
||||
label: game.i18n.localize('SW5E.VehiclePassengers'),
|
||||
items: data.data.cargo.passengers,
|
||||
css: 'cargo-row passengers',
|
||||
editableName: true,
|
||||
dataset: {type: 'passengers'},
|
||||
columns: cargoColumns
|
||||
},
|
||||
cargo: {
|
||||
label: game.i18n.localize('SW5E.VehicleCargo'),
|
||||
items: [],
|
||||
dataset: {type: 'loot'},
|
||||
columns: [{
|
||||
label: game.i18n.localize('SW5E.Quantity'),
|
||||
css: 'item-qty',
|
||||
property: 'data.quantity',
|
||||
editable: 'Number'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Price'),
|
||||
css: 'item-price',
|
||||
property: 'data.price',
|
||||
editable: 'Number'
|
||||
}, {
|
||||
label: game.i18n.localize('SW5E.Weight'),
|
||||
css: 'item-weight',
|
||||
property: 'data.weight',
|
||||
editable: 'Number'
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
let totalWeight = 0;
|
||||
for (const item of data.items) {
|
||||
this._prepareCrewedItem(item);
|
||||
if (item.type === 'weapon') features.weapons.items.push(item);
|
||||
else if (item.type === 'equipment') features.equipment.items.push(item);
|
||||
else if (item.type === 'loot') {
|
||||
totalWeight += (item.data.weight || 0) * item.data.quantity;
|
||||
cargo.cargo.items.push(item);
|
||||
}
|
||||
else if (item.type === 'feat') {
|
||||
if (!item.data.activation.type || item.data.activation.type === 'none') {
|
||||
features.passive.items.push(item);
|
||||
}
|
||||
else if (item.data.activation.type === 'reaction') features.reactions.items.push(item);
|
||||
else features.actions.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
data.features = Object.values(features);
|
||||
data.cargo = Object.values(cargo);
|
||||
data.data.attributes.encumbrance = this._computeEncumbrance(totalWeight, data);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Event Listeners and Handlers */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
activateListeners(html) {
|
||||
super.activateListeners(html);
|
||||
if (!this.options.editable) return;
|
||||
|
||||
html.find('.item-toggle').click(this._onToggleItem.bind(this));
|
||||
html.find('.item-hp input')
|
||||
.click(evt => evt.target.select())
|
||||
.change(this._onHPChange.bind(this));
|
||||
|
||||
html.find('.item:not(.cargo-row) input[data-property]')
|
||||
.click(evt => evt.target.select())
|
||||
.change(this._onEditInSheet.bind(this));
|
||||
|
||||
html.find('.cargo-row input')
|
||||
.click(evt => evt.target.select())
|
||||
.change(this._onCargoRowChange.bind(this));
|
||||
|
||||
if (this.actor.data.data.attributes.actions.stations) {
|
||||
html.find('.counter.actions, .counter.action-thresholds').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle saving a cargo row (i.e. crew or passenger) in-sheet.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Actor>|null}
|
||||
* @private
|
||||
*/
|
||||
_onCargoRowChange(event) {
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget;
|
||||
const row = target.closest('.item');
|
||||
const idx = Number(row.dataset.itemId);
|
||||
const property = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
|
||||
// Get the cargo entry
|
||||
const cargo = duplicate(this.actor.data.data.cargo[property]);
|
||||
const entry = cargo[idx];
|
||||
if (!entry) return null;
|
||||
|
||||
// Update the cargo value
|
||||
const key = target.dataset.property || 'name';
|
||||
const type = target.dataset.dtype;
|
||||
let value = target.value;
|
||||
if (type === 'Number') value = Number(value);
|
||||
entry[key] = value;
|
||||
|
||||
// Perform the Actor update
|
||||
return this.actor.update({[`data.cargo.${property}`]: cargo});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle editing certain values like quantity, price, and weight in-sheet.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
_onEditInSheet(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const property = event.currentTarget.dataset.property;
|
||||
const type = event.currentTarget.dataset.dtype;
|
||||
let value = event.currentTarget.value;
|
||||
switch (type) {
|
||||
case 'Number': value = parseInt(value); break;
|
||||
case 'Boolean': value = value === 'true'; break;
|
||||
}
|
||||
return item.update({[`${property}`]: value});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle creating a new crew or passenger row.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Actor|Item>}
|
||||
* @private
|
||||
*/
|
||||
_onItemCreate(event) {
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget;
|
||||
const type = target.dataset.type;
|
||||
if (type === 'crew' || type === 'passengers') {
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]);
|
||||
cargo.push(this.constructor.newCargo);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
return super._onItemCreate(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle deleting a crew or passenger row.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Actor|Item>}
|
||||
* @private
|
||||
*/
|
||||
_onItemDelete(event) {
|
||||
event.preventDefault();
|
||||
const row = event.currentTarget.closest('.item');
|
||||
if (row.classList.contains('cargo-row')) {
|
||||
const idx = Number(row.dataset.itemId);
|
||||
const type = row.classList.contains('crew') ? 'crew' : 'passengers';
|
||||
const cargo = duplicate(this.actor.data.data.cargo[type]).filter((_, i) => i !== idx);
|
||||
return this.actor.update({[`data.cargo.${type}`]: cargo});
|
||||
}
|
||||
|
||||
return super._onItemDelete(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Special handling for editing HP to clamp it within appropriate range.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
_onHPChange(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const hp = Math.clamped(0, parseInt(event.currentTarget.value), item.data.data.hp.max);
|
||||
event.currentTarget.value = hp;
|
||||
return item.update({'data.hp.value': hp});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle toggling an item's crewed status.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
_onToggleItem(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest('.item').dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const crewed = !!item.data.data.crewed;
|
||||
return item.update({'data.crewed': !crewed});
|
||||
}
|
||||
};
|
|
@ -1,9 +1,10 @@
|
|||
import Item5e from "../../item/entity.js";
|
||||
import TraitSelector from "../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../apps/actor-flags.js";
|
||||
import MovementConfig from "../../apps/movement-config.js";
|
||||
import {SW5E} from '../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../effects.js";
|
||||
import Item5e from "../../../item/entity.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import {SW5E} from '../../../config.js';
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
|
||||
/**
|
||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||
|
@ -99,6 +100,9 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
// Movement speeds
|
||||
data.movement = this._getMovementSpeed(data.actor);
|
||||
|
||||
// Senses
|
||||
data.senses = this._getSenses(data.actor);
|
||||
|
||||
// Update traits
|
||||
this._prepareTraits(data.actor.data.traits);
|
||||
|
||||
|
@ -121,7 +125,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
* @private
|
||||
*/
|
||||
_getMovementSpeed(actorData) {
|
||||
const movement = actorData.data.attributes.movement;
|
||||
const movement = actorData.data.attributes.movement || {};
|
||||
const speeds = [
|
||||
[movement.burrow, `${game.i18n.localize("SW5E.MovementBurrow")} ${movement.burrow}`],
|
||||
[movement.climb, `${game.i18n.localize("SW5E.MovementClimb")} ${movement.climb}`],
|
||||
|
@ -136,6 +140,20 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_getSenses(actorData) {
|
||||
const senses = actorData.data.attributes.senses || {};
|
||||
const tags = {};
|
||||
for ( let [k, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||
const v = senses[k] ?? 0
|
||||
if ( v === 0 ) continue;
|
||||
tags[k] = `${game.i18n.localize(label)} ${v} ${senses.units}`;
|
||||
}
|
||||
if ( !!senses.special ) tags["special"] = senses.special;
|
||||
return tags;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare the data structure for traits data like languages, resistances & vulnerabilities, and proficiencies
|
||||
* @param {object} traits The raw traits data object from the actor data
|
||||
|
@ -373,8 +391,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find('.trait-selector').click(this._onTraitSelector.bind(this));
|
||||
|
||||
// Configure Special Flags
|
||||
html.find('.configure-movement').click(this._onMovementConfig.bind(this));
|
||||
html.find('.configure-flags').click(this._onConfigureFlags.bind(this));
|
||||
html.find('.config-button').click(this._onConfigMenu.bind(this));
|
||||
|
||||
// Owned Item management
|
||||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
||||
|
@ -448,11 +465,24 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle click events for the Traits tab button to configure special Character Flags
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onConfigureFlags(event) {
|
||||
_onConfigMenu(event) {
|
||||
event.preventDefault();
|
||||
new ActorSheetFlags(this.actor).render(true);
|
||||
const button = event.currentTarget;
|
||||
switch ( button.dataset.action ) {
|
||||
case "movement":
|
||||
new ActorMovementConfig(this.object).render(true);
|
||||
break;
|
||||
case "flags":
|
||||
new ActorSheetFlags(this.object).render(true);
|
||||
break;
|
||||
case "senses":
|
||||
new ActorSensesConfig(this.object).render(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -529,6 +559,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
icon: '<i class="fas fa-paw"></i>',
|
||||
label: game.i18n.localize('SW5E.PolymorphWildShape'),
|
||||
callback: html => this.actor.transformInto(sourceActor, {
|
||||
keepBio: true,
|
||||
keepClass: true,
|
||||
keepMental: true,
|
||||
mergeSaves: true,
|
||||
mergeSkills: true,
|
||||
|
@ -619,14 +651,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
event.preventDefault();
|
||||
const itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.getOwnedItem(itemId);
|
||||
|
||||
// Roll powers through the actor
|
||||
if ( item.data.type === "power" ) {
|
||||
return this.actor.usePower(item, {configureDialog: !event.shiftKey});
|
||||
}
|
||||
|
||||
// Otherwise roll the Item directly
|
||||
else return item.roll();
|
||||
return item.roll();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -687,7 +712,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
data: duplicate(header.dataset)
|
||||
};
|
||||
delete itemData.data["type"];
|
||||
return this.actor.createOwnedItem(itemData);
|
||||
return this.actor.createEmbeddedEntity("OwnedItem", itemData);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -791,18 +816,6 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onMovementConfig(event) {
|
||||
event.preventDefault();
|
||||
new MovementConfig(this.object).render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
_getHeaderButtons() {
|
||||
let buttons = super._getHeaderButtons();
|
|
@ -1,4 +1,4 @@
|
|||
import ActorSheet5e from "../base.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
import Actor5e from "../../entity.js";
|
||||
|
||||
/**
|
||||
|
@ -75,6 +75,18 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
// Item details
|
||||
item.img = item.img || DEFAULT_TOKEN;
|
||||
item.isStack = Number.isNumeric(item.data.quantity) && (item.data.quantity !== 1);
|
||||
item.attunement = {
|
||||
1: {
|
||||
icon: "fa-sun",
|
||||
cls: "not-attuned",
|
||||
title: "SW5E.AttunementRequired"
|
||||
},
|
||||
2: {
|
||||
icon: "fa-sun",
|
||||
cls: "attuned",
|
||||
title: "SW5E.AttunementAttuned"
|
||||
}
|
||||
}[item.data.attunement];
|
||||
|
||||
// Item usage
|
||||
item.hasUses = item.data.uses && (item.data.uses.max > 0);
|
||||
|
@ -203,7 +215,7 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling a death saving throw for the Character
|
||||
* Handle mouse click events for character sheet actions
|
||||
* @param {MouseEvent} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
|
@ -263,37 +275,21 @@ export default class ActorSheet5eCharacter extends ActorSheet5e {
|
|||
|
||||
/** @override */
|
||||
async _onDropItemCreate(itemData) {
|
||||
let addLevel = false;
|
||||
|
||||
// Upgrade the number of class levels a character has and add features
|
||||
// Increment the number of class levels a character instead of creating a new item
|
||||
if ( itemData.type === "class" ) {
|
||||
const cls = this.actor.itemTypes.class.find(c => c.name === itemData.name);
|
||||
let priorLevel = cls?.data.data.levels ?? 0;
|
||||
const hasClass = !!cls;
|
||||
|
||||
// Increment levels instead of creating a new item
|
||||
if ( hasClass ) {
|
||||
if ( !!cls ) {
|
||||
const next = Math.min(priorLevel + 1, 20 + priorLevel - this.actor.data.data.details.level);
|
||||
if ( next > priorLevel ) {
|
||||
itemData.levels = next;
|
||||
await cls.update({"data.levels": next});
|
||||
addLevel = true;
|
||||
return cls.update({"data.levels": next});
|
||||
}
|
||||
}
|
||||
|
||||
// Add class features
|
||||
if ( !hasClass || addLevel ) {
|
||||
const features = await Actor5e.getClassFeatures({
|
||||
className: itemData.name,
|
||||
archetypeName: itemData.data.archetype,
|
||||
level: itemData.levels,
|
||||
priorLevel: priorLevel
|
||||
});
|
||||
await this.actor.createEmbeddedEntity("OwnedItem", features);
|
||||
}
|
||||
}
|
||||
|
||||
// Default drop handling if levels were not added
|
||||
if ( !addLevel ) super._onDropItemCreate(itemData);
|
||||
super._onDropItemCreate(itemData);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import ActorSheet5e from "../base.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for NPC type characters in the SW5E system.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import ActorSheet5e from "../base.js";
|
||||
import ActorSheet5e from "./base.js";
|
||||
|
||||
/**
|
||||
* An Actor sheet for Vehicle type actors.
|
||||
|
|
|
@ -34,15 +34,19 @@ export default class AbilityUseDialog extends Dialog {
|
|||
const quantity = itemData.quantity || 0;
|
||||
const recharge = itemData.recharge || {};
|
||||
const recharges = !!recharge.value;
|
||||
const sufficientUses = (quantity > 0 && !uses.value) || uses.value > 0;
|
||||
|
||||
// Prepare dialog form data
|
||||
const data = {
|
||||
item: item.data,
|
||||
title: game.i18n.format("SW5E.AbilityUseHint", item.data),
|
||||
note: this._getAbilityUseNote(item.data, uses, recharge),
|
||||
hasLimitedUses: uses.max || recharges,
|
||||
canUse: recharges ? recharge.charged : (quantity > 0 && !uses.value) || uses.value > 0,
|
||||
hasPlaceableTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||
consumePowerSlot: false,
|
||||
consumeRecharge: recharges,
|
||||
consumeResource: !!itemData.consume.target,
|
||||
consumeUses: uses.max,
|
||||
canUse: recharges ? recharge.charged : sufficientUses,
|
||||
createTemplate: game.user.can("TEMPLATE_CREATE") && item.hasAreaTarget,
|
||||
errors: []
|
||||
};
|
||||
if ( item.data.type === "power" ) this._getPowerData(actorData, itemData, data);
|
||||
|
@ -50,7 +54,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
// Render the ability usage template
|
||||
const html = await renderTemplate("systems/sw5e/templates/apps/ability-use.html", data);
|
||||
|
||||
// Create the Dialog and return as a Promise
|
||||
// Create the Dialog and return data as a Promise
|
||||
const icon = data.isPower ? "fa-magic" : "fa-fist-raised";
|
||||
const label = game.i18n.localize("SW5E.AbilityUse" + (data.isPower ? "Cast" : "Use"));
|
||||
return new Promise((resolve) => {
|
||||
|
@ -61,7 +65,10 @@ export default class AbilityUseDialog extends Dialog {
|
|||
use: {
|
||||
icon: `<i class="fas ${icon}"></i>`,
|
||||
label: label,
|
||||
callback: html => resolve(new FormData(html[0].querySelector("form")))
|
||||
callback: html => {
|
||||
const fd = new FormDataExtended(html[0].querySelector("form"));
|
||||
resolve(fd.toObject());
|
||||
}
|
||||
}
|
||||
},
|
||||
default: "use",
|
||||
|
@ -83,11 +90,11 @@ export default class AbilityUseDialog extends Dialog {
|
|||
|
||||
// Determine whether the power may be up-cast
|
||||
const lvl = itemData.level;
|
||||
const canUpcast = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||
const consumePowerSlot = (lvl > 0) && CONFIG.SW5E.powerUpcastModes.includes(itemData.preparation.mode);
|
||||
|
||||
// If can't upcast, return early and don't bother calculating available power slots
|
||||
if (!canUpcast) {
|
||||
data = mergeObject(data, { isPower: true, canUpcast });
|
||||
if (!consumePowerSlot) {
|
||||
mergeObject(data, { isPower: true, consumePowerSlot });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -122,7 +129,7 @@ export default class AbilityUseDialog extends Dialog {
|
|||
const canCast = powerLevels.some(l => l.hasSlots);
|
||||
|
||||
// Return merged data
|
||||
data = mergeObject(data, { isPower: true, canUpcast, powerLevels });
|
||||
data = mergeObject(data, { isPower: true, consumePowerSlot, powerLevels });
|
||||
if ( !canCast ) data.errors.push("SW5E.PowerCastNoSlots");
|
||||
}
|
||||
|
||||
|
|
|
@ -2,21 +2,27 @@
|
|||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
*/
|
||||
export default class MovementConfig extends BaseEntitySheet {
|
||||
export default class ActorMovementConfig extends BaseEntitySheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
title: "SW5E.MovementConfig",
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/movement-config.html",
|
||||
width: 240,
|
||||
width: 300,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.MovementConfig")}: ${this.entity.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
const data = {
|
||||
|
|
43
module/apps/senses-config.js
Normal file
43
module/apps/senses-config.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* A simple form to set actor movement speeds
|
||||
* @implements {BaseEntitySheet}
|
||||
*/
|
||||
export default class ActorSensesConfig extends BaseEntitySheet {
|
||||
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e"],
|
||||
template: "systems/sw5e/templates/apps/senses-config.html",
|
||||
width: 300,
|
||||
height: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
get title() {
|
||||
return `${game.i18n.localize("SW5E.SensesConfig")}: ${this.entity.name}`;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
getData(options) {
|
||||
const senses = this.entity._data.data.attributes?.senses ?? {};
|
||||
const data = {
|
||||
senses: {},
|
||||
special: senses.special ?? "",
|
||||
units: senses.units, movementUnits: CONFIG.SW5E.movementUnits
|
||||
};
|
||||
for ( let [name, label] of Object.entries(CONFIG.SW5E.senses) ) {
|
||||
const v = senses[name];
|
||||
data.senses[name] = {
|
||||
label: game.i18n.localize(label),
|
||||
value: Number.isNumeric(v) ? v.toNearest(0.1) : 0
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -36,8 +36,8 @@ export default class TraitSelector extends FormApplication {
|
|||
getData() {
|
||||
|
||||
// Get current values
|
||||
let attr = getProperty(this.object._data, this.attribute) || {};
|
||||
attr.value = attr.value || [];
|
||||
let attr = getProperty(this.object._data, this.attribute);
|
||||
if ( getType(attr) !== "Object" ) attr = {value: [], custom: ""};
|
||||
|
||||
// Populate choices
|
||||
const choices = duplicate(this.options.choices);
|
||||
|
|
|
@ -8,9 +8,9 @@ SW5E.ASCII = `__________________________________________
|
|||
_
|
||||
| |
|
||||
___| |_ __ _ _ ____ ____ _ _ __ ___
|
||||
/ __| __/ _\ | |__\ \ /\ / / _\ | |__/ __|
|
||||
\__ \ || (_) | | \ V V / (_) | | \__ \
|
||||
|___/\__\__/_|_| \_/\_/ \__/_|_| |___/
|
||||
/ __| __/ _\\ | |__\\ \\ /\\ / / _\\ | |__/ __|
|
||||
\\__ \\ || (_) | | \\ \V \V / (_) | | \\__ \\
|
||||
|___/\\__\\__/_|_| \\_/\\_/ \\__/_|_| |___/
|
||||
__________________________________________`;
|
||||
|
||||
|
||||
|
@ -54,6 +54,20 @@ SW5E.alignments = {
|
|||
'cd': "SW5E.AlignmentCD"
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* An enumeration of item attunement states
|
||||
* @type {{"0": string, "1": string, "2": string}}
|
||||
*/
|
||||
SW5E.attunements = {
|
||||
0: "SW5E.AttunementNone",
|
||||
1: "SW5E.AttunementRequired",
|
||||
2: "SW5E.AttunementAttuned"
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
SW5E.weaponProficiencies = {
|
||||
"sim": "SW5E.WeaponSimpleProficiency",
|
||||
|
@ -433,14 +447,14 @@ SW5E.hitDieTypes = ["d4", "d6", "d8", "d10", "d12", "d20"];
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Character senses options
|
||||
* @type {Object}
|
||||
* The set of possible sensory perception types which an Actor may have
|
||||
* @type {object}
|
||||
*/
|
||||
SW5E.senses = {
|
||||
"bs": "SW5E.SenseBS",
|
||||
"dv": "SW5E.SenseDV",
|
||||
"ts": "SW5E.SenseTS",
|
||||
"tr": "SW5E.SenseTR"
|
||||
"blindsight": "SW5E.SenseBlindsight",
|
||||
"darkvision": "SW5E.SenseDarkvision",
|
||||
"tremorsense": "SW5E.SenseTremorsense",
|
||||
"truesight": "SW5E.SenseTruesight"
|
||||
};
|
||||
|
||||
|
||||
|
@ -1140,7 +1154,7 @@ SW5E.characterFlags = {
|
|||
section: "Feats",
|
||||
type: Boolean
|
||||
},
|
||||
"remarkableAthlete": {
|
||||
"remarkableAthlete": {
|
||||
name: "SW5E.FlagsRemarkableAthlete",
|
||||
hint: "SW5E.FlagsRemarkableAthleteHint",
|
||||
abilities: ['str','dex','con'],
|
||||
|
|
|
@ -214,7 +214,6 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
|
|||
messageData.speaker = speaker || ChatMessage.getSpeaker();
|
||||
const messageOptions = {rollMode: rollMode || game.settings.get("core", "rollMode")};
|
||||
parts = parts.concat(["@bonus"]);
|
||||
fastForward = fastForward ?? (event && (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey));
|
||||
|
||||
// Define inner roll function
|
||||
const _roll = function(parts, crit, form) {
|
||||
|
@ -236,6 +235,7 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
|
|||
roll.terms[0].alter(1, criticalBonusDice);
|
||||
roll._formula = roll.formula;
|
||||
}
|
||||
roll.dice.forEach(d => d.options.critical = true);
|
||||
messageData.flavor += ` (${game.i18n.localize("SW5E.Critical")})`;
|
||||
if ( "flags.sw5e.roll" in messageData ) messageData["flags.sw5e.roll"].critical = true;
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ export async function damageRoll({parts, actor, data, event={}, rollMode=null, t
|
|||
};
|
||||
|
||||
// Create the Roll instance
|
||||
const roll = fastForward ? _roll(parts, critical || event.altKey) : await _damageRollDialog({
|
||||
const roll = fastForward ? _roll(parts, critical) : await _damageRollDialog({
|
||||
template, title, parts, data, allowCritical, rollMode: messageOptions.rollMode, dialogOptions, roll: _roll
|
||||
});
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ export default class Item5e extends Item {
|
|||
const itemData = this.data;
|
||||
const data = itemData.data;
|
||||
const C = CONFIG.SW5E;
|
||||
const labels = {};
|
||||
const labels = this.labels = {};
|
||||
|
||||
// Classes
|
||||
if ( itemData.type === "class" ) {
|
||||
|
@ -252,12 +252,8 @@ export default class Item5e extends Item {
|
|||
// Item Actions
|
||||
if ( data.hasOwnProperty("actionType") ) {
|
||||
|
||||
// Saving throws for unowned items
|
||||
const save = data.save;
|
||||
if ( save?.ability && !this.isOwned ) {
|
||||
if ( save.scaling !== "flat" ) save.dc = null;
|
||||
labels.save = game.i18n.format("SW5E.SaveDC", {dc: save.dc || "", ability: C.abilities[save.ability]});
|
||||
}
|
||||
// Saving throws
|
||||
this.getSaveDC();
|
||||
|
||||
// Damage
|
||||
let dam = data.damage || {};
|
||||
|
@ -271,6 +267,30 @@ export default class Item5e extends Item {
|
|||
this.labels = labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the derived spell DC for an item that requires a saving throw
|
||||
* @returns {number|null}
|
||||
*/
|
||||
getSaveDC() {
|
||||
if ( !this.hasSave ) return;
|
||||
const save = this.data.data?.save;
|
||||
|
||||
// Actor spell-DC based scaling
|
||||
if ( save.scaling === "spell" ) {
|
||||
save.dc = this.isOwned ? getProperty(this.actor.data, "data.attributes.spelldc") : null;
|
||||
}
|
||||
|
||||
// Ability-score based scaling
|
||||
else if ( save.scaling !== "flat" ) {
|
||||
save.dc = this.isOwned ? getProperty(this.actor.data, `data.abilities.${save.scaling}.dc`) : null;
|
||||
}
|
||||
|
||||
// Update labels
|
||||
const abl = CONFIG.DND5E.abilities[save.ability];
|
||||
this.labels.save = game.i18n.format("DND5E.SaveDC", {dc: save.dc || "", ability: abl});
|
||||
return save.dc;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,6 +55,5 @@ export function rollItemMacro(itemName) {
|
|||
const item = items[0];
|
||||
|
||||
// Trigger the item roll
|
||||
if ( item.data.type === "power" ) return actor.usePower(item);
|
||||
return item.roll();
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* @return {Promise} A Promise which resolves once the migration is completed
|
||||
*/
|
||||
export const migrateWorld = async function() {
|
||||
ui.notifications.info(`Applying SW5E System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
|
||||
ui.notifications.info(`Applying SW5e System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
|
||||
|
||||
// Migrate World Actors
|
||||
for ( let a of game.actors.entities ) {
|
||||
|
@ -56,7 +56,7 @@ export const migrateWorld = async function() {
|
|||
|
||||
// Set the migration as complete
|
||||
game.settings.set("sw5e", "systemMigrationVersion", game.system.data.version);
|
||||
ui.notifications.info(`SW5E System Migration to version ${game.system.data.version} completed!`, {permanent: true});
|
||||
ui.notifications.info(`SW5e System Migration to version ${game.system.data.version} completed!`, {permanent: true});
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -129,6 +129,7 @@ export const migrateActorData = function(actor) {
|
|||
// Actor Data Updates
|
||||
_migrateActorBonuses(actor, updateData);
|
||||
_migrateActorMovement(actor, updateData);
|
||||
_migrateActorSenses(actor, updateData);
|
||||
|
||||
// Migrate Owned Items
|
||||
if ( !actor.items ) return updateData;
|
||||
|
@ -191,6 +192,7 @@ function cleanActorData(actorData) {
|
|||
*/
|
||||
export const migrateItemData = function(item) {
|
||||
const updateData = {};
|
||||
_migrateItemAttunement(item, updateData);
|
||||
return updateData;
|
||||
};
|
||||
|
||||
|
@ -242,19 +244,72 @@ function _migrateActorBonuses(actor, updateData) {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Migrate the actor bonuses object
|
||||
* Migrate the actor speed string to movement object
|
||||
* @private
|
||||
*/
|
||||
function _migrateActorMovement(actor, updateData) {
|
||||
if ( actor.data.attributes?.movement?.walk !== undefined ) return;
|
||||
const s = (actor.data.attributes?.speed?.value || "").split(" ");
|
||||
const ad = actor.data;
|
||||
const old = ad?.attributes?.speed?.value;
|
||||
if ( old === undefined ) return;
|
||||
const s = (old || "").split(" ");
|
||||
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
|
||||
updateData["data.attributes.-=speed"] = null;
|
||||
return updateData
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Migrate the actor traits.senses string to attributes.senses object
|
||||
* @private
|
||||
*/
|
||||
function _migrateActorSenses(actor, updateData) {
|
||||
const ad = actor.data;
|
||||
if ( ad?.traits?.senses === undefined ) return;
|
||||
const original = ad.traits.senses || "";
|
||||
|
||||
// Try to match old senses with the format like "Darkvision 60 ft, Blindsight 30 ft"
|
||||
const pattern = /([A-z]+)\s?([0-9]+)\s?([A-z]+)?/
|
||||
let wasMatched = false;
|
||||
|
||||
// Match each comma-separated term
|
||||
for ( let s of original.split(",") ) {
|
||||
s = s.trim();
|
||||
const match = s.match(pattern);
|
||||
if ( !match ) continue;
|
||||
const type = match[1].toLowerCase();
|
||||
if ( type in CONFIG.SW5E.senses ) {
|
||||
updateData[`data.attributes.senses.${type}`] = Number(match[2]).toNearest(0.5);
|
||||
wasMatched = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing was matched, but there was an old string - put the whole thing in "special"
|
||||
if ( !wasMatched && !!original ) {
|
||||
updateData["data.attributes.senses.special"] = original;
|
||||
}
|
||||
|
||||
// Remove the old traits.senses string once the migration is complete
|
||||
updateData["data.traits.-=senses"] = null;
|
||||
return updateData;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Delete the old data.attuned boolean
|
||||
* @private
|
||||
*/
|
||||
function _migrateItemAttunement(item, updateData) {
|
||||
if ( item.data.attuned === undefined ) return;
|
||||
updateData["data.attunement"] = 0;
|
||||
updateData["data.-=attuned"] = null;
|
||||
return updateData;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* A general tool to purge flags from all entities in a Compendium pack.
|
||||
* @param {Compendium} pack The compendium pack to clean
|
||||
|
|
|
@ -45,7 +45,10 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
}
|
||||
|
||||
// Return the template constructed from the item data
|
||||
return new this(templateData);
|
||||
const template = new this(templateData);
|
||||
template.item = item;
|
||||
template.actorSheet = item.actor?.sheet || null;
|
||||
return template;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -55,9 +58,16 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
*/
|
||||
drawPreview() {
|
||||
const initialLayer = canvas.activeLayer;
|
||||
|
||||
// Draw the template and switch to the template layer
|
||||
this.draw();
|
||||
this.layer.activate();
|
||||
this.layer.preview.addChild(this);
|
||||
|
||||
// Hide the sheet that originated the preview
|
||||
if ( this.actorSheet ) this.actorSheet.minimize();
|
||||
|
||||
// Activate interactivity
|
||||
this.activatePreviewListeners(initialLayer);
|
||||
}
|
||||
|
||||
|
@ -92,6 +102,7 @@ export default class AbilityTemplate extends MeasuredTemplate {
|
|||
canvas.app.view.oncontextmenu = null;
|
||||
canvas.app.view.onwheel = null;
|
||||
initialLayer.activate();
|
||||
this.actorSheet.maximize();
|
||||
};
|
||||
|
||||
// Confirm the workflow (left-click)
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -80,9 +80,6 @@ body {
|
|||
font-family: 'Open Sans';
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
background-image: url('./ui/SW5e-logo.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Russo One';
|
||||
|
@ -379,12 +376,12 @@ input[type="reset"]:disabled {
|
|||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
.sw5e.chat-card .card-header,
|
||||
.sw5e.chat-card .card-header img,
|
||||
.midi-qol-item-card .card-header img {
|
||||
flex: 0 0 36px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.sw5e.chat-card .card-header,
|
||||
.sw5e.chat-card .card-header h3,
|
||||
.midi-qol-item-card .card-header h3 {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
|
@ -945,7 +942,7 @@ input[type="reset"]:disabled {
|
|||
}
|
||||
.sw5e.sheet.actor .swalt-sheet nav.sheet-navigation {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
column-gap: 16px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
|
132
sw5e.css
132
sw5e.css
|
@ -272,9 +272,12 @@
|
|||
background: transparent;
|
||||
}
|
||||
.sw5e.sheet .editable .rollable:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sw5e.sheet .editable h4.rollable:hover,
|
||||
.sw5e.sheet .editable .rollable:hover > h4 {
|
||||
color: #000;
|
||||
text-shadow: 0 0 10px red;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sw5e.sheet span.sep {
|
||||
flex: none;
|
||||
|
@ -366,6 +369,7 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
.sw5e.sheet .filter-list {
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -423,6 +427,28 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-name {
|
||||
flex: 2;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-name h3,
|
||||
.sw5e.sheet .items-list .item-name h4 {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-controls {
|
||||
flex: 0 0 60px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-controls a {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.sw5e.sheet .items-list .item {
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
|
@ -459,25 +485,10 @@
|
|||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.sw5e.sheet .items-list .items-header .item-name {
|
||||
.sw5e.sheet .items-list .items-header h3 {
|
||||
padding-left: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-name {
|
||||
flex: 2;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-controls {
|
||||
flex: 0 0 60px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.sw5e.sheet .items-list .item-controls a {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.sw5e.sheet .effects .item .effect-source,
|
||||
.sw5e.sheet .effects .item .effect-duration,
|
||||
|
@ -586,7 +597,21 @@
|
|||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.sw5e.sheet.actor .attributes input.temphp {
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement h4.attribute-name {
|
||||
position: relative;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement .config-button {
|
||||
position: absolute;
|
||||
display: none;
|
||||
right: 0;
|
||||
top: 1px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes .movement:hover .config-button {
|
||||
display: block;
|
||||
}
|
||||
.sw5e.sheet.actor .sheet-header .attributes input.temphp {
|
||||
width: 48%;
|
||||
}
|
||||
.sw5e.sheet.actor h4.box-title {
|
||||
|
@ -798,7 +823,7 @@
|
|||
margin: 0 0 3px 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.sw5e.sheet.actor .traits .configure-flags {
|
||||
.sw5e.sheet.actor .traits .config-button {
|
||||
flex: 1;
|
||||
}
|
||||
.sw5e.sheet.actor .traits label {
|
||||
|
@ -871,8 +896,8 @@
|
|||
.sw5e.sheet.actor .inventory-list .item .item-name i.attuned {
|
||||
color: #7a7971;
|
||||
}
|
||||
.sw5e.sheet.actor .inventory-list .item .item-name h4 {
|
||||
font-size: 14px;
|
||||
.sw5e.sheet.actor .inventory-list .item .item-name i.not-attuned {
|
||||
color: #44191A;
|
||||
}
|
||||
.sw5e.sheet.actor .inventory-list .item .item-uses input {
|
||||
width: 24px;
|
||||
|
@ -905,6 +930,9 @@
|
|||
.sw5e.sheet.actor .inventory-list .item-detail.item-action {
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
.sw5e.sheet.actor .inventory-list .item-detail.attunement {
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
.sw5e.sheet.actor .inventory-list .item-weight {
|
||||
flex: 0 0 60px;
|
||||
border-left: 1px solid #c9c7b8;
|
||||
|
@ -995,24 +1023,22 @@
|
|||
flex: 0 0 240px;
|
||||
margin: 0;
|
||||
}
|
||||
.sw5e.sheet.actor .powercasting-ability input,
|
||||
.sw5e.sheet.actor .powercasting-ability label,
|
||||
.sw5e.sheet.actor .powercasting-ability span {
|
||||
flex: 0 0 32px;
|
||||
flex: none;
|
||||
}
|
||||
.sw5e.sheet.actor .powercasting-ability input {
|
||||
flex: 0 0 28px;
|
||||
text-align: center;
|
||||
}
|
||||
.sw5e.sheet.actor .powercasting-ability select {
|
||||
margin: 0 5px;
|
||||
flex: 0 0 150px;
|
||||
}
|
||||
.sw5e.sheet.actor .powercasting-ability h3.power-dc {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
.sw5e.sheet.actor .power-slots,
|
||||
.sw5e.sheet.actor .power-comps {
|
||||
flex: 0 0 75px;
|
||||
padding-right: 5px;
|
||||
text-align: right;
|
||||
flex: none;
|
||||
padding: 0 5px;
|
||||
font-size: 12px;
|
||||
color: #7a7971;
|
||||
border-right: 1px solid #c9c7b8;
|
||||
|
@ -1025,9 +1051,10 @@
|
|||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.sw5e.sheet.actor .power-uses {
|
||||
padding-right: 8px;
|
||||
text-align: right !important;
|
||||
.sw5e.sheet.actor .powerbook .power-uses {
|
||||
padding-right: 5px;
|
||||
text-align: right;
|
||||
color: #7a7971;
|
||||
}
|
||||
.sw5e.sheet.actor .power-school,
|
||||
.sw5e.sheet.actor .power-action,
|
||||
|
@ -1069,6 +1096,7 @@
|
|||
padding-right: 8px;
|
||||
margin-bottom: 4px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
.sw5e.sheet.item {
|
||||
min-height: 660px;
|
||||
|
@ -1587,7 +1615,7 @@
|
|||
.sw5e.chat-card .card-footer span,
|
||||
.midi-qol-item-card .card-footer span {
|
||||
border-right: 2px groove #FFF;
|
||||
padding: 0 5px 0 0;
|
||||
padding: 0 3px 0 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
.sw5e.chat-card .card-footer span:last-child,
|
||||
|
@ -1762,7 +1790,7 @@
|
|||
resize: none;
|
||||
}
|
||||
.sw5e.sheet.actor.character .biography {
|
||||
max-width: calc(-80%);
|
||||
max-width: calc(100% - 180px);
|
||||
}
|
||||
/* ----------------------------------------- */
|
||||
/* Basic Structure */
|
||||
|
@ -1794,9 +1822,6 @@
|
|||
.sw5e.sheet.actor.npc .summary {
|
||||
font-size: 18px;
|
||||
}
|
||||
.sw5e.sheet.actor.npc .powercasting-ability label {
|
||||
flex: none;
|
||||
}
|
||||
.sw5e.sheet.actor.vehicle .features .item-controls {
|
||||
flex: 0 0 68px;
|
||||
}
|
||||
|
@ -1817,30 +1842,3 @@
|
|||
max-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: calc(100% - 2px);
|
||||
min-width: 20px;
|
||||
height: 26px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 1px 3px;
|
||||
margin: 0;
|
||||
color: #191813;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
text-align: inherit;
|
||||
line-height: inherit;
|
||||
border: 1px solid #7a7971;
|
||||
border-radius: 3px;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
input[type="number"]:focus {
|
||||
box-shadow: 0 0 5px red;
|
||||
}
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
|
15
sw5e.js
15
sw5e.js
|
@ -30,7 +30,8 @@ import ActorSheet5eNPCNew from "./module/actor/sheets/newSheet/npc.js";
|
|||
import ItemSheet5e from "./module/item/sheet.js";
|
||||
import ShortRestDialog from "./module/apps/short-rest.js";
|
||||
import TraitSelector from "./module/apps/trait-selector.js";
|
||||
import MovementConfig from "./module/apps/movement-config.js";
|
||||
import ActorMovementConfig from "./module/apps/movement-config.js";
|
||||
import ActorSensesConfig from "./module/apps/senses-config.js";
|
||||
|
||||
// Import Helpers
|
||||
import * as chat from "./module/chat.js";
|
||||
|
@ -58,7 +59,7 @@ Hooks.once("init", function() {
|
|||
ItemSheet5e,
|
||||
ShortRestDialog,
|
||||
TraitSelector,
|
||||
MovementConfig
|
||||
ActorMovementConfig
|
||||
},
|
||||
canvas: {
|
||||
AbilityTemplate
|
||||
|
@ -141,12 +142,12 @@ Hooks.once("setup", function() {
|
|||
|
||||
// Localize CONFIG objects once up-front
|
||||
const toLocalize = [
|
||||
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments",
|
||||
"abilities", "abilityAbbreviations", "abilityActivationTypes", "abilityConsumptionTypes", "actorSizes", "alignments",
|
||||
"armorProficiencies", "armorPropertiesTypes", "conditionTypes", "consumableTypes", "cover", "currencies", "damageResistanceTypes",
|
||||
"damageTypes", "distanceUnits", "equipmentTypes", "healingTypes", "itemActionTypes", "languages",
|
||||
"limitedUsePeriods", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills",
|
||||
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes",
|
||||
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes"
|
||||
"limitedUsePeriods", "movementUnits", "polymorphSettings", "proficiencyLevels", "senses", "skills",
|
||||
"powerComponents", "powerLevels", "powerPreparationModes", "powerScalingModes", "powerSchools", "targetTypes",
|
||||
"timePeriods", "toolProficiencies", "weaponProficiencies", "weaponProperties", "weaponTypes"
|
||||
];
|
||||
|
||||
// Exclude some from sorting where the default order matters
|
||||
|
@ -186,7 +187,7 @@ 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.1.0";
|
||||
const NEEDS_MIGRATION_VERSION = "1.2.0";
|
||||
const COMPATIBLE_MIGRATION_VERSION = 0.80;
|
||||
const needsMigration = currentVersion && isNewerVersion(NEEDS_MIGRATION_VERSION, currentVersion);
|
||||
if ( !needsMigration ) return;
|
||||
|
|
|
@ -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.1.2",
|
||||
"version": "R1-A1",
|
||||
"author": "Dev Team",
|
||||
"scripts": [],
|
||||
"esmodules": ["sw5e.js"],
|
||||
|
|
|
@ -85,7 +85,15 @@
|
|||
"units": "ft",
|
||||
"hover": false
|
||||
},
|
||||
"powercasting": "int",
|
||||
"senses": {
|
||||
"darkvision": 0,
|
||||
"blindsight": 0,
|
||||
"tremorsense": 0,
|
||||
"truesight": 0,
|
||||
"units": "ft",
|
||||
"special": ""
|
||||
},
|
||||
"powercasting": "none",
|
||||
"speed": {
|
||||
"_deprecated": true
|
||||
}
|
||||
|
@ -169,7 +177,6 @@
|
|||
}
|
||||
},
|
||||
"traits": {
|
||||
"senses": "",
|
||||
"languages": {
|
||||
"value": [],
|
||||
"custom": ""
|
||||
|
@ -245,6 +252,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"htmlFields": ["details.biography.value", "details.biography.public"],
|
||||
"character": {
|
||||
"templates": ["common", "creature"],
|
||||
"attributes": {
|
||||
|
@ -503,6 +511,7 @@
|
|||
"weight": 0,
|
||||
"price": 0,
|
||||
"attuned": false,
|
||||
"attunement": 0,
|
||||
"equipped": false,
|
||||
"rarity": "",
|
||||
"identified": true
|
||||
|
@ -519,7 +528,7 @@
|
|||
},
|
||||
"target": {
|
||||
"value": null,
|
||||
"width": null,
|
||||
"width": null,
|
||||
"units": "",
|
||||
"type": ""
|
||||
},
|
||||
|
@ -568,6 +577,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"htmlFields": ["description.value", "description.chat", "description.unidentified"],
|
||||
"archetype": {
|
||||
"templates": ["archetypeDescription"],
|
||||
"className": "",
|
||||
|
@ -675,8 +685,8 @@
|
|||
"supply": 0
|
||||
},
|
||||
"preparation": {
|
||||
"mode": null,
|
||||
"prepared": false
|
||||
"mode": "prepared",
|
||||
"prepared": true
|
||||
},
|
||||
"scaling": {
|
||||
"mode": "none",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<input type="text" name="data.details.alignment" value="{{data.details.alignment}}"
|
||||
placeholder="{{ localize 'SW5E.Alignment' }}" />
|
||||
<div class="proficiency">
|
||||
Proficiency {{numberFormat data.attributes.prof decimals=0 sign=true}}
|
||||
{{ localize "SW5E.Proficiency" }} {{numberFormat data.attributes.prof decimals=0 sign=true}}
|
||||
</div>
|
||||
</div>
|
||||
{{!-- Header Attributes --}}
|
||||
|
@ -77,27 +77,25 @@
|
|||
|
||||
{{!-- INITIATIVE --}}
|
||||
<section>
|
||||
<h1>{{ localize "SW5E.Initiative" }}</h1>
|
||||
<h1 class="attribute-name box-title rollable" data-action="rollInitiative">{{ localize "SW5E.Initiative" }}</h1>
|
||||
<div class="attribute-value">
|
||||
<span class="initiative">{{numberFormat data.attributes.init.total decimals=0 sign=true}}</span>
|
||||
</div>
|
||||
<footer class="attribute-footer initiative">
|
||||
<span>{{ localize "SW5E.Modifier" }}</span>
|
||||
<input name="data.attributes.init.value" type="text" placeholder="0" data-dtype="Number"
|
||||
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}" />
|
||||
<input name="data.attributes.init.value" type="text" data-dtype="Number" placeholder="0"
|
||||
value="{{numberFormat data.attributes.init.value decimals=0 sign=true}}"/>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
{{!-- SPEED / MOVEMENT TYPES --}}
|
||||
<section>
|
||||
<h1>{{ localize "SW5E.Speed" }}</h1>
|
||||
<h1>{{ localize "SW5E.Movement" }}</h1>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.speed.value" type="text" value="{{data.attributes.speed.value}}"
|
||||
placeholder="0" />
|
||||
<span>{{movement.primary}}</span>
|
||||
</div>
|
||||
<footer class="attribute-footer speed">
|
||||
<input type="text" class="speed" name="data.attributes.speed.special"
|
||||
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}" />
|
||||
<footer class="attribute-footer">
|
||||
<span>{{movement.special}}</span>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -56,17 +56,17 @@
|
|||
</footer>
|
||||
</section>
|
||||
<section>
|
||||
<h1>{{ localize "SW5E.Speed" }}</h1>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.speed.value" type="text" value="{{data.attributes.speed.value}}"
|
||||
placeholder="0" />
|
||||
</div>
|
||||
<footer class="attribute-footer speed">
|
||||
<input type="text" class="speed" name="data.attributes.speed.special"
|
||||
value="{{data.attributes.speed.special}}" placeholder="{{ localize 'SW5E.SpeedSpecial' }}" />
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
<h1>{{ localize "SW5E.Movement" }}
|
||||
<a class="config-button" data-action="movement" title="{{localize 'SW5E.MovementConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</h1>
|
||||
<div class="attribute-value">
|
||||
<span>{{movement.primary}}</span>
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<span>{{movement.special}}</span>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
|
@ -75,6 +75,7 @@
|
|||
<button class="item active" data-tab="attributes">{{ localize "SW5E.Attributes" }}</button>
|
||||
<button class="item" data-tab="features">{{ localize "SW5E.Features" }}</button>
|
||||
<button class="item" data-tab="powerbook">{{ localize "SW5E.Powerbook" }}</button>
|
||||
<button class="item" data-tab="effects">{{ localize "SW5E.Effects" }}</button>
|
||||
<button class="item" data-tab="biography">{{ localize "SW5E.Biography" }}</button>
|
||||
</nav>
|
||||
|
||||
|
@ -175,6 +176,11 @@
|
|||
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-powerbook.html"}}
|
||||
</div>
|
||||
|
||||
{{!-- Effects Tab --}}
|
||||
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
|
||||
{{> "systems/sw5e/templates/actors/newActor/parts/swalt-active-effects.html"}}
|
||||
</div>
|
||||
|
||||
{{!-- Biography Tab --}}
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
<div class="panel">
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<div class="effect-duration">{{effect.duration.label}}</div>
|
||||
<div class="item-controls effect-controls flexrow">
|
||||
<a class="effect-control" data-action="toggle" title="{{localize 'SW5E.EffectToggle'}}">
|
||||
<i class="fas fa-circle-notch"></i>
|
||||
<i class="fas {{#if effect.data.disabled}}fa-check{{else}}fa-times{{/if}}"></i>
|
||||
</a>
|
||||
<a class="effect-control" data-action="edit" title="{{localize 'SW5E.EffectEdit'}}">
|
||||
<i class="fas fa-edit"></i>
|
||||
|
|
|
@ -11,9 +11,13 @@
|
|||
</label>
|
||||
<label class="{{#unless data.traits.senses}}inactive{{/unless}}">
|
||||
{{#unless isVehicle}}
|
||||
{{localize "SW5E.Senses"}}
|
||||
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}"
|
||||
placeholder="{{ localize 'SW5E.None' }}" />
|
||||
<label>{{localize "SW5E.Senses"}}</label>
|
||||
<a class="config-button" data-action="senses" title="{{localize 'SW5E.MovementConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
<ul class="traits-list">
|
||||
{{#each senses as |v k|}}
|
||||
<li class="tag {{k}}">{{v}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/unless}}
|
||||
</label>
|
||||
<div class="languages">
|
||||
|
@ -111,7 +115,7 @@
|
|||
{{#unless isVehicle}}
|
||||
<div>
|
||||
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
||||
<a class="configure-flags"><i class="fas fa-cog"></i></a>
|
||||
<a class="config-button" data-action="flags" title="{{localize 'SW5E.SpecialTraits'}}"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
<ul class="passives"></ul>
|
||||
|
|
158
templates/actors/newActor/vehicle-sheet.html
Normal file
158
templates/actors/newActor/vehicle-sheet.html
Normal file
|
@ -0,0 +1,158 @@
|
|||
<form class="{{cssClass}} flexcol" autocomplete="off">
|
||||
<header class="sheet-header flexrow">
|
||||
<img class="profile" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}"
|
||||
data-edit="img">
|
||||
<section class="header-details flexrow">
|
||||
<h1 class="charnam">
|
||||
<input name="name" type="text" value="{{actor.name}}"
|
||||
placeholder="{{localize 'SW5E.Name'}}">
|
||||
</h1>
|
||||
<ul class="summary flexrow">
|
||||
<li>
|
||||
<span>{{lookup config.actorSizes data.traits.size}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>{{localize 'SW5E.Vehicle'}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.traits.dimensions"
|
||||
value="{{data.traits.dimensions}}"
|
||||
placeholder="{{localize 'SW5E.Dimensions'}}">
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" name="data.details.source"
|
||||
value="{{data.details.source}}"
|
||||
placeholder="{{localize 'SW5E.Source'}}">
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="attributes flexrow">
|
||||
<li class="attribute health">
|
||||
<h4 class="attribute-name box-title">{{localize 'SW5E.Health'}}</h4>
|
||||
<div class="attribute-value multiple">
|
||||
<input name="data.attributes.hp.value" type="text" placeholder="—"
|
||||
value="{{data.attributes.hp.value}}" data-dtype="Number">
|
||||
<span class="sep"> / </span>
|
||||
<input name="data.attributes.hp.max" type="text" placeholder="—"
|
||||
value="{{data.attributes.hp.max}}" data-dtype="Number">
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input name="data.attributes.hp.dt" type="text" class="temphp"
|
||||
placeholder="{{localize 'SW5E.Threshold'}}"
|
||||
value="{{data.attributes.hp.dt}}" data-dtype="Number">
|
||||
<input name="data.attributes.hp.mt" type="text" class="temphp"
|
||||
placeholder="{{localize 'SW5E.VehicleMishap'}}"
|
||||
value="{{data.attributes.hp.mt}}" data-dtype="Number">
|
||||
</footer>
|
||||
</li>
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{localize 'SW5E.ArmorClass'}}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.ac.value" type="text" placeholder="—"
|
||||
value="{{data.attributes.ac.value}}" data-dtype="Number">
|
||||
</div>
|
||||
<footer class="attribute-footer">
|
||||
<input type="text" name="data.attributes.ac.motionless"
|
||||
placeholder="—" value="{{data.attributes.ac.motionless}}">
|
||||
</footer>
|
||||
</li>
|
||||
<li class="attribute">
|
||||
<h4 class="attribute-name box-title">{{localize 'SW5E.Speed'}}</h4>
|
||||
<div class="attribute-value">
|
||||
<input name="data.attributes.speed" type="text" placeholder="—" value="{{data.attributes.speed}}"/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<nav class="sheet-navigation tabs" data-group="primary">
|
||||
<a class="item active" data-tab="attributes">{{localize 'SW5E.Attributes'}}</a>
|
||||
<a class="item" data-tab="features">{{localize 'SW5E.Features'}}</a>
|
||||
<a class="item" data-tab="cargo">{{localize 'SW5E.VehicleCargoCrew'}}</a>
|
||||
<a class="item" data-tab="biography">{{localize 'SW5E.Description'}}</a>
|
||||
</nav>
|
||||
|
||||
<section class="sheet-body">
|
||||
<div class="tab attributes flexrow" data-group="primary" data-tab="attributes">
|
||||
<ul class="ability-scores flexrow">
|
||||
{{#each data.abilities as |ability id|}}
|
||||
<li class="ability" data-ability="{{id}}">
|
||||
<h4 class="ability-name box-title rollable">{{ability.label}}</h4>
|
||||
<input class="ability-score" name="data.abilities.{{id}}.value" type="text"
|
||||
value="{{ability.value}}" data-dtype="Number" placeholder="0">
|
||||
<div class="ability-modifiers flexrow">
|
||||
<span class="ability-mod" title="{{localize 'SW5E.Modifier'}}">
|
||||
{{numberFormat ability.mod decimals=0 sign=true}}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<section class="center-pane flexcol">
|
||||
<div class="counters">
|
||||
<div class="counter flexrow creature-cap">
|
||||
<h4>{{localize 'SW5E.VehicleCreatureCapacity'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" placeholder="—"
|
||||
name="data.attributes.capacity.creature"
|
||||
value="{{data.attributes.capacity.creature}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow cargo-cap">
|
||||
<h4>{{localize 'SW5E.VehicleCargoCapacity'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.capacity.cargo" placeholder="0"
|
||||
data-dtype="Number" value="{{data.attributes.capacity.cargo}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow stations">
|
||||
<h4>{{localize 'SW5E.VehicleActionStations'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input name="data.attributes.actions.stations" type="checkbox"
|
||||
data-dtype="Boolean" value="{{data.attributes.actions.stations}}"
|
||||
{{checked data.attributes.actions.stations}}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow actions">
|
||||
<h4>{{localize 'SW5E.ActionPl'}}</h4>
|
||||
<div class="counter-value">
|
||||
<input type="text" name="data.attributes.actions.value" placeholder="0"
|
||||
data-dtype="Number" value="{{data.attributes.actions.value}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="counter flexrow action-thresholds">
|
||||
<h4>{{localize 'SW5E.VehicleActionThresholds'}}</h4>
|
||||
<div class="counter-value">
|
||||
<span class="sep"><</span>
|
||||
<input type="text" placeholder="—" data-dtype="Number"
|
||||
value="{{data.attributes.actions.thresholds.[2]}}"
|
||||
name="data.attributes.actions.thresholds.2">
|
||||
<span class="sep"><</span>
|
||||
<input type="text" placeholder="—" data-dtype="Number"
|
||||
value="{{data.attributes.actions.thresholds.[1]}}"
|
||||
name="data.attributes.actions.thresholds.1">
|
||||
<span class="sep"><</span>
|
||||
<input type="text" placeholder="—" data-dtype="Number"
|
||||
value="{{data.attributes.actions.thresholds.[0]}}"
|
||||
name="data.attributes.actions.thresholds.0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{> 'systems/sw5e/templates/actors/newActor/parts/swalt-traits.html'}}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="tab features flexcol" data-group="primary" data-tab="features">
|
||||
{{> 'systems/sw5e/templates/actors/newActor/parts/swalt-features.html' sections=features}}
|
||||
</div>
|
||||
|
||||
<div class="tab cargo flexcol" data-group="primary" data-tab="cargo">
|
||||
{{> 'systems/sw5e/templates/actors/newActor/parts/swalt-inventory.html' sections=cargo}}
|
||||
</div>
|
||||
|
||||
<div class="tab biography flexcol" data-group="primary" data-tab="biography">
|
||||
{{editor content=data.details.biography.value target='data.details.biography.value'
|
||||
button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
|
@ -81,9 +81,10 @@
|
|||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<li class="attribute movement">
|
||||
<h4 class="attribute-name box-title">
|
||||
{{ localize "SW5E.Speed" }}
|
||||
{{ localize "SW5E.Movement" }}
|
||||
<a class="config-button" data-action="movement" title="{{localize 'SW5E.MovementConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</h4>
|
||||
<div class="attribute-value">
|
||||
<span>{{movement.primary}}</span>
|
||||
|
|
|
@ -61,9 +61,10 @@
|
|||
</footer>
|
||||
</li>
|
||||
|
||||
<li class="attribute">
|
||||
<li class="attribute movement">
|
||||
<h4 class="attribute-name box-title">
|
||||
{{ localize "SW5E.Speed" }}
|
||||
{{ localize "SW5E.Movement" }}
|
||||
<a class="config-button" data-action="movement" title="{{localize 'SW5E.MovementConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</h4>
|
||||
<div class="attribute-value">
|
||||
<span>{{movement.primary}}</span>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
{{/each}}
|
||||
{{/select}}
|
||||
</select>
|
||||
<span>{{localize "SW5E.AbbreviationDC"}} {{data.attributes.powerdc}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="filter-list flexrow" data-filter="powerbook">
|
||||
|
@ -30,28 +31,28 @@
|
|||
<ol class="items-list inventory-list">
|
||||
{{#each powerbook as |section|}}
|
||||
<li class="items-header powerbook-header flexrow">
|
||||
<h3 class="item-name flexrow">{{section.label}}</h3>
|
||||
|
||||
<div class="power-slots">
|
||||
{{#if section.usesSlots}}
|
||||
<input type="text" name="data.powers.{{section.prop}}.value" value="{{section.uses}}" placeholder="0"
|
||||
data-dtype="Number"/>
|
||||
<span class="sep"> / </span>
|
||||
<span class="power-max" data-level="{{section.prop}}" data-slots="{{section.slots}}">
|
||||
{{{section.slots}}}
|
||||
{{#if ../editable}}
|
||||
<a class="slot-max-override" title="{{localize 'SW5E.PowerProgOverride'}}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<div class="item-name flexrow">
|
||||
<h3>{{section.label}}</h3>
|
||||
<div class="power-slots">
|
||||
{{#if section.usesSlots}}
|
||||
<input type="text" name="data.powers.{{section.prop}}.value" value="{{section.uses}}" placeholder="0"
|
||||
data-dtype="Number"/>
|
||||
<span class="sep"> / </span>
|
||||
<span class="power-max" data-level="{{section.prop}}" data-slots="{{section.slots}}">
|
||||
{{{section.slots}}}
|
||||
{{#if ../editable}}
|
||||
<a class="slot-max-override" title="{{localize 'SW5E.PowerProgOverride'}}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
{{ else }}
|
||||
<span>{{{section.uses}}}</span>
|
||||
<span class="sep"> / </span>
|
||||
<span class="power-max">{{{section.slots}}}</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
{{ else }}
|
||||
<span>{{{section.uses}}}</span>
|
||||
<span class="sep"> / </span>
|
||||
<span class="power-max">{{{section.slots}}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="power-school">{{localize "SW5E.PowerSchool"}}</div>
|
||||
<div class="power-action">{{localize "SW5E.PowerUsage"}}</div>
|
||||
<div class="power-target">{{localize "SW5E.PowerTarget"}}</div>
|
||||
|
@ -74,12 +75,11 @@
|
|||
{{#if item.data.uses.per }}
|
||||
<div class="item-detail power-uses">Uses {{item.data.uses.value}} / {{item.data.uses.max}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="power-comps">
|
||||
{{#each labels.components}}
|
||||
<span class="power-component {{this}}">{{this}}</span>
|
||||
{{/each}}
|
||||
<div class="power-comps">
|
||||
{{#each labels.components}}
|
||||
<span class="power-component {{this}}">{{this}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="power-school">{{labels.school}}</div>
|
||||
<div class="power-action">{{labels.activation}}</div>
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
</div>
|
||||
|
||||
{{#unless isVehicle}}
|
||||
<div class="form-group ">
|
||||
<label>{{localize "SW5E.MovementConfig"}}</label>
|
||||
<a class="configure-movement" title="{{localize 'SW5E.MovementConfig'}}"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{#unless data.traits.senses}}inactive{{/unless}}">
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.Senses"}}</label>
|
||||
<input type="text" name="data.traits.senses" value="{{data.traits.senses}}" placeholder="{{ localize 'SW5E.None' }}"/>
|
||||
<a class="config-button" data-action="senses" title="{{localize 'SW5E.MovementConfig'}}"><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>
|
||||
|
||||
<div class="form-group {{data.traits.languages.cssClass}}">
|
||||
|
@ -123,7 +123,7 @@
|
|||
{{#unless isVehicle}}
|
||||
<div class="form-group ">
|
||||
<label>{{localize "SW5E.SpecialTraits"}}</label>
|
||||
<a class="configure-flags"><i class="fas fa-cog"></i></a>
|
||||
<a class="config-button" data-action="flags" title="{{localize 'SW5E.SpecialTraits'}}"><i class="fas fa-cog"></i></a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div class="effect-duration">{{effect.duration.label}}</div>
|
||||
<div class="item-controls effect-controls flexrow">
|
||||
<a class="effect-control" data-action="toggle" title="{{localize 'SW5E.EffectToggle'}}">
|
||||
<i class="fas fa-circle-notch"></i>
|
||||
<i class="fas {{#if effect.data.disabled}}fa-check{{else}}fa-times{{/if}}"></i>
|
||||
</a>
|
||||
<a class="effect-control" data-action="edit" title="{{localize 'SW5E.EffectEdit'}}">
|
||||
<i class="fas fa-edit"></i>
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<p class="notification error">{{localize this}}</p>
|
||||
{{/each}}
|
||||
|
||||
{{#if canUpcast}}
|
||||
{{#if consumePowerSlot}}
|
||||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.PowerCastUpcast" }}</label>
|
||||
<div class="form-fields">
|
||||
<select name="level" {{#unless canUpcast}}disabled{{/unless}}>
|
||||
<select name="level">
|
||||
{{#select item.data.level}}
|
||||
{{#each powerLevels as |l|}}
|
||||
<option value="{{l.level}}" {{#unless l.canCast}}disabled{{/unless}}>{{l.label}}</option>
|
||||
|
@ -24,13 +24,25 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if hasLimitedUses}}
|
||||
{{#if consumeUses}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox"><input type="checkbox" name="consumeUse" checked/>{{ localize "SW5E.AbilityUseConsume" }}</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if hasPlaceableTemplate}}
|
||||
{{#if consumeResource}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox"><input type="checkbox" name="consumeResource" checked/>{{ localize "SW5E.ConsumeResource" }}</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if consumeRecharge}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox"><input type="checkbox" name="consumeRecharge" checked/>{{ localize "SW5E.ConsumeRecharge" }}</label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if createTemplate}}
|
||||
<div class="form-group">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="placeTemplate" checked/>
|
||||
|
|
20
templates/apps/senses-config.html
Normal file
20
templates/apps/senses-config.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<form autocomplete="off">
|
||||
<p class="notes">{{localize "SW5E.SensesConfigHint"}}</p>
|
||||
{{#each senses as |sense name|}}
|
||||
<div class="form-group">
|
||||
<label>{{sense.label}}</label>
|
||||
<input name="data.attributes.senses.{{name}}" type="number" step="0.1" value="{{sense.value}}"/>
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.MovementUnits"}}</label>
|
||||
<select name="data.attributes.senses.units">
|
||||
{{selectOptions movementUnits selected=units}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.SenseSpecial"}}</label>
|
||||
<input type="text" name="data.attributes.senses.special" value="{{special}}"/>
|
||||
</div>
|
||||
<button type="submit" name="submit" value="1"><i class="far fa-save"></i> {{ localize "Submit"}}</button>
|
||||
</form>
|
|
@ -107,7 +107,7 @@
|
|||
<label>
|
||||
{{localize "SW5E.ClassSkillsChosen"}}
|
||||
{{#if editable }}
|
||||
<a class="trait-selector class-skills" data-edit="data.skills" data-options="skills">
|
||||
<a class="trait-selector class-skills" data-target="data.skills" data-options="skills">
|
||||
<i class="fas fa-edit"></i></a>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
|
|
@ -57,6 +57,13 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.Attunement"}}</label>
|
||||
<select name="data.attunement" data-dtype="Number">
|
||||
{{selectOptions config.attunements selected=data.attunement localize=true}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.ItemConsumableStatus" }}</label>
|
||||
<label class="checkbox">
|
||||
|
@ -65,9 +72,6 @@
|
|||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h3 class="form-header">{{ localize "SW5E.ItemConsumableUsage" }}</h3>
|
||||
|
|
|
@ -59,6 +59,13 @@
|
|||
</div>
|
||||
|
||||
{{#unless isMountable}}
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.Attunement"}}</label>
|
||||
<select name="data.attunement" data-dtype="Number">
|
||||
{{selectOptions config.attunements selected=data.attunement localize=true}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{!-- Equipment Status --}}
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.ItemEquipmentStatus" }}</label>
|
||||
|
@ -71,9 +78,6 @@
|
|||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||
</label>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
|
|
|
@ -75,6 +75,5 @@
|
|||
<div class="tab effects flexcol" data-group="primary" data-tab="effects">
|
||||
{{> "systems/sw5e/templates/actors/parts/active-effects.html"}}
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</form>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<div class="form-group">
|
||||
<label>{{ localize "SW5E.ItemAttackBonus" }}</label>
|
||||
<div class="form-fields">
|
||||
<input type="text" name="data.attackBonus" value="{{data.attackBonus}}" data-dtype="Number"/>
|
||||
<input type="text" name="data.attackBonus" value="{{data.attackBonus}}"/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
<div class="form-fields">
|
||||
<input type="text" name="data.uses.value" value="{{data.uses.value}}" data-dtype="Number"/>
|
||||
<span class="sep">{{ localize "SW5E.of" }}</span>
|
||||
<input type="text" name="data.uses.max" value="{{data.uses.max}}" data-dtype="Number"/>
|
||||
<input type="text" name="data.uses.max" value="{{data.uses.max}}"/>
|
||||
<span class="sep">{{ localize "SW5E.per" }}</span>
|
||||
<select name="data.uses.per">
|
||||
{{#select data.uses.per}}
|
||||
|
|
|
@ -58,6 +58,13 @@
|
|||
</div>
|
||||
|
||||
{{#unless isMountable}}
|
||||
<div class="form-group">
|
||||
<label>{{localize "SW5E.Attunement"}}</label>
|
||||
<select name="data.attunement" data-dtype="Number">
|
||||
{{selectOptions config.attunements selected=data.attunement localize=true}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{!-- Weapon Status --}}
|
||||
<div class="form-group stacked">
|
||||
<label>{{ localize "SW5E.ItemWeaponStatus" }}</label>
|
||||
|
@ -71,9 +78,6 @@
|
|||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.identified" {{checked data.identified}}/> {{ localize "SW5E.Identified" }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="data.attuned" {{checked data.attuned}}/> {{ localize "SW5E.Attuned" }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue