Lua LScript for Unreal

Introduction

LScript for Unreal is a simple and full integration of Lua with the Epic Unreal game engine. It has been integrated without problems into many versions of "Americas Army: Operations" and "Unreal Tournament 2004".

To take full advantage of the Unreal engine via LScript an Engine package has been added to the basic Lua interpreter. The Engine package adds these functions, which can be accessed from either a Lua script or the Lua console: CloseConsole, Dump, DumpClass, FindFirst, FindFirstAActor, FindNext, FindNextAActor, FullName, IsA, Load, Log, New, OpenConsole, Restart

The source code is distributed without licence though a credit would be nice: Picklelicious, Temp2 and SmokeZ. Enjoy. LScript for Unreal connects a Lua scripting engine up to the Unreal game engine. It allows you to do things like:

PC = Engine.UObjects['StudentController Entry.StudentController']
PC.Region.Zone.bDistanceFog = false
Rot = Engine.New(Engine.UObjects['Struct Object.Rotator'])
Rot.Pitch = 1024
Rot.Yaw = 0x8000
PC.SetRotation(Rot)
PC.ConsoleCommand('exit')
Canvas.DrawText('Hello!')
Canvas.DrawText(PRI.PlayerName)
Lua is a scripting language designed for extending applications. You can learn more about the Lua scripting language here: www.lua.org

Compiling

You can compile this project with gcc (Makefile.win) or use Dev-C++ (LScript.dev) or with VC6 / VC7 (LScript.dsp, LScript.vcproj, LScript.dsw, LScript.sln)
Source Code
LScript for Unreal's source code can be found in SourceForge here: http://sourceforge.net/projects/lscript4unreal/
How to build LScript
1. Download the latest Lua source code from the Lua site.
2. Copy all the files from lua-x.x/src directory into your build directory.
3. Delete the lua.c and luac.c files.
4. Copy LScript.cpp and the appropriate workspace file(s) into the build directory.
5. Create a project that includes all the files in the build directory.
6. Build it.

Hitory

Picklelicious thought it would be fun to try and hook Lua up to the AAO 2.6 engine, and then see if he could write some useful Lua scripts (LScripts) instead of Unreal scripts (UScripts). Temp2 and SmokeZ ported, created hooks for newer versions and kept it going.

One of the nice thing about Lua scripts is that they are simple to create. You only need a text editor (no SDKs or compilers).

Lua Console

LScript can display an interactive Lua interpreter in a console window. Pressing <Alt>+c will open and close the Lua console. It is easier to use the console while playing the game in windowed mode.

Clicking the X (close button) on the Lua console will close the entire game. Sometimes the Lua console will not be responsive to keystrokes. If this happens closing and reopening the console with <Alt>-C will fix the problem.

The Lua console is very similar to the game's console. You can type in any Lua commands here. It is a good way to try things out before adding them to your scripts. If your script is in a file called "test.lua", you can load it from the Lua console like this:

Engine.Load('test.lua')
You can totally restart (which loads default.lua too) with <Alt>-R which is equivalent to the Engine.Restart() command. <Alt>-L will display the LScript log file default.log

A useful thing to do is to size the console to whatever size you want, right click, select properties, layout tab, and then set the Screen Buffer Size to whatever you like. Picklelicious likes a lot history, so set his to Width:124 and Height:2048.

Default.lua

Default.lua is the default script. The Lua interpreter will load this script during its initialization.

Engine Package

LScript has added an Engine package to the basic Lua interpreter. The Engine package adds these functions, which can be accessed from either a Lua script or the Lua console:
CloseConsole
Dump
DumpClass
FindFirst
FindFirstAActor
FindNext
FindNextAActor
FullName
IsA
Load
Log
New
OpenConsole
Restart
To call the functions you need to use the package name. A call to CloseConsole would look like this: Engine.CloseConsole() The Engine package also adds these objects
AActors
FNames
UObjects
To access these objects you use []. For example, you can get the 10th FName like this:
FName[10]
The Lua length operator works on these objects. For example:
print(#Engine.FNames)
40016
The Lua length operator also works on objects. It will return the object's index into UObjects. For example:
X = Engine.UObjects[123]
print(#X)
123
This allows you to check if two objects are the same. For example:
X = Engine.UObjects[123]
Y = Engine.UObjects[123]
print(X == Y)
false
You would think these objects would be the same, but they are not. They are two different Lua objects that point to two different C++ data structures. There is a pointer in the C++ data structure that points to the real object. These pointers are the same, but Lua knows nothing about them. If you print the objects from Lua you get:
print(X, Y)
userdata: 1CAE4390     userdata: 1CAF19A8

They are not the same.  If you print the objects lengths you get:
print(#X, #Y)
123     123

print(#X == #Y)
true

Engine Functions

Engine.CloseConsole()
This function closes the Lua console.
Engine.Dump
This command will dump whatever object you pass to it. It is very useful for figuring out what object you have, what properties it has, and what values the properties currently have. For example, assuming that PC contains an a player controller object:
Engine.Dump(PC.Region)
Struct Actor.PointRegion part of [StudentController Entry.StudentController]
  FPointRegion
    AZoneInfo *  Zone       [ZoneInfo8]
    int          iLeaf      [237]
    byte         ZoneNumber [1]

Engine.Dump(PC.Pawn.Location)
Struct Object.Vector part of [AGP_Character Entry.AGP_Character]
  FVector
    float X [-255.703]
    float Y [-8636.898]
    float Z [14.950]
The dumps are sent to both the Lua console and the log file.
Engine.DumpClass
This command will dump the class information of whatever object you pass to it. It is very useful for figuring out an object's inheritance. For example, assuming that PC contains an a player controller object:
Engine.DumpClass(PC)
Class AGP.Student Controller
  Class AGP.HumanController
  Class Engine.PlayerController
  Class Engine.Controller
  Class Engine.Actor
  Class Core.Object
The dumps are sent to both the Lua console and the log file.
Engine.FindFirst(FName, UClass) / FindNext(UObject)
These commands are used to speed up searching the massive UObjects list. Internally, the game hashes all the objects by their FNames. It then links similar objects based on their hashes. The effect is that it is normally 4000 times faster to search the hash table instead of the full list. The only catch is that the objects you are interested in must have the same FNames.

FindFirst(FName, UClass) will return the first object in the hash list that matches FName and UClass, or nil if there are no objects.

FindFirst(UObject) will return the first object in the hash list that matches Object (FindFirst uses the FName and UClass of UObject).

FindNext(Object) will return the next object in the hash list, or nil if there are no more objects.

Here is an example on how to find all the PRIs:

FName_PRI = Engine.FNames['PlayerReplicationInfo']
UClass_PRI = Engine.UObjects['Class Engine.PlayerReplicationInfo']
PRI = Engine.FindFirst(FName_PRI, UClass_PRI);
while PRI do
  print(PRI.PlayerName)
  PRI = Engine.FindNext(PRI)
end
Here is another way:
PRI = Engine.FindFirst(PC.PlayerReplicationInfo)
while PRI do
  print(PRI.PlayerName)
  PRI = Engine.FindNext(PRI)
end
Engine.FindFirstAActor(UClass) / FindNextAActor(UObject, UClass)
These commands are used to speed up searching the large AActors list. It is much faster to search that list in C++ than in Lua.

FindFirst(UClass) will return the first object in AActors that IsA UClass or nil if there are no objects.

FindNext(Object, UClass) will return the next object in AActors that IsA UClass or nil if there are no more objects.

Here is an example on how to find all the pickups:

UClass_Pickup = Engine.UObjects['Class Engine.Pickup']
Pickup = Engine.FindFirstAActor(UClass_Pickup);
while Pickup do
  print(Engine.FNames[Pickup.Name])
  Pickup = Engine.FindNext(Pickup, UClass_Pickup)
end
Engine.FullName(UObject)
This command returns the full name of UObject. Here is an example that prints out all the AActors.
for i = 0, #Engine.AActors-1 do
  if Engine.AActors[i] then
    print(tostring(i)..' '..Engine.FullName(Engine.AActors[i]))
  else
    print(tostring(i)..' Empty')
  end
end
Engine.IsA(UObject, UClass)
This command returns whether UObject is a UClass. Here is an exmple:
UClass_PRI = Engine.UObjects['Class Engine.PlayerReplicationInfo']
print(Engine.IsA(PC, UClass_PRI))
false

print(Engine.IsA(PC.PlayerReplicationInfo, UClass_PRI))
true
Engine.Load()
This command will load and add it to the currently running script. <file> is just the file name without a path. The path is the same as the LScript.exe directory.
Engine.Log(...)
This command will write (...) to the log file. The log file is LScript.log and is in the same directory as LScript.exe.
Engine.New(Object)
This command creates a new structure object. For example:
FVector = Engine.UObjects['Struct Object.Vector']
Vec = Engine.New(FVector)
This creates a vector structure that you can use as a parameter to UScript function calls. For example:
PC.Pawn.SetLocation(Vec)
You can also use existing vector data. For example:
PC.Pawn.SetLocation(PC.Location)
Please take notice the following:
Vec = PC.Pawn.Location
This points Vec at PC.Pawn.Location. Dumping Vec will confirm this:
Engine.Dump(Vec)
Struct Object.Vector part of [AGP_Character Entry.AGP_Character]
...

Vec = Engine.New[FVector]
This point Vec at a new FVector. Dumping Vec will confirm this:
Engine.Dump(Vec)
Struct Object.Vector
...
Engine.OpenConsole()
This command opens the Lua console.
Engine.Restart()
This command restarts the Lua interpreter and loads the default.lua script file.

Engine Objects

Engine AActors
This is the current list of AActor objects being used by the game engine. This list is much smaller than the Engine.UObjects list. You can index AActors with either the object number or a string containing the full name of the object.

This example prints the names of all AActors in the list:

for i = 0, #Engine.AActors-1 do
  if Engine.AActors[i] then
    print(tostring(i)..' '..Engine.FNames[Engine.AActors[i].Name])
  else
    print(tostring(i)..' Empty')
  end
end
Notice that not every AActors entry contains an AActor. Some of the entries are empty. This example gets an AActor by name
PC = Engine.AActors['StudentController Bridge.StudentController']
Indexing the AActors object with a number is very quick. Indexing it with a string is slow because it has to search all the entries looking for the one you requested.

Engine FNames
This is the current list of FName objects being used by the game engine. The list is usually huge. You can index the FNames list with either the FName number or a string containing the FName. This example print the FNames
for i = 0, #Engine.FNames-1 do
    print(tostring(i)..' '..Engine.FNames[i])
end
As you can see, indexing the FNames with a number returns the FName string. Indexing the FNames with a string will return the index.
print(Engine.FNames[0])
none

print(Engine.FNames['none'])
0
Indexing the FNames object with a number is very quick. Indexing it with a string is slow because it has to search all the entries looking for the one you requested.
Engine UObjects
This is the current list of UObject objects being used by the game engine. This list is huge. You can index UObjects with either the object number or a string containing the full name of the object. This example prints the names of all UObjects in the list:
for i = 0, #Engine.UObjects-1 do
  if Engine.UObjects[i] then
    print(tostring(i)..' '..Engine.FNames[Engine.UObjects[i].Name])
  else
    print(tostring(i)..' Empty')
  end
end
Notice that not every UObjects entry contains an UObject. Some of the entries are empty.

This example gets an UObject by name

FVector = Engine.UObjects['Struct Object.Vector']
Indexing the UObjects object with a number is very quick. Indexing it with a string is slow because it has to search all the entries looking for the one you requested.

Callbacks

There are four callbacks. They are similar to their UT counter parts.
KeyEvent
PreRender
PostRender
Tick
KeyEvent(Key, Action, Value)
This callback is called whenever a key event needs to be processed. Returning zero means the key event should be passed on to the game. Returning true means that you handled the key event and it should not be passed onto the game. This example will toggle fog on/off when the 'F' key is pressed:
function KeyEvent(Key, Action, Value)
  --Check for the F key
  if Key == 0x46 then
    --Keydown action
    if Action == 1 then
      PC.Region.Zone.bDistanceFog = not PC.Region.Zone.bDistanceFog
    end
    return true
  end

  return false
end
PreRender(Canvas)
This callback is called before rendering starts. It is a good time to change your point of view, turn off fog, or anything that you want to take effect in this frame.
PostRender(Canvas)
This callback is called when the rendering is finished. It allows you to draw on the screen. This example will draw your location on the screen (if you have a pawn):
function PostRender(Canvas)
  Canvas.Font = Canvas.SmallFont
  Canvas.SetDrawColor(255, 0, 0, 255)
  Canvas.SetPos(10, 60)

  if (PC.Pawn) then
    Canvas.DrawText(string.format(
      'Location: (%d %d %d) (%d)',
      PC.Pawn.Location.X,
      PC.Pawn.Location.Y,
      PC.Pawn.Location.Z,
      PC.DesiredFOV))
  else
    Canvas.DrawText('No pawn')    
  end    
end
Tick(Delta, Viewport)
This callback is called at the beginning of a tick. It is a good time to save the PlayerController (PC). It is probably a good place to check for level changes too.
function Tick(DeltaTime, ViewPort)
  PC = ViewPort.Actor

  if LevelName ~= PC.Level.sLevelName then
    LevelName = PC.Level.sLevelName
    --Do your level init here 
  end
end

Calling UScript Functions

For the most part, calling UScript functions works the way it normally does. For example:
print(PC.CanFire())
true

X = PC.CanFire()
print(X)
true

FRotator = Engine.UObjects['Struct Object.Rotator']
Rot = Engine.New(FRotator)
Rot.Pitch = 1024
Rot.Yaw = 0x8000
PC.SetRotation(Rot)
The major difference is with UScript out parameters. For example, the three parameters to this function are all out parameters:
PC.PlayerCalcView(Target, Vec, Rot)
However, if you call this function in Lua you will notice that none of the parameters get modified. That is because Lua does not support out parameters (pass by reference). Instead Lua supports multiple returns. Any out parameters are added to the returns. To call this function in Lua you would do this:
Target, Vec, Rot = PC.PlayerCalcView(Target, Vec, Rot)
This function does not have a normal return value, but if it did it would be written like this:
ReturnValue, Target, Vec, Rot = PC.PlayerCalcView(Target, Vec, Rot)
You don't have to use all the return values. Any one that you don't use is thrown away. This is fine:
Target = PC.PlayerCalcView(Target, Vec, Rot)
This is not:
Vec = PC.PlayerCalcView(Target, Vec, Rot)
The reason is because the return values are in order. If you need Vec, then you need to do this (Target has to come first):
Target, Vec = PC.PlayerCalcView(Target, Vec, Rot)
An easy way to see how many values a function returns is to do this:
print(PC.PlayerCalcView(Target, Vec, Rot))
userdata: 1CAA3948   userdata: 0196D68   userdata: 1CAA2418
That shows that the function returns 3 userdata types. Userdata is a type that Lua does not understand and is just a placeholder for C++ data. LScript uses Lua userdata types for UObjects, properties of UObjects, and structures. LScript uses the Lua boolean, number, and string type for UScript boolean, byte, FName, int, float, and FString.

You can figure out what the userdata is by calling the Engine.Dump function:

print(PC)
userdata: 1CAAC838

Engine.Dump(PC)
StudentController Entry.StudentController
  AStudentController
    bool  bUsedCheats           [0]
    bool  bSeated               [0]
    bool  bForceSit             [0]
    AActor * _AutoTraceHitActor [nill]
...

Target = PC.PlayerCalcView(Target, Vec, Rot)
print(Target)
userdata: 1CAA3948

Engine.Dump(Target)
AGP_Character Entry.AGP_Character
  AAGP_Character
    bool        bWasTurningLeft    [0]
    ATeamInfo * DeadBodyFormerTeam [nill]
    FName       AnimSequence       [None]
...

Patching UScript Functions

You can access a functions UScript using the [] operators. For example:
print(PC.PlayerCalcView[0])
8

PC.PlayerCalcView[0] = 0x2a
You can get the length of the script using the Lua length operator:
print(#PC.PlayerCalcView)
1217
This will dump the script:
S = PC.PlayerCalcView
for i=0, #S-1 do print(X[i]) end
8
98
0
130
130
180
1
0
201
13
6
...