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

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()
        }))
    }
}
When targeting an IPlayer directly, 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")
        }
    }
}
Tip: 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
}
Note: GLFW key codes differ from CNPC's keyPressed event codes. Common GLFW codes: A=65, V=86, TAB=258, ESC=256, SPACE=32, LSHIFT=340, F1=290.