]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
Merge branch 'master' into martin-t/defaults
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / random_items / sv_random_items.qc
1 #include "sv_random_items.qh"
2
3 /// \file
4 /// \brief Source file that contains implementation of the random items mutator.
5 /// \author Lyberta
6 /// \copyright GNU GPLv2 or any later version.
7
8 //============================ Constants ======================================
9
10 //======================= Global variables ====================================
11
12 // Replace cvars
13
14 /// \brief Classnames to replace %s with.
15 /// string autocvar_g_random_items_replace_%s;
16
17 // Map probability cvars
18
19 /// \brief Probability of random %s spawning in the map.
20 /// float autocvar_g_random_items_%s_probability;
21
22 /// \brief Probability of random %s spawning in the map during overkill.
23 /// float autocvar_g_random_items_overkill_%s_probability;
24
25 // Loot
26
27 float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
28 float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
29 float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
30 float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
31
32 // Loot probability cvars
33
34 /// \brief Probability of random %s spawning as loot.
35 /// float autocvar_g_random_loot_%s_probability;
36
37 /// \brief Probability of random %s spawning as loot during overkill.
38 /// float autocvar_g_random_loot_overkill_%s_probability;
39
40 /// \brief Holds whether random item is spawning. Used to prevent infinite
41 /// recursion.
42 bool random_items_is_spawning = false;
43
44 //====================== Forward declarations =================================
45
46 /// \brief Returns a random classname of the item with specific property.
47 /// \param[in] prefix Prefix of the cvars that hold probabilities.
48 /// \return Random classname of the item.
49 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
50         .bool item_property);
51
52 //=========================== Public API ======================================
53
54 string RandomItems_GetRandomItemClassName(string prefix)
55 {
56         if (MUTATOR_CALLHOOK(RandomItems_GetRandomItemClassName, prefix))
57         {
58                 return M_ARGV(1, string);
59         }
60         return RandomItems_GetRandomVanillaItemClassName(prefix,
61                 RANDOM_ITEM_TYPE_ALL);
62 }
63
64 string RandomItems_GetRandomVanillaItemClassName(string prefix, int types)
65 {
66         if (types == 0)
67         {
68                 return "";
69         }
70         while (types != 0)
71         {
72                 string cvar_name;
73                 RandomSelection_Init();
74                 if (types & RANDOM_ITEM_TYPE_HEALTH)
75                 {
76                         cvar_name = sprintf("g_%s_health_probability", prefix);
77                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
78                         {
79                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
80                         }
81                         else
82                         {
83                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
84                                         cvar(cvar_name), 1);
85                         }
86                 }
87                 if (types & RANDOM_ITEM_TYPE_ARMOR)
88                 {
89                         cvar_name = sprintf("g_%s_armor_probability", prefix);
90                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
91                         {
92                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
93                         }
94                         else
95                         {
96                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
97                                         cvar(cvar_name), 1);
98                         }
99                 }
100                 if (types & RANDOM_ITEM_TYPE_RESOURCE)
101                 {
102                         cvar_name = sprintf("g_%s_resource_probability", prefix);
103                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
104                         {
105                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
106                         }
107                         else
108                         {
109                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
110                                         cvar(cvar_name), 1);
111                         }
112                 }
113                 if (types & RANDOM_ITEM_TYPE_WEAPON)
114                 {
115                         cvar_name = sprintf("g_%s_weapon_probability", prefix);
116                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
117                         {
118                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
119                         }
120                         else
121                         {
122                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON, cvar(cvar_name), 1);
123                         }
124                 }
125                 if (types & RANDOM_ITEM_TYPE_POWERUP)
126                 {
127                         cvar_name = sprintf("g_%s_powerup_probability", prefix);
128                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
129                         {
130                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
131                         }
132                         else
133                         {
134                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP, cvar(cvar_name), 1);
135                         }
136                 }
137                 int item_type = RandomSelection_chosen_float;
138                 string class_name = "";
139                 switch (item_type)
140                 {
141                         case RANDOM_ITEM_TYPE_HEALTH:
142                         {
143                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
144                                         prefix, instanceOfHealth);
145                                 break;
146                         }
147                         case RANDOM_ITEM_TYPE_ARMOR:
148                         {
149                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
150                                         prefix, instanceOfArmor);
151                                 break;
152                         }
153                         case RANDOM_ITEM_TYPE_RESOURCE:
154                         {
155                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
156                                         prefix, instanceOfAmmo);
157                                 break;
158                         }
159                         case RANDOM_ITEM_TYPE_WEAPON:
160                         {
161                                 RandomSelection_Init();
162                                 FOREACH(Weapons, it != WEP_Null &&
163                                         !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
164                                 {
165                                         cvar_name = sprintf("g_%s_%s_probability", prefix,
166                                                 it.m_canonical_spawnfunc);
167                                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
168                                         {
169                                                 LOG_WARNF("Random items: cvar %s doesn't exist.",
170                                                         cvar_name);
171                                                 continue;
172                                         }
173                                         RandomSelection_AddString(it.m_canonical_spawnfunc,
174                                                 cvar(cvar_name), 1);
175                                 });
176                                 class_name = RandomSelection_chosen_string;
177                                 break;
178                         }
179                         case RANDOM_ITEM_TYPE_POWERUP:
180                         {
181                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
182                                         prefix, instanceOfPowerup);
183                                 break;
184                         }
185                 }
186                 if (class_name != "")
187                 {
188                         return class_name;
189                 }
190                 types &= ~item_type;
191         }
192         return "";
193 }
194
195 //========================= Free functions ====================================
196
197 /// \brief Returns list of classnames to replace a map item with.
198 /// \param[in] item Item to inspect.
199 /// \return List of classnames to replace a map item with.
200 string RandomItems_GetItemReplacementClassNames(entity item)
201 {
202         string cvar_name = sprintf("g_random_items_replace_%s", item.classname);
203         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
204         {
205                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
206                 return "";
207         }
208         return cvar_string(cvar_name);
209 }
210
211 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
212         .bool item_property)
213 {
214         RandomSelection_Init();
215         FOREACH(Items, it.item_property && (it.spawnflags & ITEM_FLAG_NORMAL) &&
216                 Item_IsDefinitionAllowed(it),
217         {
218                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
219                         it.m_canonical_spawnfunc);
220                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
221                 {
222                         LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
223                         continue;
224                 }
225                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
226         });
227         return RandomSelection_chosen_string;
228 }
229
230 /// \brief Replaces a map item.
231 /// \param[in] item Item to replace.
232 /// \return Spawned item on success, NULL otherwise.
233 entity RandomItems_ReplaceMapItem(entity item)
234 {
235         //PrintToChatAll(strcat("Replacing ", item.classname));
236         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
237         if (new_classnames == "")
238         {
239                 return NULL;
240         }
241         string new_classname;
242         if (new_classnames == "random")
243         {
244                 new_classname = RandomItems_GetRandomItemClassName("random_items");
245                 if (new_classname == "")
246                 {
247                         return NULL;
248                 }
249         }
250         else
251         {
252                 int num_new_classnames = tokenize_console(new_classnames);
253                 if (num_new_classnames == 1)
254                 {
255                         new_classname = new_classnames;
256                 }
257                 else
258                 {
259                         int classname_index = floor(random() * num_new_classnames);
260                         new_classname = argv(classname_index);
261                 }
262         }
263         //PrintToChatAll(strcat("Replacing with ", new_classname));
264         if (new_classname == item.classname)
265         {
266                 return NULL;
267         }
268         random_items_is_spawning = true;
269         entity new_item;
270         if (!MUTATOR_IS_ENABLED(ok))
271         {
272                 new_item = Item_Create(strzone(new_classname), item.origin,
273                         Item_ShouldKeepPosition(item));
274                 random_items_is_spawning = false;
275                 if (new_item == NULL)
276                 {
277                         return NULL;
278                 }
279         }
280         else
281         {
282                 new_item = spawn();
283                 new_item.classname = strzone(new_classname);
284                 new_item.spawnfunc_checked = true;
285                 new_item.noalign = Item_ShouldKeepPosition(item);
286                 new_item.ok_item = true;
287                 Item_Initialize(new_item, new_classname);
288                 random_items_is_spawning = false;
289                 if (wasfreed(new_item))
290                 {
291                         return NULL;
292                 }
293                 setorigin(new_item, item.origin);
294         }
295         if (item.team)
296         {
297                 new_item.team = item.team;
298         }
299         return new_item;
300 }
301
302 /// \brief Spawns a random loot item.
303 /// \param[in] position Position of the item.
304 /// \return No return.
305 void RandomItems_SpawnLootItem(vector position)
306 {
307         string class_name = RandomItems_GetRandomItemClassName("random_loot");
308         if (class_name == "")
309         {
310                 return;
311         }
312         vector spread = '0 0 0';
313         spread.z = autocvar_g_random_loot_spread / 2;
314         spread += randomvec() * autocvar_g_random_loot_spread;
315         random_items_is_spawning = true;
316         if (!MUTATOR_IS_ENABLED(ok))
317         {
318                 Item_CreateLoot(class_name, position, spread,
319                         autocvar_g_random_loot_time);
320         }
321         else
322         {
323                 entity item = spawn();
324                 item.ok_item = true;
325                 item.classname = class_name;
326                 Item_InitializeLoot(item, class_name, position, spread,
327                         autocvar_g_random_loot_time);
328         }
329         random_items_is_spawning = false;
330 }
331
332 //============================= Hooks ========================================
333
334 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
335 {
336         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
337 }
338
339 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
340 {
341         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
342 }
343
344 /// \brief Hook that is called when an item is about to spawn.
345 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
346 {
347         //PrintToChatAll("FilterItem");
348         if (!autocvar_g_random_items)
349         {
350                 return false;
351         }
352         if (random_items_is_spawning == true)
353         {
354                 return false;
355         }
356         entity item = M_ARGV(0, entity);
357         if (Item_IsLoot(item))
358         {
359                 return false;
360         }
361         if (RandomItems_ReplaceMapItem(item) == NULL)
362         {
363                 return false;
364         }
365         return true;
366 }
367
368 /// \brief Hook that is called after the player has touched an item.
369 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
370 {
371         //PrintToChatAll("ItemTouched");
372         if (!autocvar_g_random_items)
373         {
374                 return;
375         }
376         entity item = M_ARGV(0, entity);
377         if (Item_IsLoot(item))
378         {
379                 return;
380         }
381         entity new_item = RandomItems_ReplaceMapItem(item);
382         if (new_item == NULL)
383         {
384                 return;
385         }
386         Item_ScheduleRespawn(new_item);
387         delete(item);
388 }
389
390 /// \brief Hook which is called when the player dies.
391 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
392 {
393         //PrintToChatAll("PlayerDies");
394         if (!autocvar_g_random_loot)
395         {
396                 return;
397         }
398         entity victim = M_ARGV(2, entity);
399         vector loot_position = victim.origin + '0 0 32';
400         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
401                 autocvar_g_random_loot_max);
402         for (int item_index = 0; item_index < num_loot_items; ++item_index)
403         {
404                 RandomItems_SpawnLootItem(loot_position);
405         }
406 }