Skip to content

Registration points

A registration point is a place where a plugin attaches a feature to the editor. Call all of them inside Register(host) as host.Add…(data type). The data types are C# records. You can pass fields that have default values as named arguments (Order: 600).

Registration pointData typeSDKIn the editor
AddMenuItemNpMenuItem1.0Menu bar
AddQuickAddItemNpQuickAdd1.0Main toolbar Add
AddCommandNpCommand1.0Console panel
AddImporter · AddExporterNpFileFormat1.0File › Import: … · Build › Export: …
AddContentTabNpContentTab1.0Bottom area panel (Window menu)
AddModeNpMode1.0Mode row · Tools panel · viewport clicks
AddActorTypeNpActorType1.0Outliner Type column
AddCellGuardFunc<int,int,int,bool>1.0Every block edit
AddSymmetryFunc<IReadOnlyList<NpBlock>, IEnumerable<NpBlock>>1.0Place · Break · Brush · Shape
AddShotScenarioNpShot1.0--np-shot=<id>
AddShortcutNpShortcut1.1Tools › Shortcuts…
AddSceneEventNpSceneEvent1.2World logic — Adding world logic types

Common rules:

  • Id gets an ext.<plugin id>. prefix inside the editor. It only needs to be unique within your plugin.
  • If Id (Name for commands, Kind for actor types) is empty or the run function (Run · Spawn · Build) is null, registration throws. The whole plugin then fails to enable.
  • If a registered callback throws, the plugin is isolated.
  • Order is the order within the same location. Smaller comes first. The default is 500.
  • Do not put emoji or pictographic characters in on-screen text. Pictographic characters are stripped from menu, tab and mode names. Give icons as an icon name (prefab · block · light · trigger · logic …) in the Icon field.
public sealed record NpMenuItem(string Id, string Menu, string Label, Action Run, string Shortcut = "", int Order = 500);
FieldDescription
IdItem id
Menu"파일" · "편집" · "창" · "도구" · "빌드" · "도움말". Any other name creates a new menu before Help.
LabelText shown in the menu
RunRuns when clicked
ShortcutShortcut string (for example "Ctrl+Alt+H"). If the key can be parsed, it appears next to the menu text, runs the item when pressed, and can be rebound in Tools › Shortcuts….
OrderOrder within the menu. A separator appears where the hundreds digit changes (for example between 499 and 500).
host.AddMenuItem(new NpMenuItem("hollow", "도구", "선택 속 비우기", () =>
{
if (host.World.Selection is not { } b) { host.Message("먼저 영역을 고르세요"); return; }
int n = host.World.Edit("속 비우기", e =>
{
for (int y = b.Min.Y + 1; y < b.Max.Y; y++)
for (int z = b.Min.Z + 1; z < b.Max.Z; z++)
for (int x = b.Min.X + 1; x < b.Max.X; x++)
e.Clear(x, y, z);
});
host.Message($"{n:N0}칸 비움");
}, Order: 550));
  • Items in the Window menu are attached below the panel list.
  • Put export-type items in Build.
public sealed record NpQuickAdd(string Id, string Label, string Category, Action<NpQuickAddContext> Spawn,
string Keywords = "", int Order = 500, string Icon = "", string IconState = "");
public sealed record NpQuickAddContext(INpWorld World, NpCell Place);
FieldDescription
LabelName shown in the list
CategoryCategory name. A new name creates a new category after the built-in categories.
SpawnRuns when chosen. ctx.Place = the cell to place at (the cell outside the block face under the mouse; if none, in front of the screen center)
KeywordsSearch words (space-separated). Including both Korean and English makes items easier to find.
IconIcon name (for example "prefab" · "light")
IconStateBlock state string to draw as the icon. If set, it takes precedence over Icon.
host.AddQuickAddItem(new NpQuickAdd("lamp_post", "가로등", "도로", ctx =>
ctx.World.Edit("가로등", e =>
{
for (int y = 0; y < 3; y++) e.Set(ctx.Place.X, ctx.Place.Y + y, ctx.Place.Z, "minecraft:dark_oak_fence");
e.Set(ctx.Place.X, ctx.Place.Y + 3, ctx.Place.Z, "minecraft:lantern[hanging=false]");
}),
Keywords: "lamp light 가로등", Icon: "light", IconState: "minecraft:lantern"));
public sealed record NpCommand(string Name, string Usage, string Help, Func<string[], string> Run);
FieldDescription
NameCommand name, without spaces. No prefix is added, so start it with your own name (for example road_count).
UsageOne line of usage (for example "road_count [블록 id]")
HelpOne line of description
RunArguments (the words after the name) → result text shown in the console
host.AddCommand(new NpCommand("road_count", "road_count [블록 id]", "선택 안에서 그 블록 수를 셈", args =>
{
string id = args.Length > 0 ? args[0] : "minecraft:stone";
long n = host.World.Blocks(host.World.Selection).LongCount(b => b.State.StartsWith(id));
return $"{id}: {n:N0}칸";
}));
  • If the name already exists (built-in or another plugin), that command is not attached.
  • If Run throws, the console shows a failure and the plugin is isolated.

AddImporter · AddExporter — file formats

Section titled “AddImporter · AddExporter — file formats”
public sealed record NpFileFormat(string Id, string Label, string[] Extensions, Action<string> Run, int Order = 500);
FieldDescription
LabelMenu text. Appears as File › Import: Label · Build › Export: Label.
ExtensionsList of extensions (without the dot, for example new[] { "csv" }). Used as the file dialog filter.
RunReceives the full path of the file the user chose.
OrderOrder within the menu
host.AddExporter(new NpFileFormat("road_stats", "블록 통계 CSV", new[] { "csv" }, path =>
{
var rows = host.World.Blocks(host.World.Selection)
.GroupBy(b => b.State.Split('[')[0])
.OrderByDescending(g => g.Count())
.Select(g => $"{g.Key},{g.Count()}");
File.WriteAllLines(path, rows.Prepend("block,count"), new System.Text.UTF8Encoding(true));
}));
  • The default file name in the export dialog is <world name>.<first extension>.
  • When an importer changes blocks, wrap the changes in a single World.Edit too.
public sealed record NpContentTab(string Id, string Label, Func<NpUi> Build, int Order = 500);
FieldDescription
LabelPanel title. The panel also appears under this name in the Window menu’s panel list.
BuildPanel content (NpUi). Called once when the plugin is enabled.
OrderOrder in the panel list
host.AddContentTab(new NpContentTab("panel", "도로", () => new NpUi()
.Title("도로 만들기")
.Number("폭", _width, 1, 9, 1, v => _width = (int)v)
.Check("가로등", _lamps, v => _lamps = v)
.Button("선택 영역에 깔기", () => host.Message($"폭 {_width}"))));
  • The panel is created closed in the bottom area. The user opens it from the Window menu. Once open, it can be dragged to another area or popped out as a floating window.
  • The icon is the plugin image.
  • How to use NpUi is covered in Panel UI (NpUi).
public sealed record NpMode(string Id, string Label, Func<NpUi>? Panel = null, Func<NpClick, bool>? OnClick = null,
Action? Enter = null, Action? Exit = null, string Shortcut = "", int Order = 500);
public sealed record NpClick(NpCell Hit, NpCell Place, bool Shift, bool Ctrl);
FieldDescription
LabelMode name (mode row tooltip · Tools panel header)
PanelContent shown in the Tools panel. Built once when first shown, then reused. If omitted, only the title appears.
OnClickLeft click in the viewport. Hit = the block cell that was hit, Place = the cell outside that face (where to place). Return true if you handled it.
Enter · ExitWhen entering · leaving the mode
ShortcutShortcut string that switches to the mode
OrderOrder among plugin modes. Plugin modes always come after the built-in modes.
host.AddMode(new NpMode("flag", "깃발 꽂기",
Panel: () => new NpUi().Title("깃발").Label("왼쪽 클릭 = 깃발 · Shift+클릭 = 지우기"),
OnClick: c =>
{
var p = c.Shift ? c.Hit : c.Place;
host.World.Edit(c.Shift ? "깃발 지우기" : "깃발", e =>
{
if (c.Shift) { for (int i = 0; i < 3; i++) e.Clear(p.X, p.Y + i, p.Z); return; }
e.Set(p.X, p.Y, p.Z, "minecraft:oak_fence");
e.Set(p.X, p.Y + 1, p.Z, "minecraft:oak_fence");
e.Set(p.X, p.Y + 2, p.Z, "minecraft:red_wool");
});
return true;
},
Exit: () => host.Draw.Clear()));
  • If OnClick is set, every viewport left click in that mode goes to OnClick. The default block placement does not happen.
  • Clicks that hit no block (the sky) do not call OnClick.
  • Dragging (moving while held) cannot be received yet. Only clicks are received.
  • Modes after the first 8 in the mode row go into More. Plugin modes are usually in More.
  • If the plugin is disabled while its mode is in use, the editor returns to Block brush mode.
