name: galaxy-code-organization description: File structure, include order, and modular layout for Galaxy script projects. Use when splitting a monolithic script into multiple files, establishing the MapScript.galaxy bootstrap chain, organizing UI into sub-files, or setting up Enums/GlobalVariables/Header/MapInit. Covers which files are auto-generated by the editor and must never be edited (MapScript.galaxy, LibHASH.galaxy, LibHASH_h.galaxy). Do not use for language syntax questions (use galaxy-language-fundamentals).
Galaxy Code Organization & File Splitting
Galaxy scripts in a SC2 map can be split across many .galaxy files using include. This keeps code maintainable and modular. The SC2-IngameDevTools mod is the #1 primary reference for file structure and module layout.
Key References
| Resource | URL |
|---|---|
| SC2-IngameDevTools Script/ folder (PRIMARY — #1 example) | https://github.com/abrahamYG/SC2-IngameDevTools/tree/main/DevToolsIngame.SC2Mod/Script |
| SSF scripts (secondary style reference) | https://github.com/Cristall/SC2-SwarmSpecialForces/tree/main/SwarmSpecialForces.SC2Map/scripts |
| Alcyone Frontlines scripts | https://github.com/KimPlaybit/Alcyone_Frontlines/tree/master/ProximaFrontlines.SC2Mod/scripts |
| Native function reference | https://mapster.talv.space/galaxy/reference |
| GalaxyScript guide | https://s2editor-guides.readthedocs.io/New_Tutorials/03_Trigger_Editor/058_GalaxyScript/ |
| SC2 editor guides index | https://s2editor-guides.readthedocs.io |
| SC2Mapster wiki | https://sc2mapster.wiki.gg/ |
SC2-IngameDevTools Handler-Module Layout (PRIMARY PATTERN)
The SC2-IngameDevTools project organises each feature as a self-contained handler file in Script/. There is one main coordinator, a shared utilities folder, and a _h.galaxy header for forward declarations.
Folder & file structure
Script/
├── DevToolsMain.galaxy ← coordinator: includes all handlers, calls all _Init()
├── debug.galaxy ← global debug helpers: print(), console(), err()
├── debug_h.galaxy ← forward declarations only (header pattern)
├── split_string.galaxy ← utility (string split)
├── ItemList.galaxy ← shared data structure (aggregator)
├── ItemListListBoxFormat.galaxy ← formatting helpers for ItemList
├── AbilityHandler.galaxy ← feature handler (one per module)
├── AbilityOrderHandler.galaxy
├── ActorMessageHandler.galaxy
├── BehaviorHandler.galaxy
├── CameraHandler.galaxy
├── CameraShakeHandler.galaxy
├── CatalogLinkHandler.galaxy
├── CatalogValueHandler.galaxy
├── CheatHandler.galaxy
├── DataEditorHandler.galaxy
├── DataTableHandler.galaxy
├── DoodadHandler.galaxy
├── EffectHandler.galaxy
├── FogHandler.galaxy
├── LightingHandler.galaxy
├── PlayerHandler.galaxy
├── PortraitHandler.galaxy
├── RaceHandler.galaxy
├── SkinHandler.galaxy
├── SoundtrackHandler.galaxy
├── UnitHandler.galaxy
├── UpgradeHandler.galaxy
├── UserDataHandler.galaxy
├── WeaponHandler.galaxy
├── FreeCamHandler.galaxy
├── ItemList/
│ ├── index.galaxy ← the actual ItemList implementation
│ └── Listbox.galaxy ← listbox sub-feature
└── DevTools/
├── helpers.galaxy ← shared helper functions (spawn point, movement tracker)
├── helpers_h.galaxy ← forward declarations for helpers
├── ChatCommand.galaxy ← chat command subsystem
└── ChatCommand/
└── Commands.galaxy ← registered chat commands
Main coordinator (DevToolsMain.galaxy)
include "Script/debug"
include "Script/DevTools/helpers"
include "Script/ActorMessageHandler"
include "Script/BehaviorHandler"
include "Script/EffectHandler"
include "Script/UnitHandler"
include "Script/UpgradeHandler"
include "Script/WeaponHandler"
include "Script/AbilityHandler"
include "Script/CatalogValueHandler"
include "Script/CatalogLinkHandler"
include "Script/LightingHandler"
include "Script/DataTableHandler"
include "Script/DataEditorHandler"
include "Script/CameraHandler"
include "Script/FogHandler"
include "Script/DevTools/ChatCommand"
include "Script/DevTools/ChatCommand/Commands"
void DevToolsMain_Init() {
DevTools_ChatCommand_Init();
helpersInit();
ActorMessageHandler_Init();
CatalogValueHandler_Init();
UnitHandler_Init();
BehaviorHandler_Init();
EffectHandler_Init();
LightingHandler_Init();
// ... every handler's _Init() called here
DevTools_ChatCommand_Commands_Init();
}
Entry point wiring (Lib7C0075CB.galaxy — editor-generated lib file)
include "TriggerLibs/natives"
include "Lib7C0075CB_h"
include "TriggerLibs/NativeLib"
include "Script/DevToolsMain"
void TestMap_main() {
DevToolsMain_Init();
}
void lib7C0075CB_InitCustomScript() {
TestMap_main();
}
bool lib7C0075CB_InitLib_completed = false;
void lib7C0075CB_InitLib() {
if (lib7C0075CB_InitLib_completed) { return; }
lib7C0075CB_InitLib_completed = true;
lib7C0075CB_InitCustomScript();
}
Header file pattern (debug_h.galaxy)
// ONLY forward declarations — no implementations
void print(string s);
void printT(text t);
void console(string s);
void err(string s);
Single handler module anatomy (BehaviorHandler.galaxy)
// 1. Include shared utilities
include "Script/debug_h"
include "Script/ItemList"
// 2. Path constants with UPPER_SNAKE_CASE
static const string CONTAINERDLG_PATH =
"UIContainer/ConsoleUIContainer/CatalogManager/BehaviorManager";
// 3. Module struct + global instance
ItemListContainerStruct BehaviorContainer;
// 4. File-private state
static string ItemList;
static ListBoxFilterStruct ListBoxFilter;
// 5. Trigger event functions (bool a, bool b signature)
bool BehaviorListBoxFilterQuery(bool a, bool b) { ... }
bool BehaviorListBoxSelectionChanged(bool a, bool b) { ... }
bool BehaviorContainerSendHandler(bool a, bool b) { ... }
// 6. One public Init function
void BehaviorHandler_Init() {
playergroup pg = PlayerGroupAll();
ItemList = "BehaviorList";
ItemListContainer_InitStandard(BehaviorContainer, CONTAINERDLG_PATH,
"BehaviorContainerSendHandler"); // trigger registered by STRING name
ItemListInitFromCatalog(ItemList, c_gameCatalogBehavior, ItemListCatalogFilter);
ItemList_FilterListInitStandard(ListBoxFilter, "BehaviorListBox",
BehaviorListBoxSetActive, ItemListItemTextValue,
CONTAINERDLG_PATH+"/NavList");
ItemList_FilterListRebuild(ItemList, ListBoxFilter, "", pg);
}
Real-World Include Orders
SSF full include order (scripts/main.galaxy) — secondary reference
include "scripts/Lib/Starcode" // third-party lib first
include "scripts/Enums" // pure constants
include "scripts/GlobalVariables" // structs, global vars, typedefs
include "scripts/secrets"
include "scripts/Header" // forward declarations
include "scripts/Utilities" // shared helpers
include "scripts/AchievementsTemplates"
include "scripts/Achievements"
include "scripts/UI/UI-Achievements" // UI panels first (alphabetical)
include "scripts/UI/UI-CustomDefeatFrame"
include "scripts/UI/UI-HeroPanel"
include "scripts/UI/UI-Objectives"
include "scripts/UI/UI-Options"
include "scripts/UI/UI-PlayerBoard"
include "scripts/UI/UI-Popup"
include "scripts/UI/UI-Speedruns"
include "scripts/UI/UI-Stats"
include "scripts/UI/UI-Votekick"
include "scripts/UI/UI-HivePanel"
include "scripts/UI/UI-Revive"
include "scripts/UI/UI-Main" // UI coordinator last
include "scripts/Bank"
include "scripts/Enemy"
include "scripts/PartTerran" // content parts
include "scripts/PartProtoss"
include "scripts/PartZerg"
include "scripts/Parts" // part coordinator last
include "scripts/Hive"
include "scripts/HeroAbilities"
include "scripts/Player"
include "scripts/Collectibles"
include "scripts/HeroSelection"
include "scripts/Tutorial"
include "scripts/Debug"
include "scripts/MapInit" // map init always last
void main(){
TriggerAddEventMapInit(TriggerCreate("MapInit_Main"));
}
Alcyone Frontlines include order (scripts/main.galaxy)
include "scripts/GlobalVariables"
include "scripts/MapInit"
include "scripts/Bank"
include "scripts/Player"
include "scripts/Spawner"
include "scripts/Jungle"
include "scripts/UI"
include "scripts/Buildings"
include "scripts/AI"
include "scripts/HeroSelection"
include "scripts/HeroLevelUp"
⚠️ Auto-Generated Files — NEVER Edit
The SC2 editor auto-generates certain .galaxy files. Do not write code in these files — the editor will overwrite any manual changes.
| File | Why it is auto-generated |
|---|---|
MapScript.galaxy |
The editor rebuilds this on every save. It wires trigger libraries and calls InitMap(). |
LibHASH.galaxy (e.g. Lib5A1C9904.galaxy) |
The library wrapper file for an .SC2Mod. The editor generates it from the mod's trigger data. It wraps calls into scripts/main.galaxy via include. |
LibHASH_h.galaxy |
The auto-generated header for the library wrapper. |
What these files contain (reference only)
MapScript.galaxy — bootstraps the engine:
include "TriggerLibs/NativeLib"
include "LibHASH" // pulls in the mod library if present
void InitLibs() { libNtve_InitVariables(); libHASH_InitLib(); }
include "scripts/main" // custom scripts (if standalone map)
void InitCustomScript() { main(); }
void InitMap() { InitLibs(); InitCustomScript(); }
LibHASH.galaxy — a thin editor-generated wrapper that:
- calls
libNtve_InitVariables()as its library init includesscripts/mainonce- exposes public functions as
libHASH_gf_*wrappers - registers a
LoadTriggerstrigger that callsProximaInitTriggers()(or equivalent) - defines
libHASH_InitLib()/libHASH_InitTriggers()
All actual logic lives in scripts/ — that is where you write code.
Bootstrap Chain
MapScript.galaxy is the map's entry point — it is generated by the editor and should contain minimal code:
include "TriggerLibs/NativeLib"
include "TriggerLibs/LibertyLib"
void InitLibs() { libNtve_InitLib(); libLbty_InitLib(); }
include "scripts/main" // <-- pulls in all custom scripts
void InitCustomScript() { main(); }
void InitMap() { InitLibs(); InitCustomScript(); }
scripts/main.galaxy is the coordinator — it lists every include in dependency order and defines main():
include "scripts/Lib/Starcode"
include "scripts/Enums"
include "scripts/GlobalVariables"
include "scripts/Header"
include "scripts/Utilities"
// ... all other files ...
include "scripts/MapInit"
void main() {
TriggerAddEventMapInit(TriggerCreate("MapInit_Main"));
}
main() only registers a single map-init trigger. All actual initialization happens inside that trigger function in MapInit.galaxy.
Include Syntax & Rules
include "scripts/Utilities" // relative to map root, no .galaxy extension
include "scripts/UI/UI-Main" // subdirectories are supported
include "scripts/Lib/Starcode" // third-party / library code in Lib/
Critical rules:
- Paths are relative to the map root (where MapScript.galaxy lives), not to the including file.
- Do not add
.galaxyextension — the engine appends it. - Order matters: a file can only call functions/use types declared in files included before it.
- No circular includes. Each file is included exactly once.
- There is no header guard mechanism — avoid including the same file twice.
File Roles & Naming Conventions
| File | Role |
|---|---|
MapScript.galaxy |
Auto-generated by editor. NEVER edit. Entry point that wires libraries and calls InitMap(). |
scripts/main.galaxy |
Coordinator: all includes + void main(). |
scripts/Enums.galaxy |
Pure const int constants, grouped by category with comment headers. |
scripts/GlobalVariables.galaxy |
All global const, static, struct definitions, and typedef funcref. |
scripts/Header.galaxy |
Forward declarations for every cross-file function. |
scripts/Utilities.galaxy |
Shared helper functions used by many other files. |
scripts/MapInit.galaxy |
Map initialization logic (alliances, player groups, startup sequence). |
scripts/Enemy.galaxy |
Enemy spawning and behavior. |
scripts/Bank.galaxy |
Bank read/write logic. |
scripts/Player.galaxy |
Player stats, XP, upgrades. |
scripts/HeroSelection.galaxy |
Hero selection UI and unlock logic. |
scripts/HeroAbilities.galaxy |
Hero ability implementations. |
scripts/Parts.galaxy |
Coordinator for multi-part content (calls into Part* files). |
scripts/PartTerran.galaxy |
Content specific to the Terran part. |
scripts/PartProtoss.galaxy |
Content specific to the Protoss part. |
scripts/PartZerg.galaxy |
Content specific to the Zerg part. |
scripts/Debug.galaxy |
Debug helpers, override functions. |
scripts/UI/UI-Main.galaxy |
Initializes all UI subsystems. |
scripts/UI/UI-*.galaxy |
One file per UI panel/system. |
scripts/Lib/Starcode.galaxy |
Third-party library. |
Enums.galaxy — Pure Constants File
All game-wide integer constants go in one file:
// parts
const int c_Part_Terran = 0;
const int c_Part_Protoss = 1;
const int c_Part_Zerg = 2;
// game modes
const int c_GameMode_Normal = 0;
const int c_GameMode_Endless = 1;
// bitflags
const int c_BossFightState_Alive = 1 << 0;
const int c_BossFightState_Enraged = 1 << 1;
const int c_BossFightState_Dead = 1 << 2;
// boss IDs
const int c_BossFightID_Flamer = 0;
const int c_BossFightID_Crusher = 1;
Naming convention: c_CategoryName_IdentifierName
Grouping: Use // category name comment headers to separate groups.
Bitflags: Use 1 << n pattern.
Include Enums.galaxy before GlobalVariables.galaxy so struct fields can reference these constants for array sizes.
GlobalVariables.galaxy — Structs & Global State
Declare all structs, global variables, and typedef funcref here:
// global consts
const int gv_MaxAmountPlayers = 6;
const int gv_MaxAmountParts = 3;
// funcref blueprints
bool blueprint_BossAbility(unitgroup ug, unit boss);
typedef funcref<blueprint_BossAbility> Blueprint_BossAbility;
// structs
struct PlayerStruct {
bool activeFlag;
bank bankfile;
unit heroUnit;
int points;
int[gv_MaxAmountParts] wins; // array sized by const
int heroUnlocked; // stored as bitflag
};
// global arrays
PlayerStruct[gv_MaxAmountPlayers + 1] gv_PlayerStats;
Naming convention: gv_SystemName_VariableName for globals, gv_VariableName for simple globals.
Static locals that are cross-trigger: Declare as static at file scope in the file that owns them.
Header.galaxy — Forward Declarations
Galaxy requires functions to be declared before they are called. When file A calls a function defined in file B (included later), use a forward declaration in Header.galaxy:
// Header.galaxy — comment groups match their source file
// UI-PlayerBoard.galaxy
void PlayerBoard_UpdatePlayer(int playerID);
// Bank.galaxy
void Bank_Save_ForcedAll(int playerID);
void Bank_Save_RequestSave(int playerID);
// Parts.galaxy
void Part_PartFinished();
void Part_InitVariables();
// Player.galaxy
void Player_AddExp(int playerID, fixed amount);
Include Header.galaxy after GlobalVariables.galaxy so declarations can reference struct types.
UI Subdirectory Pattern
Each UI panel gets its own file. A coordinator file (UI-Main.galaxy) initializes all of them:
// scripts/UI/UI-Main.galaxy
void SSFCustomUI_Init() {
gv_UI_MasterFrame = DialogControlHookupStandard(c_triggerControlTypePanel, "UIContainer/...");
StatsInterface_Init();
Votekick_Init();
Options_Init();
HeroPanel_Init();
PlayerBoard_Init();
// ...
}
In main.galaxy, include individual UI files before UI-Main.galaxy:
include "scripts/UI/UI-HeroPanel"
include "scripts/UI/UI-PlayerBoard"
include "scripts/UI/UI-Options"
// ... all panels first ...
include "scripts/UI/UI-Main" // coordinator last, so it can call the others
Parts Pattern — Per-Content Modularity
When content is split by campaign mission / chapter / faction, use a coordinator + per-part files:
// In main.galaxy — include individual parts BEFORE coordinator
include "scripts/PartTerran"
include "scripts/PartProtoss"
include "scripts/PartZerg"
include "scripts/Parts" // coordinator included last
Each Part*.galaxy owns its content and exposes a TriggerCreate() function:
// PartTerran.galaxy
void PartTerran_AreaJunker_Second_Open() { ... }
void PartTerran_TriggerCreate() {
TriggerAddEventXxx(TriggerCreate("PartTerran_SomeHandler"), ...);
}
Parts.galaxy coordinates between parts (handles part transitions, shared state):
// Parts.galaxy
static const int observer_AmountWaypoints = 11;
static point[observer_AmountWaypoints] observer_Waypoints;
void Part_InitVariables() { ... }
void Part_PartFinished() {
PartTerran_TriggerCreate(); // calls into part files
}
Lib Subdirectory — Third-Party Code
Put external/library code in scripts/Lib/:
include "scripts/Lib/Starcode" // third-party utility library
This keeps library code clearly separated from your own code.
Function Naming Convention
All functions follow a SystemName_ActionName pattern:
MapInit_ActivePlayers()— belongs to MapInit systemBank_Save_RequestSave(int playerID)— belongs to Bank system, Save subsystemPlayerBoard_UpdatePlayer(int playerID)— belongs to PlayerBoard UIUtility_IsNumber(string input)— belongs to UtilitiesPartTerran_AreaJunker_Second_Open()— Part + Area + Action
This makes it immediately obvious which file a function lives in.
Recommended File Include Order in main.galaxy
// 1. Third-party libraries (no dependencies)
include "scripts/Lib/Starcode"
// 2. Pure constants (no dependencies)
include "scripts/Enums"
// 3. Global state (depends on Enums for array sizes)
include "scripts/GlobalVariables"
// 4. Secret/config (if any)
include "scripts/secrets"
// 5. Forward declarations (depends on GlobalVariables for types)
include "scripts/Header"
// 6. Shared utilities (may depend on GlobalVariables)
include "scripts/Utilities"
// 7. Templates/data-only files
include "scripts/AchievementsTemplates"
// 8. Feature files alphabetical (depend on headers + globals)
include "scripts/Achievements"
include "scripts/Bank"
include "scripts/Enemy"
// 9. UI files — panels first, coordinator last
include "scripts/UI/UI-HeroPanel"
include "scripts/UI/UI-PlayerBoard"
include "scripts/UI/UI-Main"
// 10. Part files — individual parts first, coordinator last
include "scripts/PartTerran"
include "scripts/PartProtoss"
include "scripts/PartZerg"
include "scripts/Parts"
// 11. High-level systems that depend on parts
include "scripts/Hive"
include "scripts/HeroAbilities"
include "scripts/Player"
include "scripts/Collectibles"
include "scripts/HeroSelection"
include "scripts/Tutorial"
// 12. Debug and init last
include "scripts/Debug"
include "scripts/MapInit"
static Variables for Trigger Parameters
When a trigger needs to pass data to its handler function (since trigger functions take no parameters), use static file-scope variables as a parameter register:
// In Utilities.galaxy
static int Utility_DelayedTextTagDestroyer_ParamTextTag;
static trigger Utility_DelayedTextTagDestroyer_Trigger;
void Utility_DelayedTextTagCreate(text inText, color inColor, point position, playergroup pg, fixed offset) {
Utility_DelayedTextTagDestroyer_ParamTextTag = TextTagCreate(...);
TriggerExecute(Utility_DelayedTextTagDestroyer_Trigger, false, false);
}
bool Utility_DelayedTextTagDestroyer(bool testCond, bool runActions) {
int textTag = Utility_DelayedTextTagDestroyer_ParamTextTag; // capture immediately
Wait(3.5, c_timeGame);
TextTagDestroy(textTag);
return true;
}
Capture the static into a local variable at the top of the handler before any Wait() calls.
Common Error: "Scri: Script load failed: Function not found"
If the SC2 editor or test map throws:
Scri: Script load failed: Function not found
and the function clearly exists in your .galaxy files, the most common cause is file encoding.
Fix: Save the offending .galaxy file as UTF-8 without BOM (not "UTF-8 with BOM").
In VS Code:
- Open the file.
- Click the encoding indicator in the bottom-right status bar (e.g.
UTF-8 with BOM). - Select "Save with Encoding" -> choose "UTF-8" (no BOM).
The SC2 engine cannot parse files saved with a BOM (EF BB BF) prefix, which causes it to fail to locate any functions defined in that file, even if the code itself is correct.