Skip to content
Open
13 changes: 13 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5380,12 +5380,25 @@
"DND5E.TimeYear": "Years",
"DND5E.TMP": "TMP",
"DND5E.ToHit": "To Hit",

"DND5E.TOKEN": {
"LockScale": {
"Hint": "Prevent a scaling adjustment from being applied to Token if Actor's size is small.",
"Label": "Lock Scale"
},
"LockSize": {
"Hint": "Prevent this Token's size from being modified when its Actor's size is changed.",
"Label": "Lock Size"
}
},

"DND5E.Tokens": {
"NoneSelected": "No Tokens Selected",
"NoneTargeted": "No Tokens Targeted",
"Selected": "Selected",
"Targeted": "Targeted"
},

"DND5E.ToggleDescription": "Toggle Description",

"DND5E.TOOL": {
Expand Down
45 changes: 42 additions & 3 deletions module/applications/token-config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getHumanReadableAttributeLabel } from "../utils.mjs";
import MovementSensesConfig from "./shared/movement-senses-config.mjs";

const { BooleanField } = foundry.data.fields;

/**
* Custom token configuration application for handling dynamic rings & resource labels.
*/
Expand All @@ -10,8 +12,9 @@ export class TokenConfig5e extends foundry.applications.sheets.TokenConfig {
async _onRender(context, options) {
await super._onRender(context, options);
if ( !this.rendered ) return;
this._prepareResourceLabels(this.element);
this._applySenseSyncNotice(this.element);
this._applyTokenScaleLock(this.element);
this._prepareResourceLabels(this.element);
}

/* -------------------------------------------- */
Expand All @@ -32,11 +35,44 @@ export class TokenConfig5e extends foundry.applications.sheets.TokenConfig {
return context;
}

/* -------------------------------------------- */
/* Appearance */
/* -------------------------------------------- */

/**
* Add control to link token scale to actor scale.
* @param {HTMLElement} html The rendered markup.
* @protected
*/
_applyTokenScaleLock(html) {
if ( html.querySelector('[name="flags.dnd5e.lockScale"]') ) return;

const lockSize = new BooleanField().toFormGroup({
hint: _loc("DND5E.TOKEN.LockSize.Hint"),
label: _loc("DND5E.TOKEN.LockSize.Label")
}, {
name: "flags.dnd5e.lockSize",
value: this.token._source.flags.dnd5e?.lockSize
});
const lockScale = new BooleanField().toFormGroup({
hint: _loc("DND5E.TOKEN.LockScale.Hint"),
label: _loc("DND5E.TOKEN.LockScale.Label")
}, {
name: "flags.dnd5e.lockScale",
value: this.token._source.flags.dnd5e?.lockScale
});

const lockRotation = html.querySelector('.form-group:has([name="lockRotation"])');
lockRotation.after(lockSize, lockScale);
}

/* -------------------------------------------- */
/* Resources */
/* -------------------------------------------- */

/**
* Adds charge based items as attributes for the current token.
* @param {object} attributes The attribute groups to add the item entries to.
* @param {object} attributes The attribute groups to add the item entries to.
* @protected
*/
_addItemAttributes(attributes) {
Expand Down Expand Up @@ -79,6 +115,8 @@ export class TokenConfig5e extends foundry.applications.sheets.TokenConfig {
}
}

/* -------------------------------------------- */
/* Vision */
/* -------------------------------------------- */

/**
Expand Down Expand Up @@ -133,8 +171,9 @@ export class PrototypeTokenConfig5e extends foundry.applications.sheets.Prototyp
async _onRender(context, options) {
await super._onRender(context, options);
if ( !this.rendered ) return;
TokenConfig5e.prototype._prepareResourceLabels.call(this, this.element);
TokenConfig5e.prototype._applySenseSyncNotice.call(this, this.element);
TokenConfig5e.prototype._applyTokenScaleLock.call(this, this.element);
TokenConfig5e.prototype._prepareResourceLabels.call(this, this.element);
}

/* -------------------------------------------- */
Expand Down
14 changes: 7 additions & 7 deletions module/documents/active-effect.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,13 @@ export default class ActiveEffect5e extends DependentDocumentMixin(ActiveEffect)
/** @inheritDoc */
static applyChange(model, change, options={}) {
// Apply shims to moved fields
change = change.effect._applyChangeShim(change);
change = change.effect?._applyChangeShim(change) ?? change;

if ( (model instanceof foundry.abstract.Document)
&& !change.effect._checkCondition(change, options.replacementData) ) return {};
&& !this._checkCondition(change, options.replacementData) ) return {};

// Handle special actor flags
if ( change.key.startsWith("flags.dnd5e.") ) change = change.effect._prepareFlagChange(model, change);
if ( change.key.startsWith("flags.dnd5e.") ) change = change.effect?._prepareFlagChange(model, change) ?? change;

// Properly handle formulas that don't exist as part of the data model
if ( ActiveEffect5e.FORMULA_FIELDS.has(change.key) ) {
Expand All @@ -294,7 +294,7 @@ export default class ActiveEffect5e extends DependentDocumentMixin(ActiveEffect)

// Handle activity-targeted changes
if ( (change.key.startsWith("activities[") || change.key.startsWith("system.activities."))
&& (model instanceof Item) ) return change.effect.applyActivity(model, change, options);
&& (model instanceof Item) ) return change.effect?.applyActivity(model, change, options);

// Handle hiding items
if ( (change.key === "items.hidden") && (model instanceof Actor) ) {
Expand Down Expand Up @@ -515,13 +515,13 @@ export default class ActiveEffect5e extends DependentDocumentMixin(ActiveEffect)
* @returns {boolean}
* @internal
*/
_checkCondition(change, conditionData) {
static _checkCondition(change, conditionData) {
if ( conditionData && !CONFIG.ActiveEffect.changeTypes[change.type]?.skipConditions ) {
if ( this.system.conditions?.check(conditionData) === false ) return false;
if ( change.effect?.system.conditions?.check(conditionData) === false ) return false;
if ( change.conditions?.check(conditionData) === false ) return false;
}

const originalChange = this.system.changes.find(c => c._id === change._id);
const originalChange = change.effect?.system.changes.find(c => c._id === change._id);
if ( originalChange ) originalChange.applied = true;

return true;
Expand Down
26 changes: 25 additions & 1 deletion module/documents/actor/actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ActorDeltasField } from "../../data/chat-message/fields/deltas-field.mj
import AdvantageModeField from "../../data/fields/advantage-mode-field.mjs";
import TransformationSetting from "../../data/settings/transformation-setting.mjs";
import { createRollLabel } from "../../enrichers.mjs";
import { Filter } from "../../filter.mjs";
import {
convertTime, defaultUnits, formatLength, formatNumber, formatTime, simplifyBonus, staticID
} from "../../utils.mjs";
Expand Down Expand Up @@ -372,7 +373,30 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
}
}

return super.applyActiveEffects(phase);
super.applyActiveEffects(phase);
if ( (phase !== "initial") || !this.system.isCreature ) return;

// Translate this Actor's size category into Token changes
const sizeData = CONFIG.DND5E.actorSizes[this.system.traits?.size];
if ( !sizeData ) return;
const tokenSize = sizeData.token ?? 1;
this.tokenActiveEffectChanges[phase].push(...["width", "height", "depth"].map(key => ({
Comment thread
arbron marked this conversation as resolved.
key, phase,
type: "override",
priority: 0,
value: tokenSize,
conditions: new Filter({ o: "NOT", v: { k: "token.flags.dnd5e.lockSize", v: true } })
})));
const tokenScale = sizeData.dynamicTokenScale ?? 1;
Comment thread
arbron marked this conversation as resolved.
if ( tokenScale !== 1 ) {
this.tokenActiveEffectChanges[phase].push(...["texture.scaleX", "texture.scaleY"].map(key => ({
key, phase,
type: "multiply",
priority: 0,
value: tokenScale,
conditions: new Filter({ o: "NOT", v: { k: "token.flags.dnd5e.lockScale", v: true } })
})));
}
}

