forked from GitHub-Mirrors/foundry-sw5e
Update Core to 1.4.1
Update Core to 1.4.1 and internal Version to 1.4.1.R1-A8
This commit is contained in:
parent
f16383841b
commit
5bb253d9c3
56 changed files with 5440 additions and 3827 deletions
|
@ -1,12 +1,16 @@
|
|||
import Actor5e from "../../entity.js";
|
||||
import Item5e from "../../../item/entity.js";
|
||||
import ProficiencySelector from "../../../apps/proficiency-selector.js";
|
||||
import PropertyAttribution from "../../../apps/property-attribution.js";
|
||||
import TraitSelector from "../../../apps/trait-selector.js";
|
||||
import ActorArmorConfig from "../../../apps/actor-armor.js";
|
||||
import ActorSheetFlags from "../../../apps/actor-flags.js";
|
||||
import ActorHitDiceConfig from "../../../apps/hit-dice-config.js";
|
||||
import ActorMovementConfig from "../../../apps/movement-config.js";
|
||||
import ActorSensesConfig from "../../../apps/senses-config.js";
|
||||
import ActorTypeConfig from "../../../apps/actor-type.js";
|
||||
import {SW5E} from "../../../config.js";
|
||||
import {onManageActiveEffect, prepareActiveEffectCategories} from "../../../effects.js";
|
||||
import ActiveEffect5e from "../../../active-effect.js";
|
||||
|
||||
/**
|
||||
* Extend the basic ActorSheet class to suppose SW5e-specific logic and functionality.
|
||||
|
@ -85,6 +89,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
// The Actor's data
|
||||
const actorData = this.actor.data.toObject(false);
|
||||
const source = this.actor.data._source.data;
|
||||
data.actor = actorData;
|
||||
data.data = actorData.data;
|
||||
|
||||
|
@ -105,6 +110,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
abl.icon = this._getProficiencyIcon(abl.proficient);
|
||||
abl.hover = CONFIG.SW5E.proficiencyLevels[abl.proficient];
|
||||
abl.label = CONFIG.SW5E.abilities[a];
|
||||
abl.baseProf = source.abilities[a].proficient;
|
||||
}
|
||||
|
||||
// Skills
|
||||
|
@ -117,6 +123,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
skl.label = CONFIG.SW5E.starshipSkills[s];
|
||||
} else {
|
||||
skl.label = CONFIG.SW5E.skills[s];
|
||||
skl.baseValue = source.skills[s].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +141,15 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
this._prepareItems(data);
|
||||
|
||||
// Prepare active effects
|
||||
data.effects = prepareActiveEffectCategories(this.actor.effects);
|
||||
data.effects = ActiveEffect5e.prepareActiveEffectCategories(this.actor.effects);
|
||||
|
||||
// Prepare warnings
|
||||
data.warnings = this.actor._preparationWarnings;
|
||||
|
||||
// Prepare property attributions
|
||||
this.attribution = {
|
||||
"attributes.ac": this._prepareArmorClassAttribution(actorData.data)
|
||||
};
|
||||
|
||||
// Return data to the sheet
|
||||
return data;
|
||||
|
@ -204,6 +219,105 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Produce a list of armor class attribution objects.
|
||||
* @param {object} data Actor data to determine the attributions from.
|
||||
* @return {AttributionDescription[]} List of attribution descriptions.
|
||||
*/
|
||||
_prepareArmorClassAttribution(data) {
|
||||
const ac = data.attributes.ac;
|
||||
const cfg = CONFIG.SW5E.armorClasses[ac.calc];
|
||||
const attribution = [];
|
||||
|
||||
// Base AC Attribution
|
||||
switch (ac.calc) {
|
||||
// Flat AC
|
||||
case "flat":
|
||||
return [
|
||||
{
|
||||
label: game.i18n.localize("SW5E.ArmorClassFlat"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||
value: ac.flat
|
||||
}
|
||||
];
|
||||
|
||||
// Natural armor
|
||||
case "natural":
|
||||
attribution.push({
|
||||
label: game.i18n.localize("SW5E.ArmorClassNatural"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||
value: ac.flat
|
||||
});
|
||||
break;
|
||||
|
||||
// Equipment-based AC
|
||||
case "default":
|
||||
const hasArmor = !!this.actor.armor;
|
||||
attribution.push({
|
||||
label: hasArmor ? this.actor.armor.name : game.i18n.localize("SW5E.ArmorClassUnarmored"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||
value: hasArmor ? this.actor.armor.data.data.armor.value : 10
|
||||
});
|
||||
if (ac.dex !== 0) {
|
||||
attribution.push({
|
||||
label: game.i18n.localize("SW5E.AbilityDex"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||
value: ac.dex
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
// Other AC formula
|
||||
default:
|
||||
const formula = ac.calc === "custom" ? ac.formula : cfg.formula;
|
||||
let base = ac.base;
|
||||
const dataRgx = new RegExp(/@([a-z.0-9_\-]+)/gi);
|
||||
for (const [match, term] of formula.matchAll(dataRgx)) {
|
||||
const value = foundry.utils.getProperty(data, term);
|
||||
if (term === "attributes.ac.base" || value === 0) continue;
|
||||
if (Number.isNumeric(value)) base -= Number(value);
|
||||
attribution.push({
|
||||
label: match,
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||
value: foundry.utils.getProperty(data, term)
|
||||
});
|
||||
}
|
||||
attribution.unshift({
|
||||
label: game.i18n.localize("SW5E.PropertyBase"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||
value: base
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Shield
|
||||
if (ac.shield !== 0)
|
||||
attribution.push({
|
||||
label: this.actor.shield?.name ?? game.i18n.localize("SW5E.EquipmentShield"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||
value: ac.shield
|
||||
});
|
||||
|
||||
// Bonus
|
||||
if (ac.bonus !== 0)
|
||||
attribution.push({
|
||||
label: game.i18n.localize("SW5E.Bonus"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||
value: ac.bonus
|
||||
});
|
||||
|
||||
// Cover
|
||||
if (ac.cover !== 0)
|
||||
attribution.push({
|
||||
label: game.i18n.localize("SW5E.Cover"),
|
||||
mode: CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||
value: ac.cover
|
||||
});
|
||||
return attribution;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -215,10 +329,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
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
|
||||
languages: CONFIG.SW5E.languages
|
||||
};
|
||||
for (let [t, choices] of Object.entries(map)) {
|
||||
const trait = traits[t];
|
||||
|
@ -238,6 +349,14 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
}
|
||||
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||
}
|
||||
|
||||
// Populate and localize proficiencies
|
||||
for (const t of ["armor", "weapon", "tool"]) {
|
||||
const trait = traits[`${t}Prof`];
|
||||
if (!trait) continue;
|
||||
Actor5e.prepareProficiencies(trait, t);
|
||||
trait.cssClass = !isObjectEmpty(trait.selected) ? "" : "inactive";
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -414,6 +533,8 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
// View Item Sheets
|
||||
html.find(".item-edit").click(this._onItemEdit.bind(this));
|
||||
// Property attributions
|
||||
html.find(".attributable").mouseover(this._onPropertyAttribution.bind(this));
|
||||
|
||||
// Editable Only Listeners
|
||||
if (this.isEditable) {
|
||||
|
@ -429,6 +550,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find(".skill-proficiency").on("click contextmenu", this._onCycleSkillProficiency.bind(this));
|
||||
|
||||
// Trait Selector
|
||||
html.find(".proficiency-selector").click(this._onProficiencySelector.bind(this));
|
||||
html.find(".trait-selector").click(this._onTraitSelector.bind(this));
|
||||
|
||||
// Configure Special Flags
|
||||
|
@ -446,7 +568,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
html.find(".decrement-class-level").click(this._onDecrementClassLevel.bind(this));
|
||||
|
||||
// Active Effect management
|
||||
html.find(".effect-control").click((ev) => onManageActiveEffect(ev, this.actor));
|
||||
html.find(".effect-control").click((ev) => ActiveEffect5e.onManageActiveEffect(ev, this.actor));
|
||||
}
|
||||
|
||||
// Owner Only Listeners
|
||||
|
@ -474,7 +596,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Iinitialize Item list filters by activating the set of filters which are currently applied
|
||||
* Initialize Item list filters by activating the set of filters which are currently applied
|
||||
* @private
|
||||
*/
|
||||
_initializeFilterItemList(i, ul) {
|
||||
|
@ -517,6 +639,9 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
const button = event.currentTarget;
|
||||
let app;
|
||||
switch (button.dataset.action) {
|
||||
case "armor":
|
||||
app = new ActorArmorConfig(this.object);
|
||||
break;
|
||||
case "hit-dice":
|
||||
app = new ActorHitDiceConfig(this.object);
|
||||
break;
|
||||
|
@ -530,7 +655,7 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
app = new ActorSensesConfig(this.object);
|
||||
break;
|
||||
case "type":
|
||||
new ActorTypeConfig(this.object).render(true);
|
||||
app = new ActorTypeConfig(this.object);
|
||||
break;
|
||||
}
|
||||
app?.render(true);
|
||||
|
@ -545,22 +670,19 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
*/
|
||||
_onCycleSkillProficiency(event) {
|
||||
event.preventDefault();
|
||||
const field = $(event.currentTarget).siblings('input[type="hidden"]');
|
||||
const field = event.currentTarget.previousElementSibling;
|
||||
const skillName = field.parentElement.dataset.skill;
|
||||
const source = this.actor.data._source.data.skills[skillName];
|
||||
if (!source) return;
|
||||
|
||||
// Get the current level and the array of levels
|
||||
const level = parseFloat(field.val());
|
||||
// Cycle to the next or previous skill level
|
||||
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]);
|
||||
}
|
||||
let idx = levels.indexOf(source.value);
|
||||
const next = idx + (event.type === "click" ? 1 : 3);
|
||||
field.value = levels[next % 4];
|
||||
|
||||
// Update the field value and save the form
|
||||
this._onSubmit(event);
|
||||
return this._onSubmit(event);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
@ -674,7 +796,12 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
if (itemData.type === "consumable" && itemData.flags.core?.sourceId) {
|
||||
const similarItem = this.actor.items.find((i) => {
|
||||
const sourceId = i.getFlag("core", "sourceId");
|
||||
return sourceId && sourceId === itemData.flags.core?.sourceId && i.type === "consumable";
|
||||
return (
|
||||
sourceId &&
|
||||
sourceId === itemData.flags.core?.sourceId &&
|
||||
i.type === "consumable" &&
|
||||
i.name === itemData.name
|
||||
);
|
||||
});
|
||||
if (similarItem && itemData.name !== "Power Cell") {
|
||||
// Always create a new powercell instead of increasing quantity
|
||||
|
@ -901,6 +1028,22 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle displaying the property attribution tooltip when a property is hovered over.
|
||||
* @param {Event} event The originating mouse event.
|
||||
* @private
|
||||
*/
|
||||
async _onPropertyAttribution(event) {
|
||||
const existingTooltip = event.currentTarget.querySelector("div.tooltip");
|
||||
const property = event.currentTarget.dataset.property;
|
||||
if (existingTooltip || !property || !this.attribution) return;
|
||||
|
||||
let html = await new PropertyAttribution(this.object, this.attribution, property).renderTooltip();
|
||||
event.currentTarget.insertAdjacentElement("beforeend", html[0]);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle rolling an Ability check, either a test or a saving throw
|
||||
* @param {Event} event The originating click event
|
||||
|
@ -957,6 +1100,22 @@ export default class ActorSheet5e extends ActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the ProficiencySelector application to configure armor, weapon, and tool proficiencies.
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onProficiencySelector(event) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
const label = a.parentElement.querySelector("label");
|
||||
const options = {name: a.dataset.target, title: label.innerText, type: a.dataset.type};
|
||||
if (options.type === "tool") options.sortCategories = true;
|
||||
return new ProficiencySelector(this.actor, options).render(true);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle spawning the TraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
|
|
|
@ -62,6 +62,10 @@ export default class ActorSheet5eCharacterNew extends ActorSheet5e {
|
|||
return [c.data.data.archetype, c.name, c.data.data.levels].filterJoin(" ");
|
||||
})
|
||||
.join(", ");
|
||||
// Weight unit
|
||||
sheetData["weightUnit"] = game.settings.get("sw5e", "metricWeightUnits")
|
||||
? game.i18n.localize("SW5E.AbbreviationKgs")
|
||||
: game.i18n.localize("SW5E.AbbreviationLbs");
|
||||
|
||||
// Return data for rendering
|
||||
return sheetData;
|
||||
|
|
|
@ -111,9 +111,28 @@ export default class ActorSheet5eNPCNew extends ActorSheet5e {
|
|||
|
||||
// Creature Type
|
||||
data.labels["type"] = this.actor.labels.creatureType;
|
||||
|
||||
// Armor Type
|
||||
data.labels["armorType"] = this.getArmorLabel();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Format NPC armor information into a localized string.
|
||||
* @return {string} Formatted armor label.
|
||||
*/
|
||||
getArmorLabel() {
|
||||
const ac = this.actor.data.data.attributes.ac;
|
||||
const label = [];
|
||||
if (ac.calc === "default") label.push(this.actor.armor?.name || game.i18n.localize("SW5E.ArmorClassUnarmored"));
|
||||
else label.push(game.i18n.localize(CONFIG.SW5E.armorClasses[ac.calc].label));
|
||||
if (this.actor.shield) label.push(this.actor.shield.name);
|
||||
return label.filterJoin(", ");
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Object Updates */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
@ -13,7 +13,7 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
classes: ["sw5e", "sheet", "actor", "vehicle"],
|
||||
width: 605,
|
||||
width: 720,
|
||||
height: 680
|
||||
});
|
||||
}
|
||||
|
@ -47,10 +47,17 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
_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;
|
||||
|
||||
const currencyPerWeight = game.settings.get("sw5e", "metricWeightUnits")
|
||||
? CONFIG.SW5E.encumbrance.currencyPerWeight.metric
|
||||
: CONFIG.SW5E.encumbrance.currencyPerWeight.imperial;
|
||||
|
||||
totalWeight += totalCoins / currencyPerWeight;
|
||||
|
||||
// Vehicle weights are an order of magnitude greater.
|
||||
totalWeight /= CONFIG.SW5E.encumbrance.vehicleWeightMultiplier;
|
||||
totalWeight /= game.settings.get("sw5e", "metricWeightUnits")
|
||||
? CONFIG.SW5E.encumbrance.vehicleWeightMultiplier.metric
|
||||
: CONFIG.SW5E.encumbrance.vehicleWeightMultiplier.imperial;
|
||||
|
||||
// Compute overall encumbrance
|
||||
const max = actorData.data.attributes.capacity.cargo;
|
||||
|
@ -80,12 +87,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
// 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 === 0.5) item.cover = "½";
|
||||
else if (item.data.cover === 0.75) item.cover = "¾";
|
||||
else if (item.data.cover === null) item.cover = "—";
|
||||
if (item.crew < 1 || item.crew === null) item.crew = "—";
|
||||
}
|
||||
|
||||
// Prepare vehicle weapons
|
||||
|
@ -114,7 +119,8 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
{
|
||||
label: game.i18n.localize("SW5E.Quantity"),
|
||||
css: "item-qty",
|
||||
property: "data.quantity"
|
||||
property: "data.quantity",
|
||||
editable: "Number"
|
||||
},
|
||||
{
|
||||
label: game.i18n.localize("SW5E.AC"),
|
||||
|
@ -138,14 +144,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
actions: {
|
||||
label: game.i18n.localize("SW5E.ActionPl"),
|
||||
items: [],
|
||||
hasActions: true,
|
||||
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",
|
||||
|
@ -179,6 +181,13 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
}
|
||||
};
|
||||
|
||||
data.items.forEach((item) => {
|
||||
item.hasUses = item.data.uses && item.data.uses.max > 0;
|
||||
item.isOnCooldown =
|
||||
item.data.recharge && !!item.data.recharge.value && item.data.recharge.charged === false;
|
||||
item.isDepleted = item.isOnCooldown && item.data.uses.per && item.data.uses.value > 0;
|
||||
});
|
||||
|
||||
const cargo = {
|
||||
crew: {
|
||||
label: game.i18n.localize("SW5E.VehicleCrew"),
|
||||
|
@ -284,6 +293,10 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
.click((evt) => evt.target.select())
|
||||
.change(this._onCargoRowChange.bind(this));
|
||||
|
||||
html.find(".item-qty:not(.cargo) input")
|
||||
.click((evt) => evt.target.select())
|
||||
.change(this._onQtyChange.bind(this));
|
||||
|
||||
if (this.actor.data.data.attributes.actions.stations) {
|
||||
html.find(".counter.actions, .counter.action-thresholds").hide();
|
||||
}
|
||||
|
@ -416,6 +429,24 @@ export default class ActorSheet5eVehicle extends ActorSheet5e {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Special handling for editing quantity value of equipment and weapons inside the features tab.
|
||||
* @param event {Event}
|
||||
* @returns {Promise<Item>}
|
||||
* @private
|
||||
*/
|
||||
|
||||
_onQtyChange(event) {
|
||||
event.preventDefault();
|
||||
const itemID = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const item = this.actor.items.get(itemID);
|
||||
const qty = parseInt(event.currentTarget.value);
|
||||
event.currentTarget.value = qty;
|
||||
return item.update({"data.quantity": qty});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle toggling an item's crewed status.
|
||||
* @param event {Event}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue