Script API — cnpcext
The cnpcext global is auto-injected into every CNPC script (NPC, Player, Block, Item). No imports needed.
cnpcext.openHtmlGui(eventOrPlayer, filename, width, height, jsonInitData)
Opens an HTML GUI on a player’s screen. Returns a GuiHandle for chaining options.
| Parameter | Type | Description |
|---|---|---|
eventOrPlayer |
Event or IPlayer | A CNPC event object (e) or any IPlayer reference |
filename |
String | HTML file path relative to <world>/customnpcs/scripts/ecmascript/ |
width |
Number | Browser width in pixels (0 = fullscreen) |
height |
Number | Browser height in pixels (0 = fullscreen) |
jsonInitData |
String | JSON string — available as window.cnpc.initData in the browser |
| Returns | GuiHandle |
Chainable handle — .setGuiEscapable(false) blocks Escape |
With an event (recommended)
Stores e.player, e.npc, e.API and the current ScriptContainer for event routing:
function interact(e) {
cnpcext.openHtmlGui(e, "shop.html", 0, 0, JSON.stringify({gold: 100}))
}
With an IPlayer (target any player)
Works like target.showCustomGui(gui) — opens the GUI on any player:
function interact(e) {
var target = e.player.getWorld().getPlayer("OtherPlayer")
if (target) {
cnpcext.openHtmlGui(target, "invite.html", 0, 0, JSON.stringify({
from: e.player.getDisplayName()
}))
}
}
e.npc will be null in htmlGuiEvent. e.player and e.API still work.
cnpcext.getClientBridge(mcPlayer)
Returns an IClientBridge for querying client-side state or sending data to an open browser.
var bridge = cnpcext.getClientBridge(player.getMCEntity())
bridge.sendToBrowser("update", JSON.stringify({score: 42}))
See Client Queries for query methods.
GuiHandle.setGuiEscapable(escapable)
Block or allow Escape from closing the HTML GUI. Chained off openHtmlGui().
function interact(e) {
cnpcext.openHtmlGui(e, "dialogue.html", 0, 0, JSON.stringify({text: "Choose wisely..."}))
.setGuiEscapable(false) // player must click a button to close
}
function htmlGuiEvent(e) {
if (e.eventName === "done") {
var bridge = cnpcext.getClientBridge(e.player.getMCEntity())
bridge.closeHtmlGui()
}
}
htmlGuiEvent(e) — Event Handler
Define this function in any script that calls openHtmlGui. It fires when the HTML calls window.cnpc.sendEvent().
function htmlGuiEvent(e) {
e.player // IPlayer — the player with the open GUI
e.npc // ICustomNpc — the NPC (null if opened via IPlayer)
e.API // NpcAPI instance
e.eventName // String — event name from sendEvent()
e.data // String — JSON data (use JSON.parse(e.data))
}
Example
function htmlGuiEvent(e) {
var data = JSON.parse(e.data)
if (e.eventName === "buy") {
var cost = data.price * data.qty
if (e.player.inventoryItemCount("minecraft:emerald") >= cost) {
e.player.removeItem("minecraft:emerald", cost)
e.player.giveItem(data.itemId, data.qty)
var bridge = cnpcext.getClientBridge(e.player.getMCEntity())
bridge.sendToBrowser("buyResult", JSON.stringify({
success: true,
balance: e.player.inventoryItemCount("minecraft:emerald")
}))
}
}
}
Server → Browser Push
Send data to an open HTML GUI at any time during a session:
var bridge = cnpcext.getClientBridge(player.getMCEntity())
bridge.sendToBrowser("eventName", JSON.stringify({key: "value"}))
The browser receives it via:
window.cnpc.onEvent("eventName", function(data) {
// data.key === "value"
})
Script Type Compatibility
cnpcext.openHtmlGui works with all CNPC script types:
| Script Type | Example Event | e.npc available? |
|---|---|---|
| NPC | interact(e), tick(e) |
Yes |
| Player | interact(e), chat(e), keyPressed(e) |
No |
| Block | interact(e), clicked(e) |
No |
| Item | interact(e), attack(e) |
No |
The htmlGuiEvent handler always fires on the same ScriptContainer that called openHtmlGui, regardless of script type.
cnpcext.entityId(entity)
New in v1.2.0 Fabric 1.21.1
Get the network entity ID from a CNPC IEntity wrapper. Used for HTML GUI/overlay entity displays.
| Parameter | Type | Description |
|---|---|---|
entity |
IEntity | Any CNPC entity wrapper: e.npc, e.player, etc. |
| Returns | int |
Network entity ID, or -1 if extraction fails |
cnpcext.openHtmlGui(e, "dialogue.html", 0, 0, JSON.stringify({
overlayEntities: [
{slot: 0, entityId: cnpcext.entityId(e.npc)},
{slot: 1, entityId: cnpcext.entityId(e.player)}
]
}))
See HTML API — Entity Display for the HTML-side setup.
cnpcext.registerCommand(name, jsonDefinition)
New in v1.1.0
Register a custom / command at runtime with full tab-completion. Commands registered this way are runtime only — they clear on server restart. For persistent commands, use /cnpcext command create instead.
| Parameter | Type | Description |
|---|---|---|
name |
String | Command name (alphanumeric + underscore, e.g. "shop") |
jsonDefinition |
String | JSON string defining permission and arguments |
Registration is idempotent — calling with the same name again is a no-op. Safe to call in init() even on a server with many players.
function init(e) {
cnpcext.registerCommand("shop", JSON.stringify({
permission: 0,
args: [
{name: "action", type: "string", suggestions: ["open", "list", "sell"]},
{name: "item", type: "string"}
]
}))
}
Argument Types
| Type | JSON | Tab Completion |
|---|---|---|
string |
{name: "x", type: "string"} |
Free text |
string + suggestions |
{name: "x", type: "string", suggestions: ["a","b"]} |
Shows suggestions |
integer |
{name: "x", type: "integer", min: 1, max: 100} |
Number in range |
player |
{name: "x", type: "player"} |
Online player names |
boolean |
{name: "x", type: "boolean"} |
true / false |
customCommand(e) — Event Handler
Define this in a player script. Fires when any script-registered or persistent command is executed.
function customCommand(e) {
e.player // IPlayer — the player who ran the command
e.command // String — command name
e.args // String — JSON arguments (use JSON.parse(e.args))
}
Example
function customCommand(e) {
var player = e.player
var args = JSON.parse(e.args)
if (e.command === "shop") {
if (args.action === "open") {
cnpcext.openHtmlGui(player, "shop.html", 0, 0, "{}")
} else if (args.action === "list") {
player.message("§eSword - 100 coins")
player.message("§eArmor - 250 coins")
}
}
}
customCommand(e) fires on all player script tabs. Each tab can handle different commands, or you can put all handlers in one tab.
Cutscene Methods
See the full Cutscene System docs for details.
cnpcext.startCutscene(player, "name") // play saved cutscene
cnpcext.startCutscene(player, "name", optionsJson) // with options
cnpcext.stopCutscene(player) // stop + restore
cnpcext.pauseCutscene(player) // freeze timeline
cnpcext.resumeCutscene(player) // continue
cnpcext.isInCutscene(player) // boolean
cnpcext.moveCamera(player, keyframesJson) // ad-hoc camera
cutscene(e) — Event Handler
Fires at each keyframe phase. Define in the script that started the cutscene.
function cutscene(e) {
e.player // IPlayer
e.cutsceneName // String
e.phase // int — keyframe index (0-based), -1 = end
}
Key State Polling (Server-Side)
New in v1.6.0
Query a player’s real-time key state from server scripts. Works even when overlays/GUIs are open — the client syncs key state via GLFW-level tracking.
var bridge = cnpcext.getClientBridge(player.getMCEntity())
bridge.isKeyHeld(86) // boolean — V key held? Uses GLFW key codes
bridge.getKeyHoldDuration(86) // int — approximate ticks held (0 if not)
bridge.isInGui() // boolean — any screen/GUI open
bridge.isTyping() // boolean — in chat input
bridge.getOpenScreen() // string — screen class name or ""
Hold-to-Show Overlay Pattern
var wasHeld = false
function tick(e) {
var bridge = cnpcext.getClientBridge(e.player.getMCEntity())
if (bridge.isTyping()) return
var held = bridge.isKeyHeld(86) // V
if (held && !wasHeld) {
cnpcext.openOverlay(e, "wheel", "radial.html", 0, 0, 0, 0, "{}")
bridge.setOverlayInteractive("wheel", true)
} else if (!held && wasHeld) {
cnpcext.closeOverlay(e.player, "wheel")
}
wasHeld = held
}
keyPressed event codes. Common GLFW codes: A=65, V=86, TAB=258, ESC=256, SPACE=32, LSHIFT=340, F1=290.