/* -------------------------------------------- */
Expand Down
35 changes: 18 additions & 17 deletions module/documents/token.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ export default class TokenDocument5e extends SystemFlagsMixin(TokenDocument) {

/* -------------------------------------------- */

/** @inheritDoc */
_getReplacementData() {
return {
...super._getReplacementData(),
token: this
};
}

/* -------------------------------------------- */

/** @inheritDoc */
static getTrackedAttributeChoices(attributes) {
const groups = super.getTrackedAttributeChoices(attributes);
Expand All @@ -173,23 +183,6 @@ export default class TokenDocument5e extends SystemFlagsMixin(TokenDocument) {
return groups;
}

/* -------------------------------------------- */

/** @override */
prepareData() {
super.prepareData();
if ( !this.hasDynamicRing ) return;
let size = this.baseActor?.system.traits?.size;
if ( !this.actorLink ) {
const deltaSize = this.delta?.system.traits?.size;
if ( deltaSize ) size = deltaSize;
}
if ( !size ) return;
const dts = CONFIG.DND5E.actorSizes[size].dynamicTokenScale ?? 1;
this.texture.scaleX = this._source.texture.scaleX * dts;
this.texture.scaleY = this._source.texture.scaleY * dts;
}

/* -------------------------------------------- */
/* Movement */
/* -------------------------------------------- */
Expand Down Expand Up @@ -315,6 +308,14 @@ export default class TokenDocument5e extends SystemFlagsMixin(TokenDocument) {

/* -------------------------------------------- */

/** @override */
async _onOverrideSize(changes) {
if ( !this.persisted || this.object?.isPreview ) this.updateSource(changes);
else if ( game.user.isActiveGM ) this.update(changes);
}

/* -------------------------------------------- */

/** @inheritDoc */
_onRelatedUpdate(update={}, operation={}) {
super._onRelatedUpdate(update, operation);
Expand Down
18 changes: 14 additions & 4 deletions module/migration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export async function migrateWorld({ bypassVersionCheck=false }={}) {
// Migrate Actor Override Tokens
for ( const s of game.scenes ) {
try {
const updateData = migrateSceneData(s, migrationData);
const updateData = migrateSceneData(s, migrationData, { bypassVersionCheck });
if ( !foundry.utils.isEmpty(updateData) ) {
log(`Migrating Scene document ${s.name}`);
await s.update(updateData, {enforceTypes: false, render: false});
Expand Down Expand Up @@ -502,6 +502,10 @@ export async function migrateSettings() {
export function migrateActorData(actor, actorData, migrationData, flags={}, { actorUuid }={}) {
const updateData = {};
_migrateTokenImage(actorData, updateData);
if ( (flags.bypassVersionCheck || foundry.utils.isNewerVersion("6.0.0", actorData._stats?.systemVersion))
&& !actor.isToken && (actor.system.traits?.size === "sm") && !actorData.prototypeToken.ring.enabled ) {
updateData["prototypeToken.flags.dnd5e.lockScale"] = true;
}
_migrateActorAC(actorData, updateData);
_migrateActorFlags(actorData, updateData);
_migrateActorMovementSenses(actorData, updateData);
Expand Down Expand Up @@ -840,15 +844,21 @@ export function migrateRollTableData(table, migrationData) {
/**
* Migrate a single Scene document to incorporate changes to the data model of its actor data overrides
* Return an Object of updateData to be applied
* @param {object} scene The Scene data to Update
* @param {object} [migrationData] Additional data to perform the migration
* @param {object} scene The Scene data to Update.
* @param {object} [migrationData] Additional data to perform the migration.
* @param {object} [options]
* @param {boolean} [options.bypassVersionCheck=false] Bypass certain migration restrictions gated behind system
* version stored in item stats.
* @returns {object} The updateData to apply
*/
export function migrateSceneData(scene, migrationData) {
export function migrateSceneData(scene, migrationData, { bypassVersionCheck }={}) {
const tokens = scene.tokens.reduce((arr, token) => {
const t = token instanceof foundry.abstract.DataModel ? token.toObject() : token;
const update = {};
_migrateTokenImage(t, update);
const size = t.delta?.system?.traits?.size ?? game.actors.get(t.actorId)?.system?.traits?.size;
if ( (bypassVersionCheck || foundry.utils.isNewerVersion("6.0.0", scene._stats?.systemVersion))
&& (size === "sm") && !t.ring.enabled ) update["flags.dnd5e.lockScale"] = true;
if ( !game.actors.has(t.actorId) ) update.actorId = null;
if ( !foundry.utils.isEmpty(update) ) arr.push({ ...update, _id: t._id });
return arr;
Expand Down