.agents/skills/galaxy-ui-and-dialogs/SKILL.md
--- name: galaxy-ui-and-dialogs description: Dialog and dialog control creation, XML frame hookup, hero/upgrade selection panels, scoreboard panels, dialog events, HUD messages, localized text, minimap pings, and the SSF hooked-frame UI pattern in Galaxy script. Use when building or updating any in-game UI: dialogs, buttons, labels, images, portraits, click handlers, or player-visible messages. Do not use for actor-based visuals (use galaxy-actor-and-visuals). --- # Galaxy Scripting – UI & Dial
npx skillsauth add KimPlaybit/Ultrasikism .agents/skills/galaxy-ui-and-dialogsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Resource | URL |
|---|---|
| Native function reference | https://mapster.talv.space/galaxy/reference |
| Galaxy syntax definition | https://github.com/Talv/vscode-sc2-galaxy/blob/master/syntaxes/galaxy.json |
| SC2-IngameDevTools (PRIMARY — #1 codebase) | https://github.com/abrahamYG/SC2-IngameDevTools/tree/main/DevToolsIngame.SC2Mod/Script |
| SSF codebase (secondary style) | https://github.com/Cristall/SC2-SwarmSpecialForces/tree/main/SwarmSpecialForces.SC2Map/scripts |
| Alcyone Frontlines codebase | https://github.com/KimPlaybit/Alcyone_Frontlines/tree/master/ProximaFrontlines.SC2Mod/scripts |
| NativeLib dialog helpers | TriggerLibs/NativeLib.galaxy — all libNtve_gf_CreateDialogItem*, libNtve_gf_SetDialogItem* functions |
| Dialogs guide | https://s2editor-guides.readthedocs.io/New_Tutorials/03_Trigger_Editor/043_Dialogs/ |
| Dialog Panels guide | https://s2editor-guides.readthedocs.io/New_Tutorials/03_Trigger_Editor/044_Dialog_Panels/ |
| UI Events guide | https://s2editor-guides.readthedocs.io/New_Tutorials/03_Trigger_Editor/049_UI_Events/ |
| SC2Mapster wiki | https://sc2mapster.wiki.gg/ |
Dialogs are overlay panels created either programmatically or hooked up to existing UI frames defined in layout XML.
SSF binds to frames already defined in the SC2 UI XML layout:
// Hook a panel frame by path from the root UI hierarchy
dialogcontrol gv_UI_MasterFrame = DialogControlHookupStandard(
c_triggerControlTypePanel,
"UIContainer/FullscreenUpperContainer/SSF_CustomUI"
);
// Hook a specific child control (button, label, etc.) within a parent frame
dialogcontrol saveBtn = DialogControlHookup(
gv_UI_MasterFrame,
c_triggerControlTypeButton,
"Menu/SaveButton"
);
// Register click event on the hooked control
TriggerAddEventDialogControl(
TriggerCreate("Bank_ManualSave"),
c_playerAny,
saveBtn,
c_triggerControlEventTypeClick
);
The init pattern in UI-Main.galaxy:
void SSFCustomUI_Init() {
gv_UI_MasterFrame = DialogControlHookupStandard(c_triggerControlTypePanel, "UIContainer/FullscreenUpperContainer/SSF_CustomUI");
StatsInterface_Init(); // each UI subsystem hooks its own controls
HeroPanel_Init();
PlayerBoard_Init();
// ...
}
dialog lv_dlg = DialogCreate(
1200, // width (pixels)
600, // height (pixels)
c_anchorCenter, // anchor position on screen
0, // x offset from anchor
0, // y offset from anchor
false // modal (blocks input to game underneath)
);
// Or capture:
gv_heroDialog = DialogLastCreated();
DialogSetVisible(lv_dlg, PlayerGroupAll(), true); // show to all
DialogSetVisible(lv_dlg, PlayerGroupSingle(3), false); // hide for player 3
c_anchorTopLeft
c_anchorTop
c_anchorTopRight
c_anchorLeft
c_anchorCenter
c_anchorRight
c_anchorBottomLeft
c_anchorBottom
c_anchorBottomRight
DialogControlHookup pattern (PRIMARY — use for existing XML frames)The SC2-IngameDevTools codebase hooks into pre-existing XML UI frames rather than creating dialog controls in code. This is the preferred pattern when UI frames are defined in Base.SC2Data/UI/Layout/:
// Hookup an existing XML panel by its frame path (no creation needed)
// The path is relative to the UI layout root
static const string CONTAINERDLG_PATH =
"UIContainer/ConsoleUIContainer/CatalogManager/BehaviorManager";
static const string EDITBOX_PATH = "Item";
static const string ADDBTN_PATH = "AddButton";
static const string REMOVEBTN_PATH = "RemoveButton";
struct BehaviorContainerStruct {
int panel; // the hooked panel (int = dialog control handle)
int messageBox;
int addButton;
int removeButton;
};
BehaviorContainerStruct BehaviorContainer;
void BehaviorHandler_Init() {
trigger t;
// Hook the root panel by absolute path
BehaviorContainer.panel =
DialogControlHookupStandard(c_triggerControlTypePanel, CONTAINERDLG_PATH);
// Hook child controls relative to the panel
BehaviorContainer.messageBox =
DialogControlHookup(BehaviorContainer.panel, c_triggerControlTypeEditBox, EDITBOX_PATH);
BehaviorContainer.addButton =
DialogControlHookup(BehaviorContainer.panel, c_triggerControlTypeButton, ADDBTN_PATH);
BehaviorContainer.removeButton =
DialogControlHookup(BehaviorContainer.panel, c_triggerControlTypeButton, REMOVEBTN_PATH);
// Register click handler — trigger registered by STRING function name
t = TriggerCreate("BehaviorContainerSendHandler");
TriggerAddEventDialogControl(t, c_playerAny, BehaviorContainer.addButton,
c_triggerControlEventTypeClick);
TriggerAddEventDialogControl(t, c_playerAny, BehaviorContainer.removeButton,
c_triggerControlEventTypeClick);
}
Key functions used:
| Function | Purpose |
|---|---|
| DialogControlHookupStandard(type, path) | Hook a root-level frame by absolute XML path |
| DialogControlHookup(parent, type, childPath) | Hook a child control relative to a parent panel |
| DialogControlGetPropertyAsString(ctrl, prop, player) | Read text/string property |
| DialogControlSetPropertyAsString(ctrl, prop, pg, val) | Set text/string property |
| DialogControlGetPropertyAsInt(ctrl, prop, player) | Read int property (e.g. selection index) |
| DialogControlGetSelectedItem(list, player) | Get selected listbox item index |
| DialogControlAddItem(list, pg, text) | Add item to a listbox/pulldown |
| DialogControlRemoveAllItems(list, pg) | Clear all listbox items |
| DialogControlGetItemCount(list, player) | Count items in a listbox |
libNtve_gf_CreateDialogItemButton(
lv_dlg, // parent dialog
200, 50, // width, height
c_anchorTopLeft, // anchor within dialog
10, 10, // x, y offset from anchor
StringToText(""), // tooltip
StringToText("Click Me"), // label
"" // style (empty = default)
);
dialogcontrol lv_btn = DialogControlLastCreated();
libNtve_gf_CreateDialogItemLabel(
lv_dlg,
300, 40,
c_anchorTopLeft,
10, 60,
StringToText("Score: 0"), // initial text
ColorWithAlpha(255, 255, 255, 255), // white
false, // word wrap
0 // wrap width (0 = no limit)
);
dialogcontrol lv_label = DialogControlLastCreated();
libNtve_gf_CreateDialogItemImage(
lv_dlg,
100, 100,
c_anchorTopLeft,
10, 110,
StringToText(""), // tooltip
"Assets\\Textures\\UI_Protoss_Something.dds",
c_triggeredImageTypeNormal,
false, // tiled
ColorWithAlpha(255, 255, 255, 255)
);
dialogcontrol lv_img = DialogControlLastCreated();
libNtve_gf_CreateDialogItemPortrait(
lv_dlg, 150, 150, c_anchorTopLeft, 10, 10,
StringToText(""), -1,
null, // unit (null for no unit)
true // use unit model
);
// Change text
libNtve_gf_SetDialogItemText(lv_label, StringToText("Score: 42"), PlayerGroupAll());
// Change image
libNtve_gf_SetDialogItemImage(lv_img, "Assets\\Textures\\NewImage.dds", PlayerGroupAll());
// Enable / disable
DialogControlSetEnabled(lv_btn, PlayerGroupAll(), true);
DialogControlSetEnabled(lv_btn, PlayerGroupSingle(lv_player), false);
// Guard against an uninitialized control (c_invalidDialogControlId == 0)
if (lv_label != c_invalidDialogControlId) {
libNtve_gf_SetDialogItemText(lv_label, StringToText("..."), PlayerGroupAll());
}
TriggerAddEventDialogControl(
myTrigger,
c_playerAny,
c_invalidDialogControlId, // 0 = any control in dialog
c_triggerControlEventTypeClick
);
TriggerAddEventDialogControl(
myTrigger,
c_playerAny,
lv_btn,
c_triggerControlEventTypeClick
);
bool MyClickHandler(bool testCond, bool runActions) {
dialogcontrol clicked = EventDialogControl();
int player = EventPlayer();
if (clicked == gv_LockInButton) {
HeroSelection_SelectHero(player, gv_SelectedHero[player]);
}
return true;
}
SSF uses hooked XML frames for hero selection. Each panel is a separate file under scripts/UI/. The HeroSelection.galaxy file drives the logic while UI-HeroPanel.galaxy owns the dialog controls:
// UI-HeroPanel.galaxy
static dialogcontrol HeroPanel_MainFrame;
static dialogcontrol[gv_MaxAmountHeroes + 1] HeroPanel_HeroButtons;
void HeroPanel_Init() {
HeroPanel_MainFrame = DialogControlHookup(gv_UI_MasterFrame, c_triggerControlTypePanel, "HeroPanel");
int i = 1;
for (; i <= gv_MaxAmountHeroes; i += 1) {
HeroPanel_HeroButtons[i] = DialogControlHookup(HeroPanel_MainFrame, c_triggerControlTypeButton, "Hero" + IntToString(i));
}
TriggerAddEventDialogControl(TriggerCreate("HeroPanel_Click"), c_playerAny, c_invalidDialogControlId, c_triggerControlEventTypeClick);
}
void HeroPanel_UpdatePlayer(int playerID) {
// Show/hide based on unlock state
int i = 1;
for (; i <= gv_MaxAmountHeroes; i += 1) {
bool unlocked = ((gv_PlayerStats[playerID].heroUnlocked & (1 << i)) != 0);
DialogControlSetEnabled(HeroPanel_HeroButtons[i], PlayerGroupSingle(playerID), unlocked);
}
}
bool HeroLevelUp_Handler(bool testCond, bool runActions) {
unit hero = EventUnit();
int level = UnitXPGetCurrentLevel(hero);
int player = UnitGetOwner(hero);
// Show appropriate upgrade panel for this level
if (level == 2) {
DialogControlSetVisible(gv_UpgradeFrame_Level2, PlayerGroupSingle(player), true);
}
return true;
}
UIDisplayMessage(PlayerGroupAll(), c_messageAreaChat, StringToText("GG!"));
UIDisplayMessage(PlayerGroupAll(), c_messageAreaSubtitle, StringToText("Round 1 – Fight!"));
UIDisplayMessage(
PlayerGroupSingle(lv_player),
c_messageAreaChat,
StringToText("Not enough minerals!")
);
// Area constants
c_messageAreaChat // bottom-left chat area
c_messageAreaSubtitle // center subtitle
c_messageAreaDefault
c_messageAreaObjective
c_messageAreaWarning
libNtve_gf_UIErrorMessage(
PlayerGroupSingle(lv_player),
StringToText("Cannot afford this upgrade!"),
SoundLink("UI_GenericError", -1)
);
// Read from GameStrings.txt / Trig/ namespace
text lv_msg = StringExternal("Trig/Bosskilled");
// With variable replacements (SSF pattern)
text lv_result = Utility_TextExpressionReplacement3(
"Trig/Bosskilled",
IntToText(gv_Part_AmountObjectivesDefeated),
IntToText(gv_Part_AmountObjectivesMax),
IntToText(gv_Difficulty_Points)
);
// Colorize player name
color playerColor = libNtve_gf_ConvertPlayerColorToColor(PlayerGetColorIndex(playerID, false));
text colored = TextWithColor(StringToText(PlayerName(playerID)), playerColor);
// Combine text
text lv_msg2 = StringToText("+") + FixedToText(amount, 2);
// Toggle built-in alert types
UISetAlertTypeVisible(PlayerGroupAll(), "AlertWorkerAttacked", false);
// Minimap ping
PingCreate(PlayerGroupAll(), lv_point, 10.0, ColorWithAlpha(255, 0, 0, 255), "");
SSF uses hooked XML frames for the player board, updated by calling PlayerBoard_UpdatePlayer(playerID):
// UI-PlayerBoard.galaxy
static dialogcontrol PlayerBoard_MainFrame;
static dialogcontrol[gv_MaxAmountPlayers + 1] PlayerBoard_KillsLabel;
static dialogcontrol[gv_MaxAmountPlayers + 1] PlayerBoard_ScoreLabel;
void PlayerBoard_Init() {
PlayerBoard_MainFrame = DialogControlHookup(gv_UI_MasterFrame, c_triggerControlTypePanel, "PlayerBoard");
int i = 1;
for (; i <= gv_MaxAmountPlayers; i += 1) {
PlayerBoard_KillsLabel[i] = DialogControlHookup(PlayerBoard_MainFrame, c_triggerControlTypeLabel, "Player" + IntToString(i) + "/Kills");
}
}
void PlayerBoard_UpdatePlayer(int playerID) {
libNtve_gf_SetDialogItemText(
PlayerBoard_KillsLabel[playerID],
IntToText(gv_PlayerStats[playerID].kills),
PlayerGroupAll()
);
}
// Switch player(s) to fullscreen UI (hides default game HUD)
UISetMode(PlayerGroupAll(), c_uiModeFullscreen, c_transitionDurationImmediate);
// Hide specific HUD frames
UISetFrameVisible(PlayerGroupAll(), c_syncFrameTypeSupply, false); // hide supply display
UISetFrameVisible(PlayerGroupAll(), c_syncFrameTypeResourcePanel, false); // hide minerals/gas panel
// Hide alert types
UISetAlertTypeVisible(PlayerGroupAll(), "AlertWorkerAttacked", false);
// Hide the tech tree button inside the help panel
HelpPanelEnableTechTreeButton(PlayerGroupAll(), false);
// Navigate to a specific help panel page
HelpPanelDisplayPage(PlayerGroupAll(), c_helpPanelPageTutorials);
// Register a unit type on the help panel's unit tab
libCamp_gf_AddUnitTypeToUnitHelpPanel("Marine", false, lv_player);
// Create a campaign tutorial entry (shows in the help panel)
libCamp_gf_CreateCampaignTutorial(
StringToText("Tutorial Title"),
StringToText("Explanation body text here."),
"Assets\\Textures\\btn-unit-terr-marine.dds", // icon path
"" // video path (or empty)
);
// Beacon / alert for a map point (flashes minimap + compass)
UIAlertPoint("TriggerName", PlayerGroupSingle(lv_player), StringExternal("Param/Alert/..."), null, lv_point);
// Beacon / alert centered on a specific unit
UIAlertUnit("TriggerName", lv_player, StringExternal("Param/Alert/..."), null, lv_unit);
// Minimap ping with facing angle (used for objectives)
libNtve_gf_CreatePingFacingAngle(
PlayerGroupAll(),
"PingObjective", // ping type from data
lv_point,
ColorWithAlpha(0, 100, 0, 0), // RGBA color (0-255 or 0-1 depending on version)
0.0, // duration (0 = permanent until destroyed)
270.0 // facing angle
);
ping lv_ping = PingLastCreated();
PingSetScale(lv_ping, 0.75);
PingSetTooltip(lv_ping, StringToText("Objective location"));
Source: Dialogs guide — Dialog Formatting
Dialogs are sized in an internal pixel space — they look the same regardless of the player's monitor resolution. The SC2 client maps this internal space to the player's screen:
| Screen ratio | Internal resolution (width × height) | |---|---| | 4:3 | 1600 × 1200 | | 16:9 | 2133 × 1200 | | 16:10 | 1920 × 1200 |
Height is always 1200 internal pixels. Width scales by ratio.
Practical rule: Keep dialogs no wider than 1600 px (the narrowest 4:3 width) to ensure they never clip or overflow on any screen ratio. Design at 1600 px wide and you're safe for all players.
// Safe full-screen-width dialog (works on all ratios)
dialog gv_mainDialog = DialogCreate(
1600, // safe max width
1200, // full height
c_anchorCenter,
0, 0,
false
);
// For UI elements, anchor to edges rather than absolute coords
// so they stay on-screen across all ratios:
// c_anchorTopLeft, c_anchorTopRight, c_anchorBottomLeft, c_anchorBottomRight, c_anchorCenter
Wrapper pattern that avoids the GUI editor crash when creating items from a template string:
// Use this helper instead of the GUI "Create Dialog Item From Template" action
int CreateDialogItemFromTemplate(int dialog, int type, string template) {
return DialogControlCreateFromTemplate(dialog, type, template);
}
testing
SC2 Data Editor — Wizards for automating complex or repetitive data creation/modification in XML. Use when creating .BlizWiz XML files to define templates for generating catalog entries (units, abilities, effects, actors, etc.) with user inputs, conditions, validations, and macros. Covers wizard elements (input, entry, condition, validate, macro), string evaluation (tokens, catalog references, arithmetic), and file placement. Always reference the Data Wizard Documentation for syntax. Do not use for direct data editing (use other sc2data-* skills) or Galaxy scripting.
data-ai
SC2 Data Editor — Units, Abilities, Movers, Turrets, Requirements, and Races in XML. Use when creating or modifying units (CUnit/CUnitHero), abilities (CAbilEffectTarget, CAbilEffectInstant, CAbilResearch, etc.), movement (CMover), turrets (CTurret), or tech requirements in GameData XML files. Always consult the catalogsData.xsd schema for exact fields and structure — do not assume unsupported fields exist. Do not use for Actors/visuals (use sc2data-actors-visuals), damage/effects (use sc2data-effects-weapons), or Galaxy scripting (use the galaxy-* skills).
data-ai
SC2 Data Editor — Effects, Weapons, Upgrades, and the damage chain in XML. Use when creating or modifying CEffect* (damage, search, apply behavior, launch missile, set), CWeapon, CUpgrade, or the full chain from weapon through to damage application. Also covers TargetFind, TargetSort, and Footprints. Always consult the catalogsData.xsd schema for exact fields and structure — do not assume unsupported fields exist. Do not use for actors/visuals (sc2data-actors-visuals) or unit/ability containers (sc2data-units-abilities).
testing
SC2 Data Editor — Behaviors (buffs, debuffs, auras, timers) and Validators (conditional tests) in XML. Use when creating or modifying CBehavior* (buff, attribute modifier, unit tracker, reveal) or CValidator* (unit type, unit order, comparison, combine) entries. Also covers behavior stacking, duration, Vitals modification, and how validators gate effects, abilities, and behaviors. Always consult the catalogsData.xsd schema for exact fields and structure — do not assume unsupported fields exist. Do not use for applying a behavior via an effect (use sc2data-effects-weapons) or actor visuals tied to a behavior (use sc2data-actors-visuals).