Для быстрого ознакомления с NoxScript, см как начать.
Некоторые из вопросов могут быть уже покрыты в Q&A.
Так же доступны примеры.
Если вопросы не нашли ответа, можно задать их здесь
или в нашем Discord.
Подразделы NoxScript
Как начать
Это руководство поможет создать ваш первый скрипт используя интерфейс скриптов OpenNox на языке Go.
Первые шаги
Заметка
Удостовертесь что у вас установлен OpenNox. Оригинальный Nox, Reloaded или иная версия не будут работать!
Чтобы создать ваш первый скрипт, скопируйте существующую папку одной из карт, либо создайте новую карту в редакторе.
Если карта была скопирована, удостоверьтесь что файл карты внутри папки тоже был переименован.
Готово! Теперь запустите карту в OpenNox, откройте консоль и вы должны увидеть там сообщение hello!.
Информация
Вам не нужно устанавливать Go или какой-либо другой компилятор чтобы писать скрипты для OpenNox.
Тем не менее, правильная настройка Go поможет включить поддержку IDE, проверку типов в скриптах, автодополнение и т.д.
По этому, мы крайне советуем это сделать.
Полная настройка
Для полной настройки нам понадобиться:
Go 1.20+ (для проверки типов, работы с зависимостями)
Скриптовые движки работают следующим образом: обычно один из движков явлется остновным (NS4 в данном случае),
который предоставляет весь функционал доступный в OpenNox. Остальные движки являются лишь слоем поверх него.
Тем не менее эти слои важны! Они предоставляют уже знакомый интерфейс для разработчиков карт (например NS3 или EUD).
Благодаря этому, полность безопастно использовать несколько движков в одной карте. Если есть опыт в NS3 - лучше начать с него.
Если вы только начинаете разбираться с Nox скриптами - выбирайте последний NS движек (NS4).
Ссылки выше ссылаются на документацию языка Go для каждого из движков - это основной источник документации по функциям и классам доступным в каждом из движков. Это руководство даст лишь базовое знание про настройку скриптов.
Использование движков
Тогда, как использовать эти движки?
Если вы пропустили полную настройку, то для это нужно лишь добавить import в Go файл и использовать его:
Можно заметить что мы вызываем IsTalking без префикса ns3.
Это, тем не менее, не типично для Go, т.к. из кода не понятно объявлена ли функция IsTalking в карте или в другом пакете.
Теперь, возвращаясь к полной настройке IDE, вы могли заметить что импорты не были опознаны.
Это происходит потому что нам нужно обновить зависимости проекта в go.mod. Мы можем сделать это двумя способами:
go mod tidy в терминале (требует Git).
Либо навести мышку на нераспознанное имя импорта, выбрать Quick fix..., и далее go get ....
В обоих случаях зависимости должны загрузиться (то есть тот NS движек который вы выбрали) и добавиться в go.mod.
Теперь автодополнение должно работать корректно для пакетов движка.
Обновление движков
Скриптовые движки периодически обновляются в OpenNox чтобы добавить новые возможности или исправить ошибки.
Это делаться автоматически, новый релиз OpenNox подключит новый движек без каких либо действий со стороны разработчика карт.
Тем не менее, IDE не будет распознавать новые функции движка до тех пор пока они не обновяться в проекте карты.
Самый простой способ обновить это открыть файл go.mod в папке карты и найти строку с движком который нужно обновить:
require (
github.com/noxworld-dev/noxscript/ns/v4 v4.3.0
// ... other lines
)
Просто обновите версию в нем, и запустите go mod tidy.
Полный список версий доступен при нажатии на версию в документации пакета скриптов
а так же на GitHub.
Пакеты
Теперь мы знаем как подключить движки скриптов и запускать скрипты. Как же лучше структурировать код?
В Go, единичным проектом является “пакет” (package): директория с одним или несколькими Go файлами.
Имена самих Go файлов не имеют значения, единственное требование это то что все файлы в директории должны иметь одинаковую
строку package в начале файла. OpenNox так же добавляет ещё одно ограничение: package должен совпадать с именем карты.
В остальном, организация кода полностью зависит от вас. Все функции и переменные обхявленные в одном файле
доступны во всех других файлах в этой директории.
Как я могу …
Это руководство лишь поверхностно, в паре шагов показывает как работать со скриптами.
Возможно вам так же интересно поближе познакомиться с языком Go. Язык очень простой, по этому это не должно составить труда.
Интерактивный Go тур является хорошим местом чтобы начать.
Некоторые из вопросов могут быть уже покрыты в Q&A.
Так же доступны примеры.
Если вопросы не нашли ответа, можно задать их здесь
или в нашем Discord.
Примеры
На этой странице приведен список примеров скриптов для OpenNox.
Here you can find a full list of functions provided by NS3 runtime.
History
Vanilla Nox has its own script runtime based on compiled source files. Since original Nox editor was never released,
community recreated the script compiler from scratch.
Since there’s no official version, the scripts had different dialects, one of the latest is known as NoxScript 3.
There’s also a Panic’s EUD compiler that uses memory manipulation to extend script functionality.
OpenNox supports vanilla scripts with no changes required. Additionally, it implements a more advanced script runtime.
New runtime
To make the transition to the new script runtime smoother, OpenNox provides a compatibility layer for NoxScript 3.
The idea is that all the functions from NoxScript 3 should work exactly the same,
accept same arguments, etc. This way all existing functions can be copied with almost no changes (only syntax will vary).
To avoid confusion, we usually refer to vanilla scripts as “NoxScript 3”, while the new compatibility layer is called NS3.
If you have existing scripts you want to migrate, see our migration guide.
Otherwise, it’s better to start directly from the NS4, which is our current script version.
Подразделы NoxScript 3
Миграция с оригинала
Это руководство поможет перенести существующие NoxScript 3
карты на NS3 в OpenNox.
В этом руководстве мы будем называть оригинальные скрипты Nox как “NoxScript 3”, в то время как “NS3” будет обозначать новые
Go скипты для OpenNox.
Зачем?
Первый логичный вопрос: зачем? OpenNox хорошо работает с NoxScript 3 скриптами.
Есть пара причин перейти на скрипты OpenNox:
NoxScript 3 ограничен ровно тем что оригинальный Nox движек открывает. Возможностей не много. И никаких обновлений не будет.
NS3 в OpenNox является прямой заменой. Абсолютно те же функции доступны в нем.
Мы предоставляем инструмены для перевода 90% кода скрипта автоматически. Только мелкие правки необходимы.
NS4 (и далее) будут иметь всё больше и больше возможностей в будущем, включая поддержку модов.
Не нужен отдельный компилятор скриптов. Скрипты запускаются напрямую из кода.
Более полная система типов: настоящие массивы, словари (map), стркутуры, классы.
Доступны библиотеки: работа со строками, полная поддержка UTF8, математика, сортировка, и т.д.
Лучшая производительность: все библиотеки скомпилированы в OpenNox и запускаются нативно.
Лучшая отладка: если скрипт “падает”, выведется трасировка. Скрипты так же могут самостоятельно справиться с ошибкой!
Язык Go используемый в скриптах так же используется в самом OpenNox. Возможно однажды будет желание присоединиться?
Было бы несправедливо не упоминуть про недостатки:
Скрипты работают только в OpenNox.
Нужно изучить новый язык для скриптов.
Скриптам возможно необходимо делать больше для правильного сохранения/загрузки.
Если вы знакомы с EUD компилятором от Panic и возможно рассматривали его, см это руководство.
In general, we believe that OpenNox is the future of Nox modding, thus porting your map may give it an entirely new life!
How?
There are two path currently: converting the compiled script from the map or converting the source.
Converting the map script
You’ll need a recent noxtools installed (assuming you have Go 1.20+ installed):
go install github.com/noxworld-dev/opennox-lib/cmd/noxtools@latest
From the map directory:
noxscript ns decomp <mapname>.map
It will print a decompiled source code converted to Go and NS3 runtime.
Because of the limitation of Nox script assembly:
All variable names will be lost.
Some control flow may be replaced with goto.
But after fixing these issues, you should be ready to go!
Converting the source
TODO: Add a guide for using cxgo to automate it.
Currently, you’ll have to manually convert the source.
Until we automate it as well, please consider converting the extracted map script, as shown above.
NoxScript 3 is similar to C, which has a lot in common with Go.
However, Go syntax is slightly different in regard to types.
Conversion steps will include:
Swapping argument and variable names with types: int a -> a int.
Adding either var or const to variable definitions: int a -> var a int.
Moving function return type to the end: int foo() -> func foo() int.
Moving { to the previous line. E.g. func foo()\n{ -> func foo() {. Same for if, else, for.
Adding { ... } to if and else which do not have them: if (1) foo() -> if (1) { foo() }.
Removing void from returns.
Fixing variable types (Go doesn’t allow implicit type conversion).
After this, add the following header to your file:
Dot import should automatically resolve all references to NS3 functions.
After conversion
Limitations
There are some temporary limitations you should be aware of:
Timers will stop each time the map is reloaded. You’ll need to restart them from the script.
All callbacks will reset when map is reloaded. You’ll need to set them again from the script.
These issues will be resolved eventually.
New: Syntax
We highly recommend checking our Go tour to get familiar with the syntax, but we’ll give a short recap here.
File structure
All files must start with package <mapname>:
packageexample
It can be followed by one or more package imports:
import"fmt"import"strconv"// it is typical to group them:
import("fmt""strconv")
Then global variables and/or functions follow. Order of declarations doesn’t matter.
Variables and types
Most notable syntax distinction: in Go, the type name is on the right side, instead of on the left as in NoxScript 3:
varxint
intx;
Note that ; is no longer needed, and variable declaration must start with var (or const).
There’s a very good reason why types are on the right: it makes reading complex types more natural.
Just read them left to right!
For example, array: var x [3][2]int simply reads left to right as “array of 3 elements, each containing 2 int values”.
Much better than a random order of int x[2][3];.
Same for pointers: *[2]int reads “pointer to an array of 2 ints”. Compare it to int* x[2];.
Functions
Function declarations are also different:
They must start with func.
Types for arguments are on the right.
Return type is after the arguments.
Void return type must be omitted.
The opening {must be on the same line as the function header.
Arguments with the same types can be grouped.
Multiple returns are supported.
funcfoo(aint){}funcbar(x,yint,sstring)int{}
voidfoo(inta){}intbar(intx,inty,strings){}
Control flow
All control flow structures require the opening { to be on the same line, for example:
if(x){foo()}if(y){bar()}
if(x)foo();if(y){bar();}
The () in the condition is also optional:
ifx{foo()}ify{bar()}
if(x)foo();if(y){bar();}
Same rules for { apply for else:
ifx{foo()}else{bar()}
if(x)foo()elsebar()
Loops
Loops must not include ( and have same rules in regard to {:
variintfori=0;i<10;i++{}
inti;for(i=0;i<10;i++){}
Loop that use whilemust use for keyword:
forx{}
while(x){}
New: Core types
In original NoxScript 3, there were only a few types available: int, float, string, object.
In NS3 the list is much longer: bool, int, uint, float32 (analog of float), string, any, etc.
The object type is replaced with more specific types from NS3 package:
ObjectID,
ObjectGroupID,
WaypointID, etc.
An important distinction is that Go doesn’t allow implicit type conversion.
For example, in NoxScript it was okay to have an int variable and put an object there.
In NS3, this is requires an explicit type conversion: int(x). But, of course, it’s better to have correct types for your variables.
Another distinction of NS3 is the support of direct conversions between int and float.
It is done the same way: int(x) or float32(x).
Converting between int and string is supported via IntToString,
but it’s better to use Go’s standard library instead: strconv.Itoa.
It also supports conversion from string to int via Go’s standard library: strconv.Atoi.
Note, that this function may return an error, which you are free to ignore (with _):
x,_:=strconv.Atoi("1")
New: Strings
NoxScript 3 had a limitation that a frequent + operation on strings overflows a string table.
There’s no such limitation in NS3: any number of strings can be created.
Printing to strings is also supported with Go’s fmt.Sprintf:
Even though strings can be created with + and individual bytes can be accessed with s[i],
strings are immutable in Go! This means, once created, they cannot be edited, only new ones can be created.
If you need to change a few characters, consider converting to byte array, making changes, and converting back:
In NoxScript 3, only fixed arrays are supported. NS3 has support for Go slices, which are arrays with dynamic length:
vararr[]intfori:=0;i<3;i++{arr=append(arr,i+1)// adds elements to the end
}// len(arr) == 3
inti;intarr[3];for(i=0;i<3;i++){arr[i]=i+1;}
New: Structures
NS3 completely supports custom struct types. They are very similar to classes in other programming languages.
Let’s say we want to build an RPG map, and we want to record a new character class for all player units:
typeMyUnitstruct{IDns.ObjectID// types on the right: field "ID" with type "ns.ObjectID"
Classstring}funcinit(){unit:=ns.Object("Bob")// creates a new struct instance, takes pointer to it
myUnit:=&MyUnit{ID:unit,Class:"archer"}changeClass(myUnit,"shaman")}// changeClass accepts a pointer to struct be able to change fields.
// Removing the pointer will make a copy of the struct for this function!
funcchangeClass(unit*MyUnit,clstring){unit.Class=cl}
The changeClass function can also be rewritten as a method on MyUnit struct:
typeMyUnitstruct{IDns.ObjectID// types on the right: field "ID" with type "ns.ObjectID"
Classstring}// changeClass is a method on MyUnit struct pointer.
// Removing the pointer will make a copy of the struct for this function!
func(u*MyUnit)changeClass(clstring){u.Class=cl}funcinit(){unit:=ns.Object("Bob")myUnit:=&MyUnit{ID:unit,Class:"archer"}// now changeClass is available as a method on the struct instance
myUnit.changeClass("shaman")}
For developer coming from C, new structs always have their fields initialized to zero vales.
New: Dictionaries (maps)
NS3 support dictionaries/sets, which are unordered collections of values indexed by a key of any type.
For example, we made a MyUnit struct in the previous example. But how to quickly find MyUnit for ns.ObjectID?
typeMyUnitstruct{IDns.ObjectID// types on the right: field "ID" with type "ns.ObjectID"
Classstring}// mapUnits maps ns.ObjectID to *MyUnit.
// All maps must be created with make before use!
varmapUnits=make(map[ns.ObjectID]*MyUnit)funcinit(){createMyUnits()findAndChangeClass()}funccreateMyUnits(){unit:=ns.Object("Bob")myUnit:=&MyUnit{ID:unit,Class:"archer"}// add new record to the map, index by ID
mapUnits[unit]=myUnit}funcfindAndChangeClass(){unit:=ns.Object("Bob")// find by ID
myUnit:=mapUnits[unit]ifunit==nil{return// not found
}myUnit.Class="shaman"}
Keys can also be deleted from the map:
delete(mapUnits,unit)// delete by unit ID
EUD от Panic'а
History
Vanilla Nox has its own script runtime based on compiled source files. Since original Nox editor was never released,
community recreated the script compiler from scratch.
Since there’s no official version, the scripts had different dialects, one of the latest is known as NoxScript 3.
However, the script runtime of Nox was very limited. Eventually the community found a way to inject custom code into the
game, which lead to a new kind of map scripts, usually referred as “memhacks”.
Panic’s EUD is the most advanced project of this kind.
It uses memory manipulation to implement a lot of new functionality for the scripts.
Unfortunately, memory manipulation usually targets a single binary version (vanilla Nox), and thus cannot run on
other versions such as OpenNox. Because of this OpenNox does not support EUD scripts.
New runtime
Even though OpenNox cannot support scripts that use memory manipulation, we still can provide similar functions
in out own script runtime. Thus, OpenNox provides a compatibility layer for EUD scripts.
The idea is that all the functions from Panic’s EUD which do not
expose the memory directly should work exactly the same, in OpenNox
This way all existing functions can be copied with almost no changes (only syntax will vary).
If you have existing scripts you want to migrate, see our migration guide.
Otherwise, it’s better to start directly from the NS4, which is our current script version.
Подразделы EUD от Panic'а
Миграция с EUD
This guide will help migrate existing Panic’s EUD
maps to NS3 and
EUD layer in OpenNox.
First, it’s important to understand that “memhacks” and direct memory access is technically not possible in OpenNox.
Here are a few reasons why:
Memhacks target a specific binary. This means it only works on one Nox version.
OpenNox constantly evolves, thus we cannot guarantee any specific memory layout.
Memhacks usually inject assembly code that targets a specific CPU architecture (Intel/AMD x86).
OpenNox can be potentially compiled for ARM (e.g. MacBooks, Android, RPi), thus script will stop working there.
We attempted to resolve these issues with EUD developer Panic, but we don’t see any willingness for collaboration
from his side. If you really want EUD to be supported directly in OpenNox, please reach out to Panic.
Resolving these technical challenges is possible, but requires tight collaboration, and will to do so from him, first and foremost.
Why?
First logical question: why bother with converting to OpenNox? EUD works great in original Nox.
A few reasons to migrate to OpenNox scripts:
OpenNox is evolving and supports new features that are impossible in vanilla (e.g. HD, better multiplayer, modding, etc).
EUD library in OpenNox is a drop-in replacement. Same functions will be supported.
NS4 (and beyond) will have even more features going forward.
You won’t need a separate script compiler. Scripts are run directly from source.
More comprehensive type system: proper arrays, dictionaries (map), structures, classes.
A lot more libraries included: string manipulation, full UTF8 support, math, sorting, etc.
Better performance: all libraries are compiled into OpenNox and run natively.
Better debugging: if script crashes, there’s a stack trace. Scripts can recover from it as well!
Go language used in scripts is used in OpenNox itself. Maybe one day you’ll decide to contribute?
In general, we believe that OpenNox is the future of Nox modding, thus porting your map may give it an entirely new life!
How?
At this point, we are still working on improving the EUD
compatibility layer, so quite a few functions might still be unavailable. Please talk to us for more details.
In general the conversion process is very similar to the one for converting NS3 source manually.
Same changes must be done to the source to align the syntax. We are working on a more automated process.
You need to be aware that all functions using GetMemory and SetMemory must be rewritten in any case.
The first thing to do is to check if functions you are using are already available in Panic’s EUD library.
If so, updating your EUD script to using these function will help you convert to our EUD
library later as well.
We aim to provide a good migration path for well-known EUD libraries, so if you copied code from another EUD project,
there’s a high chance we support it in one of the libraries.
Still, if you cannot find anything similar, talk to us. We will help convert your code and include necessary functionality
into next OpenNox release.
After conversion
The NS3 guide applies here as well, so make sure to check it out!