]> de.git.xonotic.org Git - xonotic/xonotic.wiki.git/blob - writing-your-first-mutator.md
nuke another section
[xonotic/xonotic.wiki.git] / writing-your-first-mutator.md
1 # Part 1: Hello world
2
3 In this tutorial we are going to create a Xonotic mutator that will print text when the player spawns.
4
5 ## Step 1: Getting source code
6
7 First, you will need to get the [source code of Xonotic](Repository_Access). Once you've cloned the repository and built the game successfully, we can start adding our code.
8
9 Xonotic is built on top of DarkPlaces engine which in turn was forked from the Quake 1 engine. The game code is written in QuakeC. If you have experience with C or C++, it will be no problem to adapt to QuakeC. All QuakeC code is located in `xonotic/data/xonotic-data.pk3dir/qcsrc/` directory.
10
11 Here is the outline of some subdirectories:
12
13 * `client` - this directory contains client-side code.
14 * `common` - this directory contains the code that is shared between client and server.
15 * `dpdefs` - this directory contains declarations that are used by the engine - a glue between the engine and the game.
16 * `lib` - this directory contains "libraries" - pieces of reusable code that are not specific to Xonotic.
17 * `menu` - this directory contains client-side menu code.
18 * `server` - this directory contains server-side code.
19 * `tools` - this directory contains some useful shell scripts.
20
21 ## Step 2: Creating a separate git branch
22
23 Xonotic uses git as its version control system. It is recommended to make your changes on a separate branch. There are few advantages of doing so. First, your code will not interfere with a main branch and you can quickly switch between vanilla Xonotic and your modded Xonotic. Second, if there is a breaking change in the master branch, it won't break your code until you will request manual merge of your code. Finally, this is the only way for your code to be officially added to the game.
24
25 Since we will only be modding the QuakeC part of the game, we only need to create our branch of `xonotic-data.pk3dir` repository. You should name your branch `<Your name>/<your feature>`.
26
27     xonotic$ cd data/xonotic-data.pk3dir
28     xonotic/data/xonotic-data.pk3dir$ git checkout -b Lyberta/HelloWorld
29
30 ## Step 3: Adding your code
31
32 Regular mutators are located in `common/mutators/mutator` directory, let's create a new directory called `helloworld`. Now, the prefixes of files determine whether the code is included in different QuakeC virtual machines. Files that start with `cl_` are only compiled into client-side code and files that start with `sv_` are only compiled into server-side code. Our mutator will be fully server-side so let's create a file called `sv_helloworld.qc`.
33
34 Now we need to register our mutator, open `sv_helloworld.qc` and add the following text:
35
36     REGISTER_MUTATOR(helloworld, cvar("g_helloworld"));
37
38 `helloworld` is the name of our mutator and `g_helloworld` is the console variable that will enable our mutator. Now we need to make our code execute when player spawns. To do that, Xonotic defines hooks that can be used by mutators. Server-side hooks are defined in `qcsrc/server/mutators/events.qh`. If we open this file and search for `PlayerSpawn`, we will find this:
39
40     #define EV_PlayerSpawn(i, o) \
41         /** player spawning */ i(entity, MUTATOR_ARGV_0_entity) \
42         /** spot that was used, or NULL */ i(entity, MUTATOR_ARGV_1_entity) \
43         /**/
44     MUTATOR_HOOKABLE(PlayerSpawn, EV_PlayerSpawn);
45
46 This code defines our hook, we can use by writing the following code in `sv_helloworld.qc`:
47
48     MUTATOR_HOOKFUNCTION(helloworld, PlayerSpawn)
49     {
50     }
51
52 Now we have created our hook but its body is empty, we need to write some code between curly braces. How to find which functions we need to call? Using grep. Grep is used to find text inside files and is essential in exploring Xonotic codebase. The syntax we need is `grep <text> -iRn`. Let's open the terminal in `qcsrc` directory and search for `PrintToChat`:
53
54     xonotic/data/xonotic-data.pk3dir/qcsrc$ grep PrintToChat -iRn
55
56 We will get something like this:
57
58     server/player.qh:18:void PrintToChat(entity player, string text);
59     server/player.qh:25:void DebugPrintToChat(entity player, string text);
60     server/player.qh:30:void PrintToChatAll(string text);
61     server/player.qh:36:void DebugPrintToChatAll(string text);
62     server/player.qh:42:void PrintToChatTeam(int teamnum, string text);
63     server/player.qh:49:void DebugPrintToChatTeam(int teamnum, string text);
64
65 Good, we have found our function. Let's add it to our mutator:
66
67     MUTATOR_HOOKFUNCTION(helloworld, PlayerSpawn)
68     {
69         PrintToChatAll("Hello world!");
70     }
71
72 ## Step 4: Adding code to the build system
73
74 Alright, that should do it. Now we need to instruct the build system to build our code. We do this by running `genmod.sh` script from the `tools` directory inside `qcsrc` directory.
75
76     xonotic/data/xonotic-data.pk3dir/qcsrc$ ./tools/genmod.sh
77
78 You can now see that there 2 new `_mod` files inside our `helloworld` directory and if you do `git diff`, you can see that other `_mod` files now contain our new mutator.
79
80 ## Step 5: Building the code
81
82 Now we need to build the QuakeC code. It is done using the `all` script:
83
84     xonotic$ ./all compile -qc -r
85
86 ## Step 6: Adjusting config files
87
88 The last thing we need to do is to create our console variable `g_helloworld` in configuration file. The best file for it would be `defaultXonotic.cfg`. Mutator variables start around line 423:
89
90     set g_helloworld 0
91
92 ## Step 7: Testing
93
94 The best way to test code changes is to run a dedicated server. You need to put `g_helloworld 1` in your `server.cfg`.
95
96 Now, start your server, connect to it and spawn. You should see the "Hello world!" message in the chat.
97
98 ## Step 8: Committing changes
99
100 Now, if everything works you can commit your mutator into the git repository:
101
102     xonotic/data/xonotic-data.pk3dir$ git add .
103     xonotic/data/xonotic-data.pk3dir$ git commit -m "Added HelloWorld mutator. Yay!"
104
105 Now you can switch between vanilla Xonotic and your modded version. Type:
106
107     xonotic/data/xonotic-data.pk3dir$ git checkout master
108
109 to switch to vanilla Xonotic and type
110
111     xonotic/data/xonotic-data.pk3dir$ git checkout Lyberta/HelloWorld
112
113 to switch to your modded version.
114
115 # Part 2: Variables
116
117 ## Printing to specific player's chat
118
119 If you've tested your mutator with bots or other people, you may have noticed that `Hello world!` is being printed to everyone when any player spawns. Let's make it so it is only printed to the player that just spawned. For that we need to know the player entity. Thankfully, our hook has it, let's look at it again:
120
121     #define EV_PlayerSpawn(i, o) \
122         /** player spawning */ i(entity, MUTATOR_ARGV_0_entity) \
123         /** spot that was used, or NULL */ i(entity, MUTATOR_ARGV_1_entity) \
124         /**/
125     MUTATOR_HOOKABLE(PlayerSpawn, EV_PlayerSpawn);
126
127 As you can see, it has player entity as the variable at index 0. We can access it using `M_ARGV` macro.
128
129     entity player = M_ARGV(0, entity);
130
131 And then we can use `PrintToChat` function.
132
133     MUTATOR_HOOKFUNCTION(helloworld, PlayerSpawn)
134     {
135         entity player = M_ARGV(0, entity);
136         PrintToChat(player, "Hello world!");
137     }
138
139 ## Greeting the player personally
140
141 But we can do more, we can greet the player with their name. Player name is stored in the `netname` field of their entity. However, we also need to concatenate the `Hello` string with the player's name. We can use the `strcat` function to do this.
142
143     MUTATOR_HOOKFUNCTION(helloworld, PlayerSpawn)
144     {
145         entity player = M_ARGV(0, entity);
146         string greeting = strcat("Hello, ", player.netname);
147         PrintToChat(player, greeting);
148     }
149
150 Or, less verbose:
151
152     MUTATOR_HOOKFUNCTION(helloworld, PlayerSpawn)
153     {
154         entity player = M_ARGV(0, entity);
155         PrintToChat(player, strcat("Hello, ", player.netname));
156     }
157
158 ## Modifying hook variables
159
160 `M_ARGV` macro can also be used to modify variables. For example, let's make our mutator double all damage. There is a `Damage_Calculate` hook:
161
162     #define EV_Damage_Calculate(i, o) \
163         /** inflictor           */ i(entity, MUTATOR_ARGV_0_entity) \
164         /** attacker            */ i(entity, MUTATOR_ARGV_1_entity) \
165         /** target              */ i(entity, MUTATOR_ARGV_2_entity) \
166         /** deathtype           */ i(float,  MUTATOR_ARGV_3_float) \
167         /** damage          */ i(float,  MUTATOR_ARGV_4_float) \
168         /** damage              */ o(float,  MUTATOR_ARGV_4_float) \
169         /** mirrordamage    */ i(float,  MUTATOR_ARGV_5_float) \
170         /** mirrordamage        */ o(float,  MUTATOR_ARGV_5_float) \
171         /** force           */ i(vector, MUTATOR_ARGV_6_vector) \
172         /** force                       */ o(vector, MUTATOR_ARGV_6_vector) \
173         /**/
174     MUTATOR_HOOKABLE(Damage_Calculate, EV_Damage_Calculate);
175
176 As you can see, there are a lot of variables, we need only `damage`. It has index 4.
177
178     MUTATOR_HOOKFUNCTION(helloworld, Damage_Calculate)
179     {
180         float damage = M_ARGV(4, float);
181         damage *= 2;
182                 M_ARGV(4, float) = damage;
183     }
184
185 Or less verbose:
186
187     MUTATOR_HOOKFUNCTION(helloworld, Damage_Calculate)
188     {
189         M_ARGV(4, float) *= 2;
190     }