Adding a dynamic scripting language to a game allows for rapid changing of game elements and also allows the community to change and expand the game itself. There are many scripting languages to choose from so why python? Compared to other scripting languages python offers a relatively simple syntax, dynamic typing, vast standard libraries, easy to interface with C/C++ via boost.python and a lot of documentation and tutorials. However python does have a few set backs. Its very hard to sandbox python thus it can open up a lot of exploits to server operators and there is some complexities that the programmers have to deal with (but this is better than having the end user deal with other complexities that python removes).
Adding Python to Source the source engine from scratch is a major task as there is a lot of work involved in getting all the different components running. However the GoldenEye Source Team has already done the majority of this work so that other teams can easily integrate python in a matter of minutes.
- 1 Needed Files
- 2 First Steps
- 3 Python Manager
- 4 Init Python
- 5 Making Your own Python Instance
- 6 Creating The Instance
- 7 Exposing C++ Classes/Functions/Globals to Python
- 8 Notes on compiling python from scratch
You will need to check out the code using svn from: http://code.google.com/p/sourcesdkpython/source/checkout (Alternative: https://github.com/Sandern/py-source-sdk-2013) And then export it into your mod.
Once you have downloaded the package above extract the folders in the src folder into your root source folder and the folders in the mod folder into your root mod folder.
In the server project settings you need to add some settings so it compile correctly. You will need to add theses to both release and debug unless other wise stated.
Add these to: Configure properties -> c++ -> General-> Additional Include Directories
Add these to: Configure properties -> c++ -> Preprocesser -> Preprocesser defines
For Both release and debug: Py_NO_ENABLE_SHARED BOOST_PYTHON_STATIC_LIB BOOST_PYTHON_SOURCE BOOST_PYTHON_NO_LIB For debug only: Py_DEBUG
Add this to: Configure properties -> Linker -> General -> Additional Library Directories
Add these to: Configure properties -> Linker -> Input -> Additional Dependencies
For release build: boost_pythoncore.lib pythoncore.lib For Debug build: boost_pythoncore_d.lib pythoncore_d.lib
In your Half-Life 2 SDK Solution, add these two files to your server project (make a new folder called python):
ge_pymodules.cpp ge_pymanager.cpp ge_pymanager.h
These files define the python manager class (PMC). The PMC enables you to have different instances of python running for different tasks. Each instance has its own dictionary that contains the global defines and local defines and so that two instances cant impeded on each other. PMC also loads the setup scripts that redirect python io to the console and set up the root python path so it knows where to load python files from. The PMC also includes a concommand py that enables you to execute python code in the global python instance (support for other instances coming soon)
Please note: that each python instance is executed in the same python engine and thus should be only used in the same thread (not an issue for source).
Once that is done you will need to make sure your game code calls the init and shutdown functions. In gameinterface.cpp add this into DLLInit before the return true statement (around line 550):
And in the DLLShutdown function add this:
Also add this after the defines up the top:
extern void PythonInit();
extern void PythonShutdown();
Note: The reason we extern this instead of include a header file is to keep the python aware code separate from the source sdk code.
The last thing to do is to call InitHandles (GoldenEye Source does this in GameRules but it can be any where you want). Add this to your game rule constructor:
And to the deconstructor:
And again we need to extern it up the top of the file:
extern void PythonInitHandles();
extern void PythonShutdownHandles();
Making Your own Python Instance
Now this is the fun part, making your own python instance. You will need to make your own class that inherets from PyHandle and provides functionality for Init, Shutdown.
Note: An example python handle is provided in the package.
Init gets called after Python gets init and thus provides the best place to load your script.
Note: The default python dir is modfiles\scripts\python and can be changed in the python manager
Shutdown handles the shutting down of python handle and should do things in here like call shutdown functions in your script.
The Python handle class allows you to exec code as c++ strings by calling Exec and also get the dictionary for use with other Python functions
Creating The Instance
On the top of your new PyHandle class you need to add a function allowing global access to your Python instance. Add something like this:
MyPyHandle* g_PyHandle = NULL;
return PyHandle ;
And in your constructor of your PyHandle add a call to set the instance when you make a new one:
MyPyHandle::MyPyHandle() : PyHandle()
g_PyHandle = this;
Note: Its important to have Register here other wise it wont work!
And finally in your game rules constructor make a new PyHandle:
//other stuff here
// Start our Python Handle
Exposing C++ Classes/Functions/Globals to Python
This is quite easy and a detailed guide can be found on the boost.python webpage. Once you have your module defined you will need to add it to ge_pymodule.cpp like so:
REG( HLUtil ); //this one is already there
REG( MyModule ); //this is your new module (change the name to match yours)
Some interfaces for the Source SDK have already been provided (feel free to expand on this as well), you will just need to add them to your project and then include them in the register python modules function.
Notes on compiling python from scratch
bp namespace errors
If you receive an error in ge_pymodules.cpp or elsewhere related to the bp namespace not existing, make sure you include boost/python.hpp, and definition of the namespace, as in ge_pymanager.cpp:
namespace bp = boost::python;
Recompile the boost python libraries from the solution in src\utils\python\boost, though you may need to change the output file paths to match those of the existing boost_pythoncore(_d).lib file.