Hello world!¶
This is the classical “Hello world!” example for q3py, q3py_hello.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import q3py
import ctypes
# Game module initialization function.
# Defined in ioq3/code/game/g_public.h
GAME_INIT = 0
# Engine system trap to print errors from game module.
# Defined in ioq3/code/game/g_public.h
G_ERROR = 1
# Compare with trap_Error() from ioq3/code/game/g_syscalls.c
def qerror(msg):
c_msg = ctypes.create_string_buffer(msg)
return q3py.syscall(G_ERROR, ctypes.addressof(c_msg))
# Compare with G_InitGame() from ioq3/code/game/g_main.c
def init_game(level_time, random_seed, restart):
print("Python init_game(level_time={level_time}, "
"random_seed={random_seed}, "
"restart={restart})".format(level_time=level_time,
random_seed=random_seed,
restart=restart))
qerror(b"Hello, Quake 3!")
# Compare with vmMain() from ioq3/code/game/g_main.c
def vm_main(cmd, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
arg9, arg10, arg11):
if (cmd == GAME_INIT):
init_game(arg0, arg1, bool(arg2))
return -1
# Related to dllEntry() in ioq3/code/game/g_syscalls.c
def dll_entry():
print("Python dll_entry() called")
return vm_main
|
As you can see, this is a little more involved, but to be fair it is not the most simple way to write a hello world program with q3py.
To run this example, take a look at the Configuration page. The output might look like this:
Q3PY [INFO]: Entry point is 'q3py_hello:dll_entry'
Python dll_entry() called
Q3PY [INFO]: v0.0.1 initialized
Python init_game(level_time=0, random_seed=42, restart=False)
********************
ERROR: Hello, Quake 3!
********************
----- Server Shutdown (Server crashed: Hello, Quake 3!) -----
forcefully unloading qagame vm
---------------------------
]
Hey wait, did it just say we crashed the server?
Yes, but there is a reason to this. The purpose of a Quake 3 module is not to print “Hello world”, but to implement game logic. We only implemented a tiny little part of the game logic (we could have done this for cgame or ui as well).
When Quake 3 loads this Python module via q3py, it calls the vmMain function of q3py, which in turn calls our vm_main, since that’s what we told q3py to do. The vmMain function acts as a generic dispatcher for all the calls of the engine, hence the ugly arguments and argument names.
Either way, upon initialization Quake 3 calls vmMain with the command GAME_INIT and some additional arguments specific to this call, which are getting passed into init_game here.
In init_game, we call a qerror method with the message that we saw during the server crash. So now in qerror we use q3py to do a “syscall” back into the Quake 3 engine and as you might have guessed, we call the G_ERROR syscall which justs prints the message as we’ve seen and exits the game :)