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 :)