PyNK - Engine Python

Experimental PyNK - Engine Python 1.0.0

# PyNK v2.1.0



Atualização de correções. Sem mudanças de API visíveis para scripts — tudo
que já funcionava continuava funcionando do mesmo jeito, só que agora funciona
de verdade.

## Corrigido

- **Permissões de script não tinham efeito real.** `SCRIPT_PERMISSIONS` e
`api.register_permission(...)` só guardavam o nome numa tabela interna do
PyNK, sem nunca registrar no `PluginManager` do Nukkit. Como o Nukkit trata
qualquer permissão desconhecida como "somente OP", declarar
`defaultValue=true` (ex: permissão automática `<script>.use`, pensada pra
liberar pra todo jogador) não tinha efeito nenhum na prática. Agora cada
permissão vira um `Permission` real, com remoção automática quando o
último script que eu usei foi descarregado.

- **`get_config_bool` sempre volta ao valor padrão em configs `.properties`.**
Esse formato entrega tudo como `String`, e o analisador só aceitava `Boolean`
puro. Agora é permitido `"true"/"false"/"yes"/"no"/"1"/"0"`.

- **Comandos duplicados sobrescreviam um ao outro sem aviso.** Dois scripts
registrando o mesmo nome de comando fez o segundo tomar o lugar do
primeiro silenciosamente — `add_sub_command`/`set_tab_completer` do
primeiro script passandom a mexer no comando errado. Ágora
`register_command` recusa o registro duplicado e loga um aviso claro.

- **`api.reload_module(...)` não se propagava pra quem já tinha dado
`require_module`.** Scripts que guardaram a referência do módulo num
variável antes de um reload ficaram presos na versão antiga. Ágora
`require_module` devolve um proxy que sempre resolve pro namespace mais
recente do módulo — reload passa a valer pra todo mundo que já importou.

- **`SCRIPT_DEPENDENCIES` só funcionava contra `scripts/modules/`.** Se a
dependência fosse outro script (não um módulo), o PyNK até validava como
"satisfeita", mas nunca carregava nem injetava nada. Agora resolva essa
ordem: módulo primeiro, depois outro script (carregando sob demanda se
ainda não estiver ativo), expondo os valores que ele exportou via
`api.export(...)`. Ciclos de dependência (A depende de B que depende de A)
são detectados e ignorados com aviso, em vez de travar o carregamento.

## Internos

- `TaskRegistry` passou a usar `CopyOnWriteArrayList`, alinhado com o alinhamento
`EventBridge` e `PyCommand`, evitando `ConcurrentModificationException` se
uma tarefa para cancelada durante o agendamento de outra.
- `/pynk list` não faz mais busca O(n²) por string para recompor o status de
cada script.

## Compatibilidade

Nenhuma mudança quebra scripts existentes. `SCRIPT_PERMISSIONS`,
`register_command`, `require_module`, `reload_module` e `SCRIPT_DEPENDENCIES`
continue com a mesma assinatura — só o comportamento por baixo dos panos
ficou correto.

English
PyNK v2.1.0

Bug-fix update. No visible API changes for scripts — everything that already worked continues to work the same way, except now it actually works properly.

Fixed

  • Script permissions had no real effect. SCRIPT_PERMISSIONS and api.register_permission(...) only stored the name in an internal PyNK table without ever registering it with Nukkit's PluginManager. Since Nukkit treats any unknown permission as "OP-only", declaring defaultValue=true (e.g. an automatic <script>.use permission intended to allow every player) had no practical effect. Now each permission becomes a real Permission, with automatic removal when the last script using it is unloaded.
  • get_config_bool always fell back to the default value in .properties configs. This format provides everything as a String, while the parser only accepted actual Boolean values. It now accepts "true"/"false"/"yes"/"no"/"1"/"0".
  • Duplicate commands silently overwrote each other. When two scripts registered the same command name, the second one silently replaced the first — causing add_sub_command/set_tab_completer from the first script to operate on the wrong command. Now register_command rejects duplicate registrations and logs a clear warning.
  • api.reload_module(...) did not propagate to scripts that had already called require_module. Scripts that stored a module reference before a reload remained stuck on the old version. Now require_module returns a proxy that always resolves to the module's latest namespace — reloads now take effect for everyone who has already imported the module.
  • SCRIPT_DEPENDENCIES only worked with scripts/modules/. If the dependency was another script (rather than a module), PyNK would validate it as "satisfied" but would never load or inject anything. Dependency resolution now works in the correct order: module first, then another script (loading it on demand if it is not already active), exposing the values it exported through api.export(...). Dependency cycles (A depends on B, which depends on A) are detected and skipped with a warning instead of blocking the loading process.