public sealed record NpActorType(string Kind, string Label, string Color = "#9a9a9a");
FieldDescription
KindActor kind string. Start it with your own name (for example "myroad.zone").
LabelText in the Outliner Type column
ColorColor of the Type column, "#rrggbb". Gray if it cannot be parsed.
host.AddActorType(new NpActorType("myroad.zone", "도로 구역", "#44aaff"));
  • Sets the text and color shown when actors of that kind appear in the Outliner.
  • The SDK has no API for creating actors yet.
void AddCellGuard(Func<int, int, int, bool> guard);

No edit can change a cell for which the guard returns true. This applies to brushes, shapes, fill, console commands and other plugins’ World.Edit. Undo and redo are not checked.

host.AddCellGuard((x, y, z) => y < 0); // cells below y 0 cannot be changed
  • With multiple guards, the cell is blocked if any one returns true.
  • Called for every cell. Keep it very light (a dictionary lookup or box comparison at most).
void AddSymmetry(Func<IReadOnlyList<NpBlock>, IEnumerable<NpBlock>> expand);

Receives the list of cells a user edit (place · break · brush · shape · drag box · paint) will change, and returns an expanded list. It is chained after the built-in symmetry.

host.AddSymmetry(cells => cells.Concat(cells.Select(b => b with { X = -b.X }))); // mirror across the X=0 plane
  • Return the list including the original cells. If you leave them out, the original edit disappears.
  • Cells being set to air (break) have State "minecraft:air".
public sealed record NpShot(string Id, Func<INpShotApi, Task> Run);
INpShotApi memberDescription
WorldThe world (INpWorld)
LookAt(float ex, float ey, float ez, float tx, float ty, float tz)Points the camera from the eye position → at the target point
Frames(int n)Waits n frames (await)
Check(bool ok, string what)Records one verdict (PASS/FAIL)
Shot(string name)Saves the screen as PNG (await)

Examples, how to run them and limitations are in Testing and debugging.

public sealed record NpShortcut(string Id, string Label, string Key, Action Run, string Mode = "");
FieldDescription
IdShortcut id. Keys rebound by the user are saved under this id.
LabelName shown in the shortcuts window. Prefixing it with the plugin name makes it easier to find (for example "도로: 속 비우기").
KeyDefault key string. Follow the table below.
RunRuns when pressed
Mode"" = everywhere. A mode id = only in that mode. For your own mode, use the id you passed to AddMode as-is.
host.AddShortcut(new NpShortcut("hollow_key", "도로: 선택 속 비우기", "Ctrl+Alt+H", Hollow));
host.AddShortcut(new NpShortcut("flag_undo", "깃발: 마지막 지우기", "Shift+J", RemoveLast, Mode: "flag"));
Key stringParsed
Ctrl+Alt+H · ctrl + alt + h · Cmd+K (= Ctrl)Yes
Shift+F5 · Ctrl+Enter · Alt+Space · Ctrl+[Yes
J (a single key without modifiers)Yes. Not recommended, because it easily collides with built-in keys.
1~8 · W/E/R · Ctrl++No → registration throws → the plugin does not enable
  • A key = Ctrl · Alt · Shift plus one key. Key names are a single letter or digit, F1–F24, or names such as Enter · Space · Delete · Esc.
  • Users rebind keys in Tools › Shortcuts… (Ctrl+Alt+K). Rebound keys persist when the plugin is re-enabled.
  • Registration succeeds even if the key collides with another key. The collision is shown in red in the shortcuts window and logged to the Output Log.
  • For the same key, a mode-only key takes precedence over an everywhere key. Fixed editor keys and default keys (Ctrl+Z · F1–F7 and so on) win.