[DLL/C++] Extending the Lua API

whoward69

DLL Minion
Joined
May 30, 2011
Messages
8,699
Location
Near Portsmouth, UK
The aim of this tutorial is to show you how to extend the Lua interface (or more accurately the application programming interface or API) by adding new methods backed by C++ code.

This tutorial assumes you are familiar with the techniques presented in the "Replacing hard-coded constants with database values" tutorial, specifically adding your own custom header file and the pre-processor techniques. If you haven't already done so, please read that tutorial first.

This tutorial also assumes that you have
  1. Knowledge of Lua and the Civ 5 objects, eg Player, City, Unit et al
  2. An understanding of the difference between (and significance of) the . and the : in Map.GetPlot() and pPlot:GetX()
  3. A basic knowledge of C programming and an understanding of object-orientated programming as implemented by C++ (if you don't know the significance of :: and the difference between -> and . then this tutorial is probably not for you)
  4. Successfully built, deployed and loaded the unaltered source as a custom DLL mod
  5. Created your own custom header file and added it to the CvGameCoreDLLPCH.h file

To extend the Lua API we need to do two main pieces of work
  1. Write the code to define a new Lua function attached to a Lua table (typically one of the Civ 5 "objects" - Game, Map, Player, City, Unit, etc)
  2. Hook that code up to the appropriate C++ objects and methods to perform the required actions

But first, we'll define a macro (wrapper) to make things a little bit easier. As we'll see in a bit, Firaxis provide one macro to simplify extending the Lua API but they should have provided two! So we need to define the missing one. In your custom header file add the following

Code:
// LUA API wrappers
#define LUAAPIEXTN(method)	static int l##method(lua_State* L)

If you check the Lua City object, you'll discover three related methods - GetNumRealBuilding(), SetNumRealBuilding() and GetNumFreeBuilding() - but no SetNumFreeBuilding(). We're going to start by adding that missing method.

Searching the code for GetNumFreeBuilding you'll find references in 3 pairs (.cpp/.h) of files, the ones we are interested in are CvLuaCity.cpp/h (the hint's in the file name ;) )

In CvLuaCity.h you'll find

Code:
    static int lGetNumRealBuilding(lua_State* L);
    static int lSetNumRealBuilding(lua_State* L);
    static int lGetNumFreeBuilding(lua_State* L);
    static int lIsBuildingSellable(lua_State* L);

so we need to squeeze our new method in after lGetNumFreeBuilding. Now we could just follow the pattern, but I have missed/deleted that leading l off the method name too many times to remember, which is why we defined the macro above

Code:
    static int lGetNumRealBuilding(lua_State* L);
    static int lSetNumRealBuilding(lua_State* L);
    static int lGetNumFreeBuilding(lua_State* L);
#if defined(WH_LUAAPI)
    LUAAPIEXTN(SetNumFreeBuilding);
#endif
    static int lIsBuildingSellable(lua_State* L);

That's the only change needed in CvLuaCity.h, but we need to make two more in CvLuaCity.cpp. Near the top of CvLuaCity.cpp you'll find

Code:
    Method(GetNumRealBuilding);
    Method(SetNumRealBuilding);
    Method(GetNumFreeBuilding);
    Method(IsBuildingSellable);

Method() is the Firaxis defined macro, so in this case we can just follow the pattern

Code:
    Method(GetNumRealBuilding);
    Method(SetNumRealBuilding);
    Method(GetNumFreeBuilding);
#if defined(WH_LUAAPI)
    Method(SetNumFreeBuilding);
#endif
    Method(IsBuildingSellable);

Further down the file you'll find the definition of the lGetNumFreeBuilding() method, and we'll add our lSetNumFreeBuilding() after it

Code:
int CvLuaCity::lGetNumFreeBuilding(lua_State* L)
{
    [I]// Code removed for brevity[/I]
}
#if defined(WH_LUAAPI)
int CvLuaCity::lSetNumFreeBuilding(lua_State* L)
{
    . . .
}
#endif

So the question is, "how do we join the dots?"

In Lua, we will call this method as pCity:SetNumFreeBuilding(eBuildingType, iCount), so we need to get hold of THREE pieces of information 1) the C++ equivalent of the pCity object, 2) the eBuildingType value and 3) the iCount value. All of these values are on the Lua stack that can be accessed via the lua_state object passed into our method, so we just need to use the appropriate methods/functions to get hold of them.

The pCity instance is first on the stack and a C++ pointer to it is retrieved as

Code:
    CvCity* pkCity = GetInstance(L);

Lua doesn't understand the C++ enums used to represent building, building class, unit, unit class etc types and stores them all as ints. So we need to get the second item on the stack as an int and cast it to the required C++ enum type, fortunately Firaxis have provided a C++ template to ease this, so we can access the building type as

Code:
    const BuildingTypes eBuildingType = toValue<BuildingTypes>(L, 2);

There are two approaches to getting the iCount value (the third thing on the stack), if we know it must be present we can use

Code:
    const int iCount = lua_tointeger(L, 3);

however, if we want to defend against modder stupidity we can make it optional and specify a default value (in this case 1) with

Code:
    const int iCount = luaL_optint(L, 3, 1);

With the three values obtained, we can now write the C++ code to actually do the work

Code:
    if (eBuildingType != NO_BUILDING)
    {
        pkCity->GetCityBuildings()->SetNumFreeBuilding(eBuildingType, iCount);
    }

Finally we need to specify a return value for the method. The return value is always the number of things we put back onto the Lua stack. As this is a "setter" method we haven't put anything onto the stack, so we just need to return zero. Putting it all together we get

Code:
int CvLuaCity::lGetNumFreeBuilding(lua_State* L)
{
    [I]// Code removed for brevity[/I]
}
#if defined(WH_LUAAPI)
int CvLuaCity::lSetNumFreeBuilding(lua_State* L)
{
    CvCity* pkCity = GetInstance(L);
    const BuildingTypes eBuildingType = toValue<BuildingTypes>(L, 2);
    // const int iCount = lua_tointeger(L, 3); // Use this if you know that iCount has to be passed to the Lua method
    const int iCount = luaL_optint(L, 3, 1);

    if (eBuildingType != NO_BUILDING)
    {
        pkCity->GetCityBuildings()->SetNumFreeBuilding(eBuildingType, iCount);
    }

    return 0;
}
#endif

To illustrate returning values to Lua we'll add two methods to the Unit object - CanCreateGreatWork() and CreateGreatWork()

In CvLuaUnit.h, just before the CanFound() declaration, add

Code:
#if defined(WH_LUAAPI)
    LUAAPIEXTN(CanCreateGreatWork);
    LUAAPIEXTN(CreateGreatWork);
#endif

and at the top of the CvLuaUnit.cpp file just before the Method(CanFound) macro usage, add

Code:
#if defined(WH_LUAAPI)
    Method(CanCreateGreatWork);
    Method(CreateGreatWork);
#endif

That's the house-keeping done, now we need to add the code that does the work!

The Lua methods will be called as pUnit:CanCreateGreatWork(pPlot) - "can I create a Great Work if I was standing on the specified plot?" - and pUnit:CreateGreatWork() - "I'll create a Great Work right here, right now". Both methods return a boolean - true for "I can" or "I did", and false for "I can't". These usage mimic the C++ usage.

We need a couple of extra bits of code not covered above, to get the C++ version of the Lua pPlot object from stack we use

Code:
    CvPlot* pkPlot = CvLuaPlot::GetInstance(L, 2);

and to return a value (a boolean in this case) to Lua we need to both push it onto the stack and tell Lua how many things we pushed onto the stack

Code:
    lua_pushboolean(L, bResult);
    return 1;

Putting it all together, about a quarter of the way through the CvLuaUnit.cpp file just before the lCanFound() definition, add

Code:
#if defined(WH_LUAAPI)
int CvLuaUnit::lCanCreateGreatWork(lua_State* L)
{
    CvUnit* pkUnit = GetInstance(L);
    CvPlot* pkPlot = CvLuaPlot::GetInstance(L, 2);
    const bool bResult = pkUnit->canCreateGreatWork(pkPlot);

    lua_pushboolean(L, bResult);
    return 1;
}

int CvLuaUnit::lCreateGreatWork(lua_State* L)
{
    CvUnit* pkUnit = GetInstance(L);
    const bool bResult = pkUnit->createGreatWork();

    lua_pushboolean(L, bResult);
    return 1;
}
#endif

For lua functions on objects that are static calls (eg those on Game, Map, etc) we don't use GetInstance(L) but just pick up parameters starting from position 1. For example, to add the method Game.IsInSomeReligion(eBelief) (note the use of . and not : here), in the CvLuaGame.h file add

Code:
#if defined(WH_LUAAPI)
    LUAAPIEXTN(IsInSomeReligion);
#endif

and in the CvLuaGame.cpp file, add

Code:
#if defined(WH_LUAAPI)
    Method(IsInSomeReligion);
#endif

and

Code:
#if defined(WH_LUAAPI)
int CvLuaGame::lIsInSomeReligion(lua_State* L)
{
    // There is only one Game object, so we don't get an instance and we start picking the parameters from 1
    BeliefTypes eBelief = (BeliefTypes)luaL_optint(L, 1, NO_BELIEF); // Note that we need a cast here as there is no templated version of luaL_optint()

    const bool bResult = GC.getGame().GetGameReligions()->IsInSomeReligion(eBelief);

    lua_pushboolean(L, bResult);
    return 1;
}
#endif

[END]
 
Please use this thread in the SDK / Lua sub-forum for asking questions about this tutorial, I will then update/expand this thread as necessary.

This is NOT a tutorial on basic C/C++ programming, so please do not ask questions about the syntax, semantics or usage of the C++ language - there are many excellent resources for learning C++ available (most of which can be found in your local bookshop)

Also, please do NOT ask questions about specific issues for your own mods and/or ask for someone else to write your code for you on these threads - start a new thread either in the main C&C forum or the SDK / Lua sub-forum to ask your specific question/request, as that way you'll get specific answers ;) And finally, please note that I do NOT take requests for writing code for others' mods!
 
Top Bottom