Modding
OpenNox provides a lot of tooling for modding the game. Some of it is still in development.
OpenNox provides a lot of tooling for modding the game. Some of it is still in development.
Vanilla Nox stores game balance values in an encoded gamedata.bin
file.
OpenNox allows overriding values specified in that file with the ones written in a text-based gamedata.yml
file.
To try it out, copy gamedata.yml to the Nox game directory (not OpenNox directory!). After this, you can edit it using any text editor (Notepad++ is recommended for Windows users).
Vanilla Nox stores spell configs in three different places:
thing.bin
fileThe engine defines a list of all supported spell IDs (e.g. SPELL_BLINK
) and the effect associated with it.
The thing.bin
then lists spells that should be enabled in the game (by ID) and additionally configures them.
Unfortunately, this configuration is quite limited.
The balance file may additionally tune per-level spell parameters, or special parameters that are unique to certain spells.
OpenNox extends this system and allows using spells.yml
file to configure new spell parameters, as well as old ones in one place.
Note: this modding feature is still in development. Not all the config options for spells are available in spells.yml
.
We will keep adding new ones in each version of OpenNox.
spells.yml
fileSince this feature is in the development, it is strongly advised to generate a fresh spells.yml
based on the latest
OpenNox version and your Nox game data.
This can be done by running OpenNox with NOX_DUMP_SPELLS=true
environment variable.
On Linux:
NOX_DUMP_SPELLS=true opennox
On Windows (via cmd.exe
):
set NOX_DUMP_SPELLS="true"
opennox.exe
This should create a spells.yml
in Nox game data directory (not OpenNox directory!).
Alternatively, you could copy the spells.yml file to your Nox game directory. Be aware that the sample file might be old and won’t list all options available in the engine.
spells.yml
Note that modifying spells.yml
currently requires OpenNox restart.
Usually, the spell section will look like this:
name: SPELL_BLINK
icon:
ind: 131860
icon_enabled:
ind: 131938
mana_cost: 10
price: 3000
flags:
- 1
- MOBS_CAN_CAST
- 128
- 1024
- CAN_COUNTER
- CANT_HOLD_CROWN
- 1048576
- CLASS_ANY
- 2147483648
phonemes: [cha, et, un]
title: thing.db:Blink
desc: thing.db:SPELL_BLINK_DESC
cast_sound: BlinkCast
---
Where:
name
- specifies spell ID to use; if effect
is not set, this also determines the spell effecteffect
- specifies spell ID which will control the actual effect of the spell; see adding spellsicon.ind
- the sprite index from video.bag
that is used as a spell iconicon_enabled.ind
- same as icon.ind
, but for enabled spell iconmana_cost
- mana cost of the spellprice
- base spell book price; Quest uses an additional multiplier for this priceflags
- different flags that controls which category spell belongs to, what it can target, etcphonemes
- a unique list of spell phonemes/gestures that invoke this spell; see spell castingtitle
- a string ID for the spell titledesc
- a string ID for the spell descriptioncast_sound
- a sound for casing a spellon_sound
- a sound for enabling a spelloff_sound
- a sound for disabling a spellThese are the only parameters that can be originally controlled via thing.bin
, and was ported to spells.yml
.
Apart from these, OpenNox provides more options for certain spells.
For example, SPELL_MAGIC_MISSILE
has a new section:
missiles:
spread: 16
projectile: MagicMissile
vel_mult: 0.1
offset: 4
speed_rnd_min: 0.80000001
speed_rnd_max: 1.2
search_dist: 600
Here, a count
parameter is omitted, because it is usually controlled via balance file (see MagicMissileCount
).
Specifying it here will override balance data.
When a special section like missiles
this is present, all parameters in there can be controlled individually for each spell level:
missiles:
# default parameters for all levels
spread: 16
projectile: MagicMissile
vel_mult: 0.1
offset: 4
speed_rnd_min: 0.80000001
speed_rnd_max: 1.2
search_dist: 600
# per-level configs
levels:
# levels 1-3: copied from balance file
- count: 1
- count: 2
- count: 3
# level 4: same number of missiles as lvl3, but longer homing distance
- count: 3
search_dist: 800
# level 5: make it ultimate: more missiles, longer distance, faster missiles
- count: 10
speed_rnd_min: 1.0
speed_rnd_max: 2.0
search_dist: 800
Not all flags are completely understood at this point. So we recommend to see what flags are set for existing spells and experiment by setting/unsetting them in your mod.
Names of the spell flags provided below may change in a freshly-generated spells.yml
,
but OpenNox will still support old names as well.
Player class flags:
CLASS_ANY
- spell can be used by any magic class (Conjurer and Wizard)CLASS_WIZARD
- spell can only be used by WizardCLASS_CONJURER
- spell can only be used by ConjurerTargeting flags
TARGETED
- spell is homingAT_LOCATION
- spell can be cast at a pointCANT_TARGET_SELF
- spell cannot be targeted at the characterCast flags:
NO_MANA
- spell doesn’t require manaNO_TRAP
- spell can’t be used in trapsINSTANT
- spell is instantDURATION
- spell is duration-basedOFFENSIVE
- spell is offensiveDEFENSIVE
- spell is defensiveCAN_COUNTER
- spell can be counteredMOBS_CAN_CAST
- mobs are allowed to use this spellCANT_HOLD_CROWN
- spell can’t be cast when holding a flag/crown/ballSpecial flags
SUMMON_SPELL
- this is the base Summon Creature spellSUMMON_CREATURE
- this is Summon for a specific creatureMARK_SPELL
- this is the base Mark Location spellMARK_NUMBER
- this is Mark Location with a specific numberGOTO_MARK_SPELL
- this is the base Go To Mark spellGOTO_MARK_NUMBER
- this is Go To Mark with a specific numberThere are some flags that don’t have names, which means we are not sure of its effect.
Currently, adding new spells in OpenNox is not supported.
Having said that it is possible to replace unused spell slots that already exist in the game.
In spell section there’s a name
parameter that defines the slot that the spell uses
and effect
for the engine to know which effect to run.
Usually these two IDs are the same (or effect
is empty and derived from name
), but these fields can be used to replace
unused spell IDs with custom ones.
For example, there’s SPELL_PHANTOM
which doesn’t appear in the game and has no effect in the engine.
This gives us a free spell slot to use. Let’s replace it with a custom magic missiles (SPELL_MAGIC_MISSILE
) variant.
For this we need to set name: SPELL_PHANTOM
(or find existing section with it) to specify which slot we are using,
and set effect: SPELL_MAGIC_MISSILE
for the engine to know which logic to use for it. The result should look like this:
name: SPELL_PHANTOM # replacing Phantom spell slot
effect: SPELL_MAGIC_MISSILE # but effect is based on Magic Missiles
phonemes: [un, ro, do] # phonemes must be unique, so we keep ones from Phantom
# the rest is copied from Missiles and modified
icon:
ind: 18248
icon_enabled:
ind: 131967
mana_cost: 50
price: 5000
flags:
- AT_LOCATION
- MOBS_CAN_CAST
- OFFENSIVE
- CAN_COUNTER
- CANT_TARGET_SELF
- CLASS_WIZARD
- 536870912
- 1073741824
title: thing.db:MissilesOfMagic
desc: thing.db:SPELL_MAGIC_MISSILE_DESC
cast_sound: MagicMissileCast
missiles:
count: 5
spread: 30
projectile: MagicMissile
vel_mult: 0.1
offset: 4
speed_rnd_min: 0.1
speed_rnd_max: 0.3
search_dist: 800
---
The main build supports a way to replace sprites used by the game.
For it to work, you may first need to get original sprites:
cd Nox
noxtools videobag extract -z --out ./video.bag.zip
Find sprites that you want to replace and put them into Nox/images
(create if not exists).
Make the changes to the sprite in this directory and run the game to test it.
Note that it will ONLY work with this Nox version. Original Nox, GoG version or Nox Reloaded doesn’t support this.
If you decide to change the sprite significantly, e.g. changing its size or completely redrawing the image, you may need to change the sprite offset used by the engine.
First, get the original sprite metadata:
cd Nox
noxtools videobag extract -z --out ./video.bag.zip --json
Now this archive will contain .json
files that correspond to each sprite. Copy selected ones to Nox/images
,
adjust the offsets using the text editor and check them in game.
Most of the text used in Nox is stored in the CSF files which are encoded and are hard to modify.
This build provides an easier way to customize those texts.
First, decode the original file:
noxtools strings csf2json nox.csf
This will produce nox.csf.json
file that you can modify with a regular text editor.
The build will automatically use this file instead of the original nox.csf
.
The nox.csf.json
file will consist of sections similar to this:
{
"id": "ParseCmd.c:exithelp",
"vals": [
{
"str": "Exit the game to Main Menu."
}
]
}
For translation Nox texts to a different language (or changing existing texts),
you need to keep the id
field, but translate all str
fields.
For adding custom strings, you need to add a new section with a unique id
add at least one str
.
Then you should be able to use this new id
in your map or mod.
OpenNox provides multiple scripting runtimes. Some of them are experimental or in development.
OpenNox implements a new more powerful version of NoxScript runtime.
It can be used to make new generation of maps, as well as some full-featured mods in the future.
See NoxScript quickstart if you want to try it.
OpenNox implements an experimental LUA map script runtime.
LUA scripts are deprecated. Consider using new NoxScript runtime.
To create a LUA map script, put <mapname>.lua
file in the map’s directory.
For example:
maps/
estate/
estate.map
estate.nxz
estate.lua
You also need to request a specific version of the scripting API that you want to use:
Nox = require("Nox.Map.Script.v0")
Now you are ready to write some magic Nox scripts!
It is also possible to add more LUA files, for example:
maps/
estate/
estate.map
estate.lua
other_file.lua
And use require
to load it:
other_file = require("other_file")
You can access script variables for debugging using Nox console.
First, you should enable cheats (racoiaws
), and the prefix all you commands with lua
.
For example:
lua p = Nox.Players.host
lua print(p)
Timers allow to trigger some LUA function at a later time.
This timer will call a given function after N game frames (server ticks).
function MyFunc()
print("trigger!")
end
Nox.FrameTimer(60, MyFunc)
This timer will call a given function after N in-game seconds pass.
function MyFunc()
print("trigger!")
end
Nox.SecondTimer(10, MyFunc)
Walls in Nox are positioned on a regular grid. Thus, walls can be addressed by those grid positions. If walls are marked as scriptable in the editor, it will be possible to enable (close) and disable (open) them.
Wall(xi,yi)
- get a wall by its grid coordinates.WallAt(x,y)
- get a wall at exact real coordinates (not grid ones).WallNear(x,y)
- get a wall near specific real coordinates (not grid ones).WallNear(obj)
- get a wall near a specific object or waypoint.WallGroup(id)
- finds a wall group by the ID.This object represents a single wall on the map.
w.xi
- returns X grid coordinate of the wall.w.yi
- returns Y grid coordinate of the wall.w.x
- returns real X coordinate of the wall.w.y
- returns real Y coordinate of the wall.w.enabled
- checks if the wall is enabled (closed) or sets the enabled state.w:Pos()
- returns wall’s real position as a pair of X,Y coordinates.w:Toggle()
- toggles the wall’s state (opened/closed).w:Break()
- break this wall (must be set as breakable).This object represents a group of one or more walls on the map.
w.id
- returns ID of this wall group.w:Toggle()
- toggles walls state (opened/closed).w:Break()
- break these walls (must be set as breakable).Open secret wall near the player (must be really close):
local p = Nox.Players.host
Nox.WallNear(p).enabled = false
Break a wall group with ID MyGroup
on the map:
local g = Nox.WallGroup("MyGroup")
g:Break()
This section describes player-related objects and functions.
Players list can be accessed via Nox.Players
meta-class.
Players()
- returns current list of players as LUA array.Players[i]
- returns a player by index i
.Players:Print(text)
- prints a text message to all players.Players:Blind()
- blinds all players (fades the screen to black).Players:Blind(false)
- unblinds all players (fade back to normal).Player object includes information about human-controlled player, as well as a unit he controls.
p.name
- returns player’s name.p.host
- checks if player is a host.p.unit
- returns player’s unit, if any.p.x
- gets or sets player’s unit X coordinate.p.y
- gets or sets player’s unit Y coordinate.p:Pos()
- returns player’s unit position as a pair of X,Y coordinates.p:SetPos(x,y)
- instantly moves player’s unit to given coordinates.p:SetPos(obj)
- instantly moves player’s unit to a given object or waypoint.p:Print(text)
- prints a text message to the player.p:Blind()
- blinds player (fades the screen to black).p:Blind(false)
- unblinds player (fade back to normal).Iterating over all players:
local players = Nox.Players()
for i,p in ipairs(players) do
print(p)
end
Getting the first player:
local p = Nox.Players[1]
print(p)
Getting the host player:
local p = Nox.Players.host
print(p)
Getting player’s name:
local p = Nox.Players.host
print(p.name)
Checking if player is a host:
local p = Nox.Players[1]
if p.host then
print("it's the host!")
end
Blind everyone:
Nox.Players:Blind()
Blind everyone except the host:
local players = Nox.Players()
for i,p in ipairs(players) do
if not p.host then
p:Blind()
end
end
This section describes different object present in game.
Object type describes a “prototype” of an object that can be spawned in-game.
Nox.ObjectType(id)
- find an object type by ID.t.id
- returns object type ID.t:Create(x,y)
- creates a new object instance at given coordinates.t:Create(obj)
- creates a new object instance at the position of another object or waypoint.Nox.Object(id)
- find an object by ID.v.id
- returns object’s ID, if any.v.owner
- returns or sets object’s owner.v.x
- gets or sets object’s X coordinate.v.y
- gets or sets object’s Y coordinate.v.z
- gets or sets object’s Z coordinate.v.enabled
- checks if object is enabled or sets the enabled state.v:Pos()
- returns object’s position as a pair of X,Y coordinates.v:SetPos(x,y)
- instantly moves object to given coordinates.v:SetPos(obj)
- instantly moves object to another object or waypoint.v:SetOwner(obj)
- sets object owner; same as v.owner
, but allow chaining.v:Delete()
- permanently delete object from the map.v:Toggle()
- toggles object’s enabled state.Unit extends the generic object, so everything that can be done with object can be done with a unit.
v.health
- current health of the unit.v.max_health
- max health of the unit.v.mana
- current mana of the unit.v.max_mana
- max mana of the unit.v:Freeze()
- freezes the unit in place.v:Wander()
- make the unit wander around.v:Return()
- make the unit return to its starting position.v:Idle()
- make the unit idle.v:Guard()
- make the unit guard position.v:Hunt()
- make the unit hunt for enemies.v:LookAt(x,y)
- make the unit look at certain position.v:LookAt(obj)
- make the unit look at another object or waypoint.v:LookAtDir(dir)
- make the unit look in a given direction.v:LookAngle(dir)
- make the unit look at a given angle.v:MoveTo(x,y)
- make the unit move to certain position.v:MoveTo(obj)
- make the unit move to another object or waypoint.v:WalkTo(x,y)
- make the unit walk to certain position.v:WalkTo(obj)
- make the unit walk to another object or waypoint.v:Follow(obj)
- make the unit follow another object.v:Attack(obj)
- make the unit attack another object.v:HitMelee(x,y)
- make the unit hit melee a certain position.v:HitMelee(obj)
- make the unit hit melee another object or waypoint.v:HitRanged(x,y)
- make the unit hit ranged a certain position.v:HitRanged(obj)
- make the unit hit ranged another object or waypoint.Teleport player 10 pixels right:
p = Nox.Players.host
x, y = p:Pos()
x = x + 10
p:SetPos(x,y)
Teleport player 1 to player 2:
p1 = Nox.Players[1]
p2 = Nox.Players[2]
p1:SetPos(p2)
Spawn 10 apples near the player:
apple = Nox.ObjectType("RedApple")
p = Nox.Players.host
for i = 1,10 do
apple:Create(p)
end
Spawn Mimic near the player and make him friendly:
mimic = Nox.ObjectType("Mimic")
p = Nox.Players.host
mimic:Create(p):SetOwner(p)
Spawn 2 Beholders and make them follow the player:
beholder = Nox.ObjectType("Beholder")
p = Nox.Players.host
arr = {}
for i = 1,2 do
arr[i] = beholder:Create(p)
end
squad = Nox.ObjectGroup(unpack(arr))
squad:SetOwner(p)
squad:Follow(p)
Make a train of 5 Bombers that follow each other and the player:
function trainFollow()
p:Print("Bomber train!")
prev = p
for i, b in ipairs(bombers) do
b:Follow(prev)
prev = b
end
end
function makeTrain()
bomber = Nox.ObjectType("Bomber")
p = Nox.Players.host
bombers = {}
for i = 1,5 do
bombers[i] = bomber:Create(p)
end
train = Nox.ObjectGroup(unpack(bombers))
train:SetOwner(p)
-- give them a frame or two to appear
Nox.FrameTimer(2, trainFollow)
end
makeTrain()