This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
import DynamicRingData from "./ring-data.mjs";
/**
* The start and end radii of the token ring color band.
* @typedef {Object} RingColorBand
* @property {number} startRadius The starting normalized radius of the token ring color band.
* @property {number} endRadius The ending normalized radius of the token ring color band.
*/
/**
* Dynamic ring id.
* @typedef {string} DynamicRingId
*/
/**
* Token Ring configuration Singleton Class.
*
* @example Add a new custom ring configuration. Allow only ring pulse, ring gradient and background wave effects.
* const customConfig = new foundry.canvas.tokens.DynamicRingData({
* id: "myCustomRingId",
* label: "Custom Ring",
* effects: {
* RING_PULSE: "TOKEN.RING.EFFECTS.RING_PULSE",
* RING_GRADIENT: "TOKEN.RING.EFFECTS.RING_GRADIENT",
* BACKGROUND_WAVE: "TOKEN.RING.EFFECTS.BACKGROUND_WAVE"
* },
* spritesheet: "canvas/tokens/myCustomRings.json",
* framework: {
* shaderClass: MyCustomTokenRingSamplerShader,
* ringClass: TokenRing
* }
* });
* CONFIG.Token.ring.addConfig(customConfig.id, customConfig);
*
* @example Get a specific ring configuration
* const config = CONFIG.Token.ring.getConfig("myCustomRingId");
* console.log(config.spritesheet); // Output: canvas/tokens/myCustomRings.json
*
* @example Use a specific ring configuration
* const success = CONFIG.Token.ring.useConfig("myCustomRingId");
* console.log(success); // Output: true
*
* @example Get the labels of all configurations
* const configLabels = CONFIG.Token.ring.configLabels;
* console.log(configLabels);
* // Output:
* // {
* // "coreSteel": "Foundry VTT Steel Ring",
* // "coreBronze": "Foundry VTT Bronze Ring",
* // "myCustomRingId" : "My Super Power Ring"
* // }
*
* @example Get the IDs of all configurations
* const configIDs = CONFIG.Token.ring.configIDs;
* console.log(configIDs); // Output: ["coreSteel", "coreBronze", "myCustomRingId"]
*
* @example Create a hook to add a custom token ring configuration. This ring configuration will appear in the settings.
* Hooks.on("initializeDynamicTokenRingConfig", ringConfig => {
* const mySuperPowerRings = new foundry.canvas.tokens.DynamicRingData({
* id: "myCustomRingId",
* label: "My Super Power Rings",
* effects: {
* RING_PULSE: "TOKEN.RING.EFFECTS.RING_PULSE",
* RING_GRADIENT: "TOKEN.RING.EFFECTS.RING_GRADIENT",
* BACKGROUND_WAVE: "TOKEN.RING.EFFECTS.BACKGROUND_WAVE"
* },
* spritesheet: "canvas/tokens/mySuperPowerRings.json"
* });
* ringConfig.addConfig("mySuperPowerRings", mySuperPowerRings);
* });
*
* @example Activate color bands debugging visuals to ease configuration
* CONFIG.Token.ring.debugColorBands = true;
*/
export default class TokenRingConfig {
constructor() {
if ( TokenRingConfig.#instance ) {
throw new Error("An instance of TokenRingConfig has already been created. " +
"Use `CONFIG.Token.ring` to access it.");
}
TokenRingConfig.#instance = this;
}
/**
* The token ring config instance.
* @type {TokenRingConfig}
*/
static #instance;
/**
* To know if the ring config is initialized.
* @type {boolean}
*/
static #initialized = false;
/**
* To know if a Token Ring registration is possible.
* @type {boolean}
*/
static #closedRegistration = true;
/**
* Core token rings used in Foundry VTT.
* Each key is a string identifier for a ring, and the value is an object containing the ring's data.
* This object is frozen to prevent any modifications.
* @type {Readonly<Record<DynamicRingId, RingData>>}
*/
static CORE_TOKEN_RINGS = Object.freeze({
coreSteel: {
id: "coreSteel",
label: "TOKEN.RING.SETTINGS.coreSteel",
spritesheet: "canvas/tokens/rings-steel.json"
},
coreBronze: {
id: "coreBronze",
label: "TOKEN.RING.SETTINGS.coreBronze",
spritesheet: "canvas/tokens/rings-bronze.json"
}
});
/**
* Core token rings fit modes used in Foundry VTT.
* @type {Readonly<object>}
*/
static CORE_TOKEN_RINGS_FIT_MODES = Object.freeze({
subject: {
id: "subject",
label: "TOKEN.RING.SETTINGS.FIT_MODES.subject"
},
grid: {
id: "grid",
label: "TOKEN.RING.SETTINGS.FIT_MODES.grid"
}
});
/* -------------------------------------------- */
/**
* Register the token ring config and initialize it
*/
static initialize() {
// If token config is initialized
if ( this.#initialized ) {
throw new Error("The token configuration class can be initialized only once!")
}
// Open the registration window for the token rings
this.#closedRegistration = false;
// Add default rings
for ( const id in this.CORE_TOKEN_RINGS ) {
const config = new DynamicRingData(this.CORE_TOKEN_RINGS[id]);
CONFIG.Token.ring.addConfig(config.id, config);
}
// Call an explicit hook for token ring configuration
Hooks.callAll("initializeDynamicTokenRingConfig", CONFIG.Token.ring);
// Initialize token rings configuration
if ( !CONFIG.Token.ring.useConfig(game.settings.get("core", "dynamicTokenRing")) ) {
CONFIG.Token.ring.useConfig(this.CORE_TOKEN_RINGS.coreSteel.id);
}
// Close the registration window for the token rings
this.#closedRegistration = true;
this.#initialized = true;
}
/* -------------------------------------------- */
/**
* Register game settings used by the Token Ring
*/
static registerSettings() {
game.settings.register("core", "dynamicTokenRing", {
name: "TOKEN.RING.SETTINGS.label",
hint: "TOKEN.RING.SETTINGS.hint",
scope: "world",
config: true,
type: new foundry.data.fields.StringField({required: true, blank: false,
initial: this.CORE_TOKEN_RINGS.coreSteel.id,
choices: () => CONFIG.Token.ring.configLabels
}),
requiresReload: true
});
game.settings.register("core", "dynamicTokenRingFitMode", {
name: "TOKEN.RING.SETTINGS.FIT_MODES.label",
hint: "TOKEN.RING.SETTINGS.FIT_MODES.hint",
scope: "world",
config: true,
type: new foundry.data.fields.StringField({
required: true,
blank: false,
initial: this.CORE_TOKEN_RINGS_FIT_MODES.subject.id,
choices: Object.fromEntries(Object.entries(this.CORE_TOKEN_RINGS_FIT_MODES).map(([key, mode]) => [key, mode.label]))
}),
requiresReload: true
});
}
/* -------------------------------------------- */
/**
* Ring configurations.
* @type {Map<string, DynamicRingData>}
*/
#configs = new Map();
/**
* The current ring configuration.
* @type {DynamicRingData}
*/
#currentConfig;
/* -------------------------------------------- */
/* Properties */
/* -------------------------------------------- */
/**
* A mapping of token subject paths where modules or systems have configured subject images.
* @type {Record<string, string>}
*/
subjectPaths = {};
/**
* All color bands visual debug flag.
* @type {boolean}
*/
debugColorBands = false;
/**
* Get the current ring class.
* @type {typeof TokenRing} The current ring class.
*/
get ringClass() {
return this.#currentConfig.framework.ringClass;
}
set ringClass(value) {
this.#currentConfig.framework.ringClass = value;
}
/**
* Get the current effects.
* @type {Record<string, string>} The current effects.
*/
get effects() {
return this.#currentConfig.effects;
}
/**
* Get the current spritesheet.
* @type {string} The current spritesheet path.
*/
get spritesheet() {
return this.#currentConfig.spritesheet;
}
/**
* Get the current shader class.
* @type {typeof PrimaryBaseSamplerShader} The current shader class.
*/
get shaderClass() {
return this.#currentConfig.framework.shaderClass;
}
set shaderClass(value) {
this.#currentConfig.framework.shaderClass = value;
}
/**
* Get the current localized label.
* @returns {string}
*/
get label() {
return this.#currentConfig.label;
}
/**
* Get the current id.
* @returns {string}
*/
get id() {
return this.#currentConfig.id;
}
/* -------------------------------------------- */
/* Management */
/* -------------------------------------------- */
/**
* Is a custom fit mode active?
* @returns {boolean}
*/
get isGridFitMode() {
return game.settings.get("core","dynamicTokenRingFitMode")
=== this.constructor.CORE_TOKEN_RINGS_FIT_MODES.grid.id;
}
/* -------------------------------------------- */
/**
* Add a new ring configuration.
* @param {string} id The id of the ring configuration.
* @param {RingConfig} config The configuration object for the ring.
*/
addConfig(id, config) {
if ( this.constructor.#closedRegistration ) {
throw new Error("Dynamic Rings registration window is closed. You must register a dynamic token ring configuration during" +
" the `registerDynamicTokenRing` hook.");
}
this.#configs.set(id, config);
}
/* -------------------------------------------- */
/**
* Get a ring configuration.
* @param {string} id The id of the ring configuration.
* @returns {RingConfig} The ring configuration object.
*/
getConfig(id) {
return this.#configs.get(id);
}
/* -------------------------------------------- */
/**
* Use a ring configuration.
* @param {string} id The id of the ring configuration to use.
* @returns {boolean} True if the configuration was successfully set, false otherwise.
*/
useConfig(id) {
if ( this.#configs.has(id) ) {
this.#currentConfig = this.#configs.get(id);
return true;
}
return false;
}
/* -------------------------------------------- */
/**
* Get the IDs of all configurations.
* @returns {string[]} The names of all configurations.
*/
get configIDs() {
return Array.from(this.#configs.keys());
}
/* -------------------------------------------- */
/**
* Get the labels of all configurations.
* @returns {Record<string, string>} An object with configuration names as keys and localized labels as values.
*/
get configLabels() {
const labels = {};
for ( const [name, config] of this.#configs.entries() ) {
labels[name] = config.label;
}
return labels;
}
/* -------------------------------------------- */
/* Deprecations and Compatibility */
/* -------------------------------------------- */
/**
* @deprecated since v11
* @ignore
*/
get configNames() {
const msg = "TokenRingConfig#configNames is deprecated and replaced by TokenRingConfig#configIDs";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
return this.configIDs;
}
}