Subsections of Modding

Tweaking the game balance

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).

Changing spells

Vanilla Nox stores spell configs in three different places:

  • As a section in thing.bin file
  • Balance file
  • Built into the engine

The 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.

Generating base spells.yml file

Since 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.

Modifying 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 effect
  • effect - specifies spell ID which will control the actual effect of the spell; see adding spells
  • icon.ind - the sprite index from video.bag that is used as a spell icon
  • icon_enabled.ind - same as icon.ind, but for enabled spell icon
  • mana_cost - mana cost of the spell
  • price - base spell book price; Quest uses an additional multiplier for this price
  • flags - different flags that controls which category spell belongs to, what it can target, etc
  • phonemes - a unique list of spell phonemes/gestures that invoke this spell; see spell casting
  • title - a string ID for the spell title
  • desc - a string ID for the spell description
  • cast_sound - a sound for casing a spell
  • on_sound - a sound for enabling a spell
  • off_sound - a sound for disabling a spell

These 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

Spell flags

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 Wizard
  • CLASS_CONJURER - spell can only be used by Conjurer
  • Setting none of these flags will effectively hide the spell.

Targeting flags

  • TARGETED - spell is homing
  • AT_LOCATION - spell can be cast at a point
  • CANT_TARGET_SELF - spell cannot be targeted at the character

Cast flags:

  • NO_MANA - spell doesn’t require mana
  • NO_TRAP - spell can’t be used in traps
  • INSTANT - spell is instant
  • DURATION - spell is duration-based
  • OFFENSIVE - spell is offensive
  • DEFENSIVE - spell is defensive
  • CAN_COUNTER - spell can be countered
  • MOBS_CAN_CAST - mobs are allowed to use this spell
  • CANT_HOLD_CROWN - spell can’t be cast when holding a flag/crown/ball

Special flags

  • SUMMON_SPELL - this is the base Summon Creature spell
  • SUMMON_CREATURE - this is Summon for a specific creature
  • MARK_SPELL - this is the base Mark Location spell
  • MARK_NUMBER - this is Mark Location with a specific number
  • GOTO_MARK_SPELL - this is the base Go To Mark spell
  • GOTO_MARK_NUMBER - this is Go To Mark with a specific number

There are some flags that don’t have names, which means we are not sure of its effect.

Adding new spells

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
---

Replacing sprites

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.

Adjusting sprite offsets

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.

Customizing translation

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.

Scripts

OpenNox provides multiple scripting runtimes. Some of them are experimental or in development.

Subsections of Scripts

NoxScript

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.

LUA scripts

OpenNox implements an experimental LUA map script runtime.

Note

LUA scripts are deprecated. Consider using new NoxScript runtime.

Quickstart

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")

Debugging with the in-game console

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

Timers allow to trigger some LUA function at a later time.

Frame timer

This timer will call a given function after N game frames (server ticks).

function MyFunc()
    print("trigger!")
end
Nox.FrameTimer(60, MyFunc)

Seconds timer

This timer will call a given function after N in-game seconds pass.

function MyFunc()
    print("trigger!")
end
Nox.SecondTimer(10, MyFunc)

Walls

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.

Wall object

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).

WallGroup

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).

Examples

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()

Players

This section describes player-related objects and functions.

Players meta-class

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

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).

Examples

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

Objects

This section describes different object present in game.

ObjectType

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.

Object instance

  • 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 object

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.

Examples

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()