Internals

  • TaskRegistry now uses CopyOnWriteArrayList, aligned with EventBridge and PyCommand, preventing ConcurrentModificationException if a task is cancelled while another task is being scheduled.
  • /pynk list no longer performs an O(n²) string search to reconstruct the status of each script.
Compatibility

No existing scripts are broken. SCRIPT_PERMISSIONS, register_command, require_module, reload_module, and SCRIPT_DEPENDENCIES keep the same signatures — only their underlying behavior has been corrected.
PyNK v2.0 - Python para Nukkit via Jython

Plugin para Nukkit que permite criar plugins/scripts em Python usando Jython,
com suporte completo a eventos, comandos, scheduler, módulos, permissões
e configuração em múltiplos formatos.

Novidades na v2.0
Múltiplas Pastas de Scripts

Os scripts agora podem ser organizados em subpastas:

plugins/PyNK/scripts/
example_plugin.py
examples/
subfolder_example.py
module_consumer.py


O scanner recursivo carrega automaticamente todos os arquivos .py.

A pasta modules/ é reservada e ignorada pelo scanner.


Comandos:



  • /pynk load examples/subfolder_example - carrega script em subpasta
  • /pynk scan - lista todos os scripts encontrados com status

Sistema de Permissões

Cada script pode declarar permissões via metadados:


SCRIPT_PERMISSIONS = [
"meuplugin.use",
"meuplugin.admin",
"meuplugin.action"
]


As permissões são registradas automaticamente. Uso no script:


def meu_comando(sender, label, args):
if not api.check_permission(sender, "meuplugin.admin"):
return True
# ...


Comandos:



  • /pynk perms - lista todas as permissões
  • /pynk perms <script> - permissões de um script específico

Sistema de Módulos

Módulos são scripts Python reutilizáveis localizados em scripts/modules/.


plugins/PyNK/scripts/
modules/
utils.py
core/
database.py
meu_plugin.py


Uso:


utils = api.require_module("utils")
result = utils.format_player_name("steve")

# Subpasta:
db = api.require_module("core.database")


Dependências automáticas:


SCRIPT_DEPENDENCIES = ["utils", "core.database"]


Comandos:



  • /pynk modules - lista módulos carregados e disponíveis

Comandos Dinâmicos com Subcomandos

api.register_command("guild", "Sistema de guildas", "/guild <sub>", None, "guild.use", cmd_guild)

api.add_sub_command("guild", "create", "Criar guilda", cmd_create, "guild.create")
api.add_sub_command("guild", "info", "Informações da guilda", cmd_info, "guild.use")
api.add_sub_command("guild", "delete", "Deletar guilda", cmd_delete, "guild.admin")

# Tab-complete customizado:
api.set_tab_completer("guild", tab_guild)


Exportação/Importação entre Scripts

# No script A:
api.export("minha_funcao", minha_funcao)

# No script B:
func = api.import_from("script_a", "minha_funcao")
func()


Configuração JSON/YAML/Properties

JSON (recomendado):


api.load_config("settings", "json", {
"database": {
"host": "localhost",
"port": 3306
},
"max_players": 100
})

api.get_config_string("settings", "database.host") # "localhost"
api.get_config_int("settings", "database.port") # 3306
api.get_config_bool("settings", "features.pvp", True) # True
api.get_config_list("settings", "blocked_items") # [...]
api.set_config_value("settings", "max_players", 200)
api.save_config_file("settings")
api.reload_config("settings")


YAML e Properties também são suportados:


api.load_config("messages", "yaml", {...})
api.load_config("server", "properties", {...})


Config legado (compatibilidade com v1.0):


api.get_config("chave", "default")
api.set_config("chave", valor)
api.save_config()


API Completa

Mensagens & Players


  • api.broadcast(message) - envia mensagem para todos
  • api.broadcast(message, permission) - mensagem filtrada por permissão
  • api.get_player(name) / api.get_player_exact(name)
  • api.get_online_players() - lista de jogadores online
  • api.send_message(sender, message)
  • api.colorize(message) - converte & em cores
  • api.dispatch_command(cmd) - executa comando como console

Eventos


  • api.register_event("PlayerJoinEvent", callback)
  • api.unregister_event("PlayerJoinEvent", callback)
  • api.list_events() - lista todos os eventos indexados
  • api.count_event_handlers("EventName")

Comandos


  • api.register_command(name, desc, usage, aliases, perm, callback)
  • api.add_sub_command(parent, name, desc, callback, perm)
  • api.set_tab_completer(name, completer)

