Skip to content

Commit f06b1a2

Browse files
committed
feat: convert lights and token sight data in scenes
1 parent 84ff5b7 commit f06b1a2

File tree

5 files changed

+147
-37
lines changed

5 files changed

+147
-37
lines changed

src/module/documents/actor.mjs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ import {
1010
getSystemProperty,
1111
getUnitFromString,
1212
getUnitSystem,
13+
isEmpty,
14+
MODULE_ID,
1315
setSystemProperty,
16+
UNIT_SYSTEMS,
17+
UNITS,
1418
} from "../utils.mjs";
1519
import { convertString } from "../strings.mjs";
20+
import { convertTokenVisionData } from "./scene.mjs";
1621

1722
const actorDetailFields = ["appearance", "trait", "biography.value", "ideal", "bond", "flaw"].map(
1823
(field) => `details.${field}`,
@@ -35,7 +40,7 @@ export const convertActorData = (actor, options = {}) => {
3540
?.map((item) => {
3641
const itemData = item instanceof CONFIG.Item.documentClass ? item.toObject() : item;
3742
const itemUpdateData = convertItemData(itemData, options);
38-
if (foundry.utils.isObjectEmpty(itemUpdateData)) return null;
43+
if (isEmpty(itemUpdateData)) return null;
3944
return { _id: item._id, ...itemUpdateData };
4045
})
4146
.filter(Boolean);
@@ -52,7 +57,7 @@ export const convertActorData = (actor, options = {}) => {
5257
// Senses and Movement
5358
for (const field of ["senses", "movement"]) {
5459
const fieldData = getSystemProperty(actor, `attributes.${field}`);
55-
if (fieldData) {
60+
if (fieldData && fieldData.units) {
5661
const units = getUnitFromString(fieldData.units);
5762
if (getUnitSystem(units) !== target) {
5863
setSystemProperty(updateData, `attributes.${field}.units`, getOtherUnit(units));
@@ -70,6 +75,29 @@ export const convertActorData = (actor, options = {}) => {
7075
}
7176
}
7277

78+
// Prototype token
79+
const prototypeToken = game.release.generation < 10 ? actor.token : actor.prototypeToken;
80+
if (prototypeToken) {
81+
const movementUnits = getSystemProperty(actor, "attributes.movement.units");
82+
const sensesUnits = getSystemProperty(actor, "attributes.senses.units");
83+
const inferredUnitSystem = getUnitSystem(getUnitFromString(movementUnits ?? sensesUnits));
84+
85+
const currentUnitSystem =
86+
actor.flags[MODULE_ID]?.unitSystem ?? inferredUnitSystem ?? UNIT_SYSTEMS.IMPERIAL;
87+
const tokenUpdateData = convertTokenVisionData(prototypeToken, {
88+
current: currentUnitSystem === UNIT_SYSTEMS.IMPERIAL ? UNITS.FEET : UNITS.METER,
89+
...options,
90+
});
91+
if (!isEmpty(tokenUpdateData)) {
92+
if (game.release.generation < 10) {
93+
updateData.token = tokenUpdateData;
94+
} else {
95+
updateData.prototypeToken = tokenUpdateData;
96+
}
97+
foundry.utils.setProperty(updateData, `flags.${MODULE_ID}.unitSystem`, target);
98+
}
99+
}
100+
73101
return updateData;
74102
};
75103

src/module/documents/item.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const convertItemData = (data, options = {}) => {
4141

4242
// Ability range
4343
if (systemData.range) {
44-
const units = getUnitFromString(systemData.range.units);
44+
const units = getUnitFromString(systemData.range.units ?? "");
4545
// Only handle metric/imperial units, not touch etc.
4646
if (units) {
4747
setSystemProperty(updateData, "range.units", getOtherUnit(units));
@@ -52,7 +52,7 @@ export const convertItemData = (data, options = {}) => {
5252

5353
// Target data
5454
if (systemData.target) {
55-
const units = getUnitFromString(systemData.target.units);
55+
const units = getUnitFromString(systemData.target.units ?? "");
5656
// Only handle metric/imperial units, not touch etc.
5757
if (units) {
5858
setSystemProperty(updateData, "target.units", getOtherUnit(units));

src/module/documents/journal.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// SPDX-License-Identifier: MIT
44

5-
import { getConversionOptions } from "../utils.mjs";
5+
import { getConversionOptions, isEmpty } from "../utils.mjs";
66
import { convertString } from "../strings.mjs";
77

88
/**
@@ -37,7 +37,7 @@ export const convertJournalEntryData = (journalEntry, options = {}) => {
3737
if (convertedString !== pageData.text.content)
3838
foundry.utils.setProperty(pageUpdateData, "text.content", convertedString);
3939
}
40-
if (foundry.utils.isObjectEmpty(pageUpdateData)) return null;
40+
if (isEmpty(pageUpdateData)) return null;
4141
return { _id: page._id, ...pageUpdateData };
4242
})
4343
.filter(Boolean);

src/module/documents/packs.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { convertActorData } from "./actor.mjs";
66
import { convertItemData } from "./item.mjs";
77
import { convertSceneData } from "./scene.mjs";
8-
import { getConversionOptions } from "../utils.mjs";
8+
import { getConversionOptions, isEmpty } from "../utils.mjs";
99
import { convertJournalEntryData } from "./journal.mjs";
1010

1111
/**
@@ -44,7 +44,7 @@ export const convertPack = async (pack, options) => {
4444
break;
4545
}
4646

47-
if (foundry.utils.isObjectEmpty(updateData)) continue;
47+
if (isEmpty(updateData)) continue;
4848
updates.push({ _id: document.id, ...updateData });
4949
}
5050

src/module/documents/scene.mjs

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
getOtherUnit,
99
getUnitFromString,
1010
getUnitSystem,
11+
isEmpty,
12+
UNITS,
1113
} from "../utils.mjs";
1214
import { convertActorData } from "./actor.mjs";
1315

@@ -20,7 +22,7 @@ import { convertActorData } from "./actor.mjs";
2022
* @returns {object} The update data
2123
*/
2224
export const convertSceneData = (scene, options = {}) => {
23-
const updateData = {};
25+
const sceneUpdateData = {};
2426
const { target } = getConversionOptions(options);
2527

2628
// Convert grid distance and units if necessary
@@ -30,43 +32,123 @@ export const convertSceneData = (scene, options = {}) => {
3032
const currentSystem = getUnitSystem(units);
3133
if (units && currentSystem !== target) {
3234
if (game.release.generation < 10) {
33-
updateData.gridUnits = getOtherUnit(units);
34-
updateData.gridDistance = convertDistance(currentGridDistance, units);
35+
sceneUpdateData.gridUnits = getOtherUnit(units);
36+
sceneUpdateData.gridDistance = convertDistance(currentGridDistance, units);
3537
} else {
36-
updateData.grid = {
38+
sceneUpdateData.grid = {
3739
units: getOtherUnit(units),
3840
distance: convertDistance(scene.grid.distance, units),
3941
};
4042
}
4143
}
4244

43-
const tokens = scene.tokens
44-
.map((token) => {
45-
const tokenData = "toObject" in token ? token.toObject() : token;
46-
if (!tokenData.actorId || tokenData.actorLink) {
47-
tokenData.actorData = {};
48-
} else if (!game.actors.has(tokenData.actorId)) {
49-
tokenData.actorId = null;
50-
tokenData.actorData = {};
51-
} else if (!tokenData.actorLink) {
52-
const actorData = tokenData.actorData;
53-
const actorUpdate = convertActorData(actorData, options);
45+
const sceneTokens = Array.isArray(scene.tokens) ? scene.tokens : scene.tokens.contents;
46+
const tokens = sceneTokens.flatMap((token) => {
47+
const tokenData = "toObject" in token ? token.toObject() : token;
48+
const tokenActorData = convertTokenActorData(tokenData, { target }) ?? {};
49+
const tokenVisionData =
50+
convertTokenVisionData(tokenData, { target: target, current: units }) ?? {};
51+
const updateData = { ...tokenActorData, ...tokenVisionData };
52+
if (!isEmpty(updateData)) {
53+
return [{ _id: tokenData._id, ...updateData }];
54+
} else {
55+
return [];
56+
}
57+
});
58+
59+
const sceneLights = Array.isArray(scene.lights) ? scene.lights : scene.lights.contents;
60+
const lights = sceneLights.flatMap((light) => {
61+
const updateData = convertLightData(light, { target, current: units });
62+
if (!isEmpty(updateData)) {
63+
return [{ _id: light._id, ...updateData }];
64+
} else {
65+
return [];
66+
}
67+
});
5468

55-
// Handle item updates
56-
if (!actorUpdate.items?.length) return;
57-
const updates = new Map(actorUpdate.items.map((item) => [item._id, item]));
58-
tokenData.actorData.items?.forEach((original) => {
59-
const update = updates.get(original._id);
60-
if (update) foundry.utils.mergeObject(original, update);
61-
});
62-
delete actorUpdate.items;
63-
foundry.utils.mergeObject(tokenData.actorData, actorUpdate);
64-
}
65-
return tokenData;
66-
})
67-
.filter(Boolean);
69+
return { ...sceneUpdateData, tokens, lights };
70+
};
6871

69-
return { ...updateData, tokens };
72+
/**
73+
* Converts a token's `actorData` to the other unit system.
74+
*
75+
*
76+
* @param {object} tokenData - A token's complete data object
77+
* @param {ConversionOptions} [options] - Options for conversion
78+
* @returns {{actorData: object}} A partial update object
79+
*/
80+
const convertTokenActorData = (tokenData, options = {}) => {
81+
if (!tokenData.actorId || tokenData.actorLink) {
82+
tokenData.actorData = {};
83+
} else if (!game.actors.has(tokenData.actorId)) {
84+
tokenData.actorId = null;
85+
tokenData.actorData = {};
86+
} else if (!tokenData.actorLink) {
87+
const actorData = tokenData.actorData;
88+
const actorUpdate = convertActorData(actorData, options);
89+
90+
// Handle item updates
91+
if (actorUpdate.items?.length) {
92+
const updates = new Map(actorUpdate.items.map((item) => [item._id, item]));
93+
tokenData.actorData.items?.forEach((original) => {
94+
const update = updates.get(original._id);
95+
if (update) foundry.utils.mergeObject(original, update);
96+
});
97+
delete actorUpdate.items;
98+
}
99+
foundry.utils.mergeObject(tokenData.actorData, actorUpdate);
100+
}
101+
return isEmpty(tokenData.actorData) ? {} : { actorData: tokenData.actorData };
102+
};
103+
104+
/**
105+
* Converts a token's `sight` data
106+
*
107+
* @param {object} tokenData - A {@link TokenDocument} or its data object
108+
* @param {ConversionOptions} [options] - Options for conversion
109+
* @returns {{sight?: TokenSightData}} A partial update object
110+
*/
111+
export const convertTokenVisionData = (tokenData, options = {}) => {
112+
const { target, current = UNITS.FEET } = options;
113+
const currentUnitSystem = getUnitSystem(current);
114+
if (target === currentUnitSystem) return {};
115+
if (game.release.generation < 10) {
116+
const { dimSight, brightSight } = tokenData;
117+
const updateData = {};
118+
if (dimSight) updateData.dimSight = convertDistance(dimSight, current);
119+
if (brightSight) updateData.brightSight = convertDistance(brightSight, current);
120+
return updateData;
121+
} else {
122+
const sight = tokenData.sight;
123+
if (!sight) return {};
124+
const { range } = sight;
125+
if (range) {
126+
sight.range = convertDistance(range, current);
127+
}
128+
return { sight };
129+
}
130+
};
131+
132+
/**
133+
* Converts a light's `dim` and `bright` values
134+
*
135+
* @param {AmbientLight | object} light
136+
* @param {ConversionOptions} [options]
137+
* @returns {{dim?: number, bright?: number}} A partial update object
138+
*/
139+
const convertLightData = (light, options = {}) => {
140+
const lightData = "toObject" in light ? light.toObject() : light;
141+
const { target, current = UNITS.FEET } = options;
142+
const currentUnitSystem = getUnitSystem(current);
143+
if (target === currentUnitSystem) return {};
144+
const { dim, bright } = lightData.config;
145+
if (dim) {
146+
lightData.config.dim = convertDistance(dim, current);
147+
}
148+
if (bright) {
149+
lightData.config.bright = convertDistance(bright, current);
150+
}
151+
return lightData;
70152
};
71153

72154
export const convertScene = async (scene, options) => {

0 commit comments

Comments
 (0)