Repository navigation
Plugins ‐ Tutorial
Plugin modules are written as hscript files using a custom hscript implementation, but can also be added directly into the codebase if you compile the source code yourself. Plugins are designed to compile in both hscript and standard Haxe, allowing developers to use intellisense during development and then export the code as a text file for use in the application.
Each plugin must specify its type through the type field. Below is a list of plugin types and their functionalities.
Menu plugins add custom menu items to the application interface.
-
Type Name:
"MenuPlugin_Window" -
Purpose: Adds menu items to the main application window's menu.
-
Definition:
typedef MenuPlugin_Window = Plugin & { var menuPath: Array<String>; var shouldApply: MenuPlugin_Window_ShouldApply; function getMenu(context: MenuPluginContext): MenuItem; }
-
Type Name:
"MenuPlugin_RowContext" -
Purpose: Adds context menu items when right-clicking on a row in a sheet.
-
Definition:
typedef MenuPlugin_RowContext = MenuPlugin_Window & { function appliesToRow(context: MenuPluginContext): Bool; }
-
Type Name:
"MenuPlugin_SheetContext" -
Purpose: Adds context menu items when right-clicking on a sheet.
-
Definition:
typedef MenuPlugin_SheetContext = MenuPlugin_Window & { function appliesToSheet(context: MenuPluginContext): Bool; }
Define a new sheet view as a plugin. This is a large topic which will require its own guide.
Level plugins interact with the level editor, allowing customization of level editing behavior and appearance.
-
Type Name:
"LevelPlugin" -
Purpose: General plugin responding to level editor events.
-
Definition:
typedef LevelPlugin = Plugin & { function execute<T>(onTrigger: LevelPluginTrigger<T>): T; }
-
Type Name:
"LevelPlugin_LevelObjectDisplay" -
Purpose: Modifies the display of level objects.
-
Definition:
typedef LevelPlugin_LevelObjectDisplay = Plugin & { function appliesToObject(context: LevelObjectPluginContext): Bool; function execute(context: LevelObjectPluginContext): Void; }
-
Type Name:
"LevelPlugin_LevelObjectEditProps" -
Purpose: Adds custom properties or editing capabilities to level objects.
-
Definition:
typedef LevelPlugin_LevelObjectEditProps = Plugin & { function appliesToObject(context: LevelObjectPluginContext): Bool; function render(context: LevelObjectPluginContext): Void; function writeToSheet(context: LevelObjectPluginContext): Void; }
-
Type Name:
"LevelPlugin_LevelObjectScript" -
Purpose: Associates custom scripts with level objects.
-
Definition:
typedef LevelPlugin_LevelObjectScript = Plugin & { var scriptName: String; function appliesToObject(context: LevelObjectPluginContext): Bool; function execute(context: LevelObjectPluginContext): Void; }
Each plugin file must export its plugin objects using the export variable. This variable should be an array containing one or more plugin objects.
Example:
var export:Array<Plugin> = [myPlugin];Because the plugin system uses an enhanced hscript interpreter, there are syntactical/functional limitations. All of these can be circumvented by baking the plugin within the source code at compilation time, but then you will not be able to use them in the browser or share them.
This system uses a modified version of hscript Iris that allows for import statements. Only classes and modules included in compilation can be imported. Any external classes not included OR classes removed by DCE will not be accessible.
Unlike vanilla hscript or Iris, this system allows the use of wildcard (_) variables in switch cases. You cannot capture variables directly in switch cases. To access enum parameters, use the getParameters() method.
switch PLUGIN.dyn(onTrigger) {
case LevelPluginTrigger.LevelOpen(_):
var params = onTrigger.getParameters();
var level = params[0];
// Use 'level' as needed
}Classes and enums cannot be defined within hscript plugins. While classes can be referenced if they are part of the compiled application, you cannot define new classes or enums in your script.
Extern functions and classes are not included in the hscript compilation. In Haxe, accessing browser APIs (e.g., js.Browser) relies on extern classes, which cannot be used in hscript. As a result, direct browser access is non-functional within hscript plugins. The PLUGIN utility exposes the window and document objects to the plugin context, allowing limited interaction with the browser environment.
// Accessing window and document
var window = PLUGIN.window;
var document = PLUGIN.document;Type parameters (generics) are unusable in hscript plugins. Use Dynamic types or the PLUGIN.dyn() function to handle generic cases.
When writing lambda or inline functions, use the () -> {} notation instead of the function() {} notation.
// Correct syntax
var myFunction = () -> {
// Function body
};
// Incorrect syntax (will not work)
var myFunction = function() {
// Function body
};Direct casting using (cast obj:Type) may not function as expected. Use PLUGIN.dyn() to convert variables to Dynamic when necessary.
var dynamicObj = PLUGIN.dyn(obj);SharedRepo is a key-value store sharing data between the plugin and the application or among plugins.
PLUGIN is a convenience class exposing functionality that hscript has difficulty parsing, such as type parameters and casting.
// Accessing window and document
var window = PLUGIN.window;
var document = PLUGIN.document;
// Cast to dynamic to get around typing issues
var result = PLUGIN.dyn(someFunctionWithGenerics());
Adding a "Duplicate" option to the right-click context menu of sheet rows.
var menuPlugin = {
type: "MenuPlugin_RowContext",
menuPath: [],
shouldApply: MenuPlugin_Window_ShouldApply.Always,
appliesToRow: (context) -> {
return true; // Always apply to rows
},
getMenu: (context) -> {
var duplicateItem = new MenuItem({ label: "Duplicate" });
duplicateItem.click = () -> {
context.sheet.copyLine(context.rowIndex);
context.model.refresh();
context.model.save();
};
return duplicateItem;
}
};
var export = [menuPlugin];Creating a plugin that adds a grid overlay to the level editor and provides grid options.
var drawGridPlugin = {
type: "LevelPlugin",
execute: (onTrigger) -> {
switch PLUGIN.dyn(onTrigger) {
case LevelPluginTrigger.LevelRedraw(level, view): {
if (SharedRepo.get("LevelShowGrid") == true) {
var gridSize = SharedRepo.get("LevelGridSize");
if (gridSize != null) {
drawGrid(view, gridSize, gridSize);
}
}
}
case LevelPluginTrigger.LevelOpen(level): {
renderLevelOptions(level);
renderPaletteOptions(level);
}
case LevelPluginTrigger.LevelClose(level): {
unrenderLevelOptions(level);
unrenderPaletteOptions(level);
}
default:
}
return null;
}
};
//see included plugin file for full implementation
var export = [drawGridPlugin];Creating a plugin that adds an "Export as PNG" option to the level options menu.
var pngExportPlugin = {
type: "LevelPlugin",
execute: (onTrigger) -> {
switch onTrigger {
case LevelPluginTrigger.LevelOpen(level): {
var optionsMenu = level.content.find(".submenu.options");
optionsMenu.append('<div class="item pngExportPlugin">
<input type="submit" name="pngExport" value="Export as PNG"/>
</div>');
optionsMenu.find("[name=pngExport]").on('click', (e) -> {
exportToPNG(level.view.getCanvas());
});
}
case LevelPluginTrigger.LevelClose(level): {
var optionsMenu = level.content.find(".submenu.options");
optionsMenu.find(".pngExportPlugin").remove();
}
default:
}
return null;
}
};
function exportToPNG(canvas: CanvasElement): Void {
PLUGIN.window.requestAnimationFrame((dt) -> {
var dataURL = canvas.toDataURL('image/png');
var link = cast PLUGIN.document.createElement('a'): AnchorElement;
link.href = dataURL;
link.download = 'level.png';
link.click();
});
}
var export = [pngExportPlugin];If you have a suggestion for other plugin types, feel free to raise an issue.