Welcome to the iw6x-client wiki!
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.
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)
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")
-- [...]
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 are text enclosed by quotes: "Hello World"
.
There is not much to say about them, as they are a common concept.
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.
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.
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 x
≘ r
, y
≘ g
and z
≘ b
).
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)
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
.
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.
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.
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 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)
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
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.
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")
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)