System adapters define how Stylish Action HUD interacts with different game systems. Each adapter translates system-specific data (attributes, items, spells) into a format the HUD can display.
All custom adapters should extend or implement the interface defined by BaseSystemAdapter.
class BaseSystemAdapter {
constructor() {
this.systemId = "generic";
}
// Required Methods
getStats(actor, configAttributes) { }
getConditions(actor) { }
getActionCategories(actor) { }
getSubMenuData(actor, categoryId) { }
// Optional Methods
updateAttribute(actor, path, input) { }
removeCondition(actor, conditionId) { }
executeAction(actor, actionId) { }
useItem(actor, itemId) { }
getTrackableAttributes(actor) { }
getDefaultAttributes() { }
getDefaultStatusEffects() { }
}
Use api.BaseSystemAdapter when a companion module needs to extend Stylish Action HUD's generic adapter behavior. This keeps the companion module on the public API surface instead of importing scripts/systems/base.js, which is an internal module file.
The safest timing is the API ready hook:
Hooks.once("stylish-action-hud.apiReady", (api) => {
const { BaseSystemAdapter } = api;
class MySystemAdapter extends BaseSystemAdapter {
constructor() {
super();
this.systemId = "my-system";
}
async getSubMenuData(actor, categoryId) {
if (String(categoryId).startsWith("custom-")) {
return super.getSubMenuData(actor, categoryId);
}
return this._getSystemSubMenuData(actor, categoryId, { label: categoryId });
}
}
api.registerSystemAdapter("my-system", MySystemAdapter, {
priority: 1,
source: "my-module",
isCompatible: (context) => context.system.id === "my-system",
});
});
If the adapter class lives in another file, export a factory that receives BaseSystemAdapter:
// adapter.js
export function createMySystemAdapter(BaseSystemAdapter) {
return class MySystemAdapter extends BaseSystemAdapter {
constructor() {
super();
this.systemId = "my-system";
}
};
}
// main.js
import { createMySystemAdapter } from "./adapter.js";
Hooks.once("stylish-action-hud.apiReady", (api) => {
const MySystemAdapter = createMySystemAdapter(api.BaseSystemAdapter);
api.registerSystemAdapter("my-system", MySystemAdapter, {
source: "my-module",
});
});
After stylish-action-hud.apiReady has fired, the same class is also available at game.modules.get("stylish-action-hud").api.BaseSystemAdapter and window.stylishActionHUD.BaseSystemAdapter. Prefer the hook for registration because it guarantees that the API object has been initialized.
Do not import modules/stylish-action-hud/scripts/systems/base.js from a companion module. That path is not part of the public API and can change between releases.
class MySystemAdapter {
constructor() {
this.systemId = "my-system";
}
/**
* Get actor statistics for the Party HUD
* @param {Actor} actor - The Foundry actor
* @param {Array} configAttributes - Configured attributes [{path, label, color}, ...]
* @returns {Array} - Processed attributes [{path, label, color, value, max, percent}, ...]
*/
getStats(actor, configAttributes) {
if (!configAttributes || configAttributes.length === 0) return [];
return configAttributes.map((attr) => {
const value = foundry.utils.getProperty(actor, `${attr.path}.value`) ?? 0;
const max = foundry.utils.getProperty(actor, `${attr.path}.max`) ?? 0;
const percent = Math.clamp((value / (max || 1)) * 100, 0, 100);
return {
path: attr.path,
label: attr.label,
color: attr.color,
value: value,
max: max,
percent: percent,
style: attr.style || "bar",
subtype: "resource",
x: attr.x || 0,
y: attr.y || 0,
};
});
}
/**
* Get active conditions/effects for display
* @param {Actor} actor
* @returns {Array} - [{id, src, name, value}, ...]
*/
getConditions(actor) {
const effects = actor.temporaryEffects || [];
return effects
.map((e) => ({
id: e.id || e.flags?.core?.statusId || e.name || "unknown",
src: e.icon,
name: e.name || e.label || "Unknown",
value: e.value ?? null,
}))
.filter((c) => c.src);
}
/**
* Define action menu button categories
* @param {Actor} actor
* @returns {Array} - Action category definitions
*/
getActionCategories(actor) {
return [
{
id: "attacks",
systemId: "attacks",
label: "Attacks",
icon: "fa-solid fa-sword",
type: "submenu",
},
{
id: "spells",
systemId: "spells",
label: "Spells",
icon: "fa-solid fa-sparkles",
type: "submenu",
},
{
id: "inventory",
systemId: "inventory",
label: "Inventory",
icon: "fa-solid fa-backpack",
type: "submenu",
},
{
id: "sheet",
label: "Character Sheet",
icon: "fa-solid fa-user",
type: "sheet",
},
];
}
/**
* Get submenu data for a category
* @param {Actor} actor
* @param {String} categoryId
* @returns {Object} - {title, items, hasTabs, tabLabels, ...}
*/
async getSubMenuData(actor, categoryId) {
// Parse category index
const parts = categoryId.split("-");
const index = parseInt(parts[parts.length - 1]);
// Get category definition
const categories = this.getActionCategories(actor);
const category = categories[index];
if (!category?.systemId) {
return { title: "", items: [] };
}
return await this._getSystemSubMenuData(actor, category.systemId, category);
}
/**
* Internal: Load system-specific submenu data
*/
async _getSystemSubMenuData(actor, systemId, menuData) {
switch (systemId) {
case "attacks":
return this._getAttacks(actor, menuData);
case "spells":
return this._getSpells(actor, menuData);
case "inventory":
return this._getInventory(actor, menuData);
default:
return { title: menuData.label, items: [] };
}
}
_getAttacks(actor, menuData) {
const weapons = actor.items.filter((i) => i.type === "weapon");
return {
title: menuData.label,
hasTabs: false,
items: weapons.map((w) => ({
id: w.id,
name: w.name,
img: w.img,
description: w.system.description?.value || "",
})),
};
}
// ... implement other methods
}
Hooks.once("stylish-action-hud.apiReady", (api) => {
api.registerSystemAdapter("my-system", MySystemAdapter, {
priority: 10, // Higher priority = preferred (default: 0)
source: "my-module", // Identifier for debugging
isCompatible: (context) => {
// Optional: Check compatibility conditions
return context.system.id === "my-system";
},
});
});
| Option | Type | Default | Description |
|---|---|---|---|
priority |
number |
0 |
Higher priority adapters are preferred |
source |
string |
"unknown" |
Module identifier for debugging |
isCompatible |
function |
null |
Function returning boolean for compatibility check |