Scheduler


  • api.schedule_task(callback, delay_ticks)
  • api.schedule_repeating_task(callback, period_ticks)
  • api.cancel_task(task_id)

Config


  • api.load_config(name, format, defaults) - carrega config
  • api.get_config_value(name, key) / get_config_string/int/double/bool/list/section
  • api.set_config_value(name, key, value)
  • api.remove_config_key(name, key)
  • api.config_contains(name, key)
  • api.save_config_file(name) / api.reload_config(name)
  • api.save_all_configs() / api.list_configs()

Permissões


  • api.has_permission(sender, perm) - verifica
  • api.check_permission(sender, perm) - verifica e notifica
  • api.register_permission(perm, default, desc)
  • api.get_script_permissions()

Módulos


  • api.require_module(name) - carrega módulo
  • api.reload_module(name) - recarrega módulo
  • api.is_module_loaded(name) - verifica se está carregado
  • api.list_available_modules() / api.list_loaded_modules()

Exportação/Importação


  • api.export(name, value) - exporta função/variável
  • api.import_from(script_name, name) - importa de outro script

Comandos do /pynk





























































ComandoDescriçãoPermissão
/pynk listLista scripts carregadospynk.script.list
/pynk load <path>Carrega script (suporta subpastas)pynk.script.load
/pynk unload <name>Descarrega um scriptpynk.script.unload
/pynk reload <name>Recarrega um scriptpynk.script.reload
/pynk reloadallRecarrega todos os scriptspynk.script.reload
/pynk events [filter]Lista eventos indexados(nenhuma)
/pynk info <name>Informações detalhadaspynk.script.info
/pynk modulesLista módulos(nenhuma)
/pynk perms [script]Lista permissões(nenhuma)
/pynk scanEscaneia pastas e mostra statuspynk.admin

Metadados de Scripts

PLUGIN_NAME = "MeuPlugin"
PLUGIN_VERSION = "1.0.0"
PLUGIN_AUTHOR = "Autor"
PLUGIN_DESCRIPTION = "Descrição"
SCRIPT_PERMISSIONS = ["meuplugin.use", "meuplugin.admin"]
SCRIPT_DEPENDENCIES = ["utils"]


Variáveis Globais Injetadas





























































VariávelTipoDescrição
apiPyAPIAPI de conveniência
serverServerInstância do servidor Nukkit
pluginPyNKPluginInstância do plugin PyNK
loggerLoggerLogger do plugin
SCRIPT_NAMEstrNome do script
SCRIPT_FILEstrCaminho absoluto do arquivo .py
SCRIPT_FOLDERstrSubpasta relativa (vazio se raiz)
DATA_FOLDERstrPasta de dados do script
MODULE_NAMEstrNome do módulo (apenas módulos)
MODULE_PATHstrCaminho do módulo (apenas módulos)

Build

mvn clean package


O arquivo final .jar com Jython embutido será gerado em target/PyNK.jar.

Licença

MIT




PyNK v2.0 - Python for Nukkit via Jython

A plugin for Nukkit that allows creating plugins/scripts in Python using Jython,
with full support for events, commands, scheduler, modules, permissions,
and multi-format configuration.

What's New in v2.0
Multiple Script Folders

Scripts can now be organized into subfolders:

plugins/PyNK/scripts/
example_plugin.py
examples/
subfolder_example.py
module_consumer.py


The recursive scanner automatically loads all .py files.

The modules/ folder is reserved and ignored by the scanner.


Commands:



  • /pynk load examples/subfolder_example - loads a script from a subfolder
  • /pynk scan - lists all detected scripts with status

Permission System

Each script can declare permissions via metadata:


SCRIPT_PERMISSIONS = [
"myplugin.use",
"myplugin.admin",
"myplugin.action"
]


Permissions are registered automatically. Usage in script:


def my_command(sender, label, args):
if not api.check_permission(sender, "myplugin.admin"):
return True
# ...


Commands:



  • /pynk perms - lists all permissions
  • /pynk perms <script> - permissions of a specific script

Module System

Modules are reusable Python scripts located in scripts/modules/.


plugins/PyNK/scripts/
modules/
utils.py
core/
database.py
my_plugin.py


Usage:


utils = api.require_module("utils")
result = utils.format_player_name("steve")

# Subfolder:
db = api.require_module("core.database")


Automatic dependencies:


SCRIPT_DEPENDENCIES = ["utils", "core.database"]


Commands:



  • /pynk modules - lists loaded and available modules

Dynamic Commands with Subcommands

api.register_command("guild", "Guild system", "/guild <sub>", None, "guild.use", cmd_guild)

api.add_sub_command("guild", "create", "Create guild", cmd_create, "guild.create")
api.add_sub_command("guild", "info", "Guild info", cmd_info, "guild.use")
api.add_sub_command("guild", "delete", "Delete guild", cmd_delete, "guild.admin")

# Custom tab-complete:
api.set_tab_completer("guild", tab_guild)


Export/Import Between Scripts

# In script A:
api.export("my_function", my_function)

# In script B:
func = api.import_from("script_a", "my_function")
func()


Config JSON/YAML/Properties

JSON (recommended):


api.load_config("settings", "json", {
"database": {
"host": "localhost",
"port": 3306
},
"max_players": 100
})

api.get_config_string("settings", "database.host") # "localhost"
api.get_config_int("settings", "database.port") # 3306
api.get_config_bool("settings", "features.pvp", True) # True
api.get_config_list("settings", "blocked_items") # [...]
api.set_config_value("settings", "max_players", 200)
api.save_config_file("settings")
api.reload_config("settings")


YAML and Properties are also supported:


api.load_config("messages", "yaml", {...})
api.load_config("server", "properties", {...})


Legacy config (v1.0 compatibility):


api.get_config("key", "default")
api.set_config("key", value)
api.save_config()


Full API

Messages & Players


  • api.broadcast(message) - message to all
  • api.broadcast(message, permission) - permission-filtered message
  • api.get_player(name) / api.get_player_exact(name)
  • api.get_online_players() - list of Player
  • api.send_message(sender, message)
  • api.colorize(message) - converts & to colors
  • api.dispatch_command(cmd) - executes command as console

Events


  • api.register_event("PlayerJoinEvent", callback)
  • api.unregister_event("PlayerJoinEvent", callback)
  • api.list_events() - lists all indexed events
  • api.count_event_handlers("EventName")

Commands


  • api.register_command(name, desc, usage, aliases, perm, callback)
  • api.add_sub_command(parent, name, desc, callback, perm)
  • api.set_tab_completer(name, completer)

Scheduler


  • api.schedule_task(callback, delay_ticks)
  • api.schedule_repeating_task(callback, period_ticks)
  • api.cancel_task(task_id)

Config


  • api.load_config(name, format, defaults) - loads config
  • api.get_config_value(name, key) / get_config_string/int/double/bool/list/section
  • api.set_config_value(name, key, value)
  • api.remove_config_key(name, key)
  • api.config_contains(name, key)
  • api.save_config_file(name) / api.reload_config(name)
  • api.save_all_configs() / api.list_configs()

Permissions


  • api.has_permission(sender, perm) - checks
  • api.check_permission(sender, perm) - checks and notifies
  • api.register_permission(perm, default, desc)
  • api.get_script_permissions()

Modules


  • api.require_module(name) - loads module
  • api.reload_module(name) - reloads module
  • api.is_module_loaded(name) - checks if loaded
  • api.list_available_modules() / api.list_loaded_modules()

Export/Import


  • api.export(name, value) - exports function/variable
  • api.import_from(script_name, name) - imports from another script

/pynk Commands





























































CommandDescriptionPermission
/pynk listLists loaded scriptspynk.script.list
/pynk load <path>Loads script (supports subfolder)pynk.script.load
/pynk unload <name>Unloads a scriptpynk.script.unload
/pynk reload <name>Reloads a scriptpynk.script.reload
/pynk reloadallReloads all scriptspynk.script.reload
/pynk events [filter]Lists indexed events(none)
/pynk info <name>Detailed script infopynk.script.info
/pynk modulesLists modules(none)
/pynk perms [script]Lists permissions(none)
/pynk scanScans folders and lists statuspynk.admin

Script Metadata

PLUGIN_NAME = "MyPlugin"
PLUGIN_VERSION = "1.0.0"
PLUGIN_AUTHOR = "Author"
PLUGIN_DESCRIPTION = "Description"
SCRIPT_PERMISSIONS = ["myplugin.use", "myplugin.admin"]
SCRIPT_DEPENDENCIES = ["utils"]


Injected Global Variables





























































VariableTypeDescription
apiPyAPIConvenience API
serverServerNukkit server instance
pluginPyNKPluginPyNK plugin instance
loggerLoggerPlugin logger
SCRIPT_NAMEstrScript name
SCRIPT_FILEstrAbsolute path of the .py file
SCRIPT_FOLDERstrRelative subfolder (empty if root)
DATA_FOLDERstrScript data folder
MODULE_NAMEstrModule name (modules only)
MODULE_PATHstrModule path (modules only)

Build

mvn clean package


The final jar with embedded Jython will be generated at target/PyNK.jar.

License

MIT
Top