Home.md

Welcome to the iw6x-client wiki!

Scripting.md

Introduction

This is the documentation for the IW6x scripting API.
Please note that the API might change over time and thus break existing scripts.
The intention is to create the best API possible and thus negelecting backwards compatibility.
There are also plans to create a common API with the Plutonium team, which might also have a huge impact on the API again, once this is done.

The IW6x scripting API integrates into the game's scripting VM/environment, which might be more commonly known as GSC. The scripts are written in LUA. In case you are not familiar with LUA, you should catch up here.

Quick & dirty Example

Here is a sample script. If you want to read code instead of documentation, this should cover up the basics of the API:

iw6x/scripts/heli/__init__.lua

function move_helicopter(heli, player)
    local origin = player.origin
    origin.z = origin.z + 1000

    heli:setvehgoalpos(origin, 1)
end

function setup_helicopter(heli, player)
    heli:setturningability(1)
    heli:setspeed(40, 15, 5)
    heli:setcandamage(false)

    local moveinterval = game:oninterval(function() move_helicopter(heli, player) end, 5000)

    function disconnect_callback()
        moveinterval:clear()
        heli:delete()
    end

    player:onnotifyonce("disconnect", disconnect_callback)
end

function spawn_helicopter(player)
    local origin = player.origin;
    local angles = player.angles;

    origin.z = origin.z + 1000.0;

    local heli = game:spawnhelicopter(player, origin, angles, "cobra_mp", "vehicle_battle_hind")

    setup_helicopter(heli, player)
end

function player_spawned(player)
    print("Player spawned: " .. player.name)
    player:freezecontrols(false)

    spawn_helicopter(player)
end

function player_connected(player)
    print("Player connected: " .. player.name)

    player:onnotifyonce("spawned_player", function() player_spawned(player) end)
end

level:onnotify("connected", player_connected)

File structure

All scripts need to be placed in the iw6x/scripts folder in your Call of Duty: Ghosts installation folder.
You will need to create a folder for your script, for example my_script. The main entrypoint is always the __init__.lua file in there.

The structure should be like this:

COD Ghosts
├── iw6x
│   ├── scripts
│   │   ├── my_script
│   │   │   ├── __init__.lua
│   │   │   └── other_script.lua
│   │   ├── my_next_script
│   │   │   ├── __init__.lua
│   │   │   └── script.lua
└── iw6x.exe

To load another script, you can use the include function.
If you want tofor example include other_script.lua, use it like this:

include("other_script")
-- [...]

Types

There are only 5 distinct types needed to interact with game's scripting environment.
LUA of course has more types, but what is meant by this is the following:
When dealing with events, functions or fields, no complex LUA types are supported, only these primitive types:

Strings

Strings are text enclosed by quotes: "Hello World". There is not much to say about them, as they are a common concept.

Integer

Integers are all whole numbers like 0, 1000 or -15.
Boolean values, so true and false, also fall under that category and are handled as 1 and 0 respectively.

Float

Floating point values are all numbers like 1.05 or -10.5.
1.0 is also a floating point number literal. This is due to the dot notation, even though it's technically a whole number.

Vector

A vector is a type that groups exactly 3 floating point components. It is used to represent coordinates, angles or colors in the game.

Creating a new vector can be done like this:

local my_vector = vector:new(1.0, 2.0, 3.0)

-- Statements below will be true
assert(my_vector.x == 1.0)
assert(my_vector.y == 2.0)
assert(my_vector.z == 3.0)

You can access the individual components using either x, y and z property accessors, or r, g and b (note that both notations are equivalent, meaning xr, yg and zb).

local my_vector = vector:new() -- vector is initialized as (0.0, 0.0, 0.0)
my_vector.x = 5.0
my_vector.y = 1.0
my_vector.z = 2.0

local val1 = my_vector.x -- val1 is 5.0
local val2 = my_vector.r -- val2 is also 5.0, as x and r are equivalent

-- The statements below are always true
assert(my_vector.y == my_vector.g)
assert(my_vector.z == my_vector.b)

Entity

Entities represent 'things' that 'exist' in the game. Players are for example entities.
Vehicles, hud elements, or the level are also entites. They can fire events, but also call functions into the game's scripting environment. Some entites also have certain fields (or properties), like an origin or a name.

Events

Entites can fire events. The concept was previously known as notify and waittill in GSC.
Popular events are the connected event fired by the level, or the spawned_player event fired by players.

Listening

You can listen to any of these events by calling either onnotify or onnotifyonce on an entity:

function player_connected(player)
  -- [...]
end

level:onnotify("connected", player_connected)

In the example above, the player_connected callback is called every time a player connects. If you don't want to be notified every time, you can use onnotifyonce instead, which only fires once.

If you called onnotify (or onnotifyonce), but want to stop listening for notifications, you can call clear on the object is returned:

-- [...]

local listener = level:onnotify("connected", player_connected)

-- [...]

listener:clear() -- stops listening for the 'connected' event

Events can carry arguments. For example the connected event by the level carries the connecting player as an argument. You can get these arguments by adding paramters to the callback functions, as seen in the player_connected callback above.

Firing

To fire events, you can call the notify function on an entity:

function player_connected(player)
  function my_callback()
    print("Yay")
  end

  player:onnotify("my_cool_event", my_callback)

  -- [...]

  -- This fires the 'my_cool_event' event
  player:notify("my_cool_event")
end

You can also pass arguments to the event (there can be any amount, but they must be instances of one of the 5 primitive types):

function player_connected(player)
  function my_callback(arg1, arg2)
    print(arg1 .. " " .. arg2)
  end

  player:onnotify("my_cool_event", my_callback)

  -- [...]

  -- This fires the 'my_cool_event' event
  player:notify("my_cool_event", "Hello", "World")
end

Timers

Timers are required to delay the execution. GSC had a function called wait, which paused the execution of a thread for a specific amount of time.
Due to the control flow being different in LUA, there are neither threads, nor is there a wait function.

However, you can do something else to delay the execution. The functions ontimeout and oninterval, which need to be called on the game object, allow to do that. They take two arguments, a callback and a time to wait in milliseconds.

function callback()
  -- [...]
end

game:ontimeout(callback, 1000)

Similar to the functions setTimeout and setInterval known from JavaScript, ontimeout executes the callback exactly once and oninterval executes it every time the given amount of time passes.

You might want to cancel a timeout or an interval. This can be done exactly like it works for events.
Both ontimeout and oninterval return a timer with a clear method:

function callback()
  -- [...]
end

local timer = game:oninterval(callback, 1000)

-- [...]

timer:clear()

Timer callbacks don't take any arguments. If you want to pass an object, like a player, to a callback, you have to capture it, in a lambda for example:

function do_something(player)
  -- [...]
end

-- [...]

local player = -- [...]
game:oninterval(function() do_something(player) end, 1000)

Functions

The game provides a set of global functions and entity methods.
The functions provided are pretty much the same as COD4 had them.
You can have a look at a COD4 scripting reference: https://znation.nl/cod4script/ (libcod category doesn't exist in IW6x!)
Also note that all functions and methods are lowercase in IW6x!

Global functions are functions that don't need an entity to work. They need to be called on the game object.
In the COD4 scripting reference, these functions don't have a Call this on: paragraph.
Here is an example:

game:ambientplay("embient_mp_highrise");

There are also member functions. These do have a paragraph called Call this on: in the COD4 reference. Here is an example:

player:freezecontrols(false)

For a list of all functions and methods available in IW6x, you can have a look at the source code:
https://github.com/XLabsProject/iw6x-client/blob/develop/src/client/game/scripting/function_tables.cpp

Fields

Entities do have fields (or properties).
They differ for each type of entity. For example players have fields like name, origin or angles and hud elements have fields like x, y or alpha.
You can get and set them like this (only the 5 primitive types are supported):

local playername = player.name
player.origin = vector:new()

Creating new properties or fields does not work yet, so you can't store own data in an entity.
You have to create your own construct for that.

Additionally, there is no complete list of which fields exist. level.players is not a field for example. This was a variable.
So not every variable that existed in GSC is available as a field in here.

Command execution

Unlike in GSC, the scripting API allows to execute console commands (for versions > v1.1.0).
You can use the executecommand function on the game object like this:

game:executecommand("map mp_prisonbreak")

Noteworthy things

Players can disconnect. It might sound obvious, but is important for scripting.
Executing functions on a player that doesn't exist won't work. Additionally, captured player objects need to be freed.

GSC used to have a function called endon, which allowed to terminate a thread as soon as an event happend, for example player endon("disconnect") stopped the execution, when a player disconnected.

There is no such concept in IW6x. You will need to work with a combination of clearing timers and onnotifyonce, like this:

function do_something()
  -- [...]
end

local timer = game:oninterval(do_something, 1000)

  -- [...]

player:onnotifyonce("disconnect", function() timer:clear() end)