]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
Random items: Removing more useless macros.
[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 enum
11 {
12         RANDOM_ITEM_TYPE_HEALTH = 1,
13         RANDOM_ITEM_TYPE_ARMOR,
14         RANDOM_ITEM_TYPE_RESOURCE,
15         RANDOM_ITEM_TYPE_WEAPON,
16         RANDOM_ITEM_TYPE_POWERUP
17 };
18
19 //======================= Global variables ====================================
20
21 // Replace cvars
22
23 /// \brief Classnames to replace %s with.
24 /// string autocvar_g_random_items_replace_%s;
25
26 // Map probability cvars
27
28 /// \brief Probability of random health items spawning in the map.
29 float autocvar_g_random_items_health_probability;
30 /// \brief Probability of random armor items spawning in the map.
31 float autocvar_g_random_items_armor_probability;
32 /// \brief Probability of random resource items spawning in the map.
33 float autocvar_g_random_items_resource_probability;
34 /// \brief Probability of random weapons spawning in the map.
35 float autocvar_g_random_items_weapon_probability;
36 /// \brief Probability of random powerups spawning in the map.
37 float autocvar_g_random_items_powerup_probability;
38
39 /// \brief Probability of random %s spawning in the map.
40 /// float autocvar_g_random_items_%s_probability;
41
42 /// \brief Probability of random %s spawning in the map during overkill.
43 /// float autocvar_g_random_items_overkill_%s_probability;
44
45 // Loot
46
47 bool autocvar_g_random_loot; ///< Whether to enable random loot.
48
49 float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
50 float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
51 float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
52 float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
53
54 // Loot probability cvars
55
56 /// \brief Probability of random health items spawning as loot.
57 float autocvar_g_random_loot_health_probability;
58 /// \brief Probability of random armor items spawning as loot.
59 float autocvar_g_random_loot_armor_probability;
60 /// \brief Probability of random resource items spawning as loot.
61 float autocvar_g_random_loot_resource_probability;
62 /// \brief Probability of random weapons spawning as loot.
63 float autocvar_g_random_loot_weapon_probability;
64 /// \brief Probability of random powerups spawning as loot.
65 float autocvar_g_random_loot_powerup_probability;
66
67 /// \brief Probability of random %s spawning as loot.
68 /// float autocvar_g_random_loot_weapon_%s_probability;
69
70 /// \brief Probability of random %s spawning as loot during overkill.
71 /// float autocvar_g_random_loot_overkill_%s_probability;
72
73 /// \brief Holds whether random item is spawning. Used to prevent infinite
74 /// recursion.
75 bool random_items_is_spawning = false;
76
77 //========================= Free functions ====================================
78
79 string RandomItems_GetItemVarName(string class_name)
80 {
81         if (startsWith(class_name, "weapon_"))
82         {
83                 FOREACH(Weapons, it.m_canonical_spawnfunc == class_name, {
84                         if (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)
85                         {
86                                 return "";
87                         }
88                         return class_name;
89                 });
90         }
91         bool is_ok = expr_evaluate(autocvar_g_overkill);
92         switch (class_name)
93         {
94                 #define XCOND(classname, expr) case #classname: if (expr) return #classname; else break
95                 case "item_health_small": return "item_health_small";
96                 case "item_health_medium": return "item_health_medium";
97                 case "item_health_big": return "item_health_big";
98                 XCOND(item_health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
99
100                 case "item_armor_small": return "item_armor_small";
101                 XCOND(item_armor_medium, !is_ok || !autocvar_g_overkill_filter_armormedium);
102                 XCOND(item_armor_big, !is_ok || !autocvar_g_overkill_filter_armorbig);
103                 XCOND(item_armor_mega, !is_ok || !autocvar_g_overkill_filter_armormega);
104
105                 case "item_shells": return "item_shells";
106                 case "item_bullets": return "item_bullets";
107                 case "item_rockets": return "item_rockets";
108                 case "item_cells": return "item_cells";
109                 case "item_plasma": return "item_plasma";
110                 case "item_fuel": return "item_fuel";
111
112                 case "item_strength": return "item_strength";
113                 case "item_shield": return "item_shield";
114                 case "item_fuel_regen": return "item_fuel_regen";
115                 case "item_jetpack": return "item_jetpack";
116
117                 case "item_vaporizer_cells": return "item_vaporizer_cells";
118                 case "item_invisibility": return "item_invisibility";
119                 case "item_extralife": return "item_extralife";
120                 case "item_speed": return "item_speed";
121
122                 #undef XCOND
123         }
124         return "";
125 }
126
127 /// \brief Returns list of classnames to replace a map item with.
128 /// \param[in] item Item to inspect.
129 /// \return List of classnames to replace a map item with.
130 string RandomItems_GetItemReplacementClassNames(entity item)
131 {
132         string class_name = RandomItems_GetItemVarName(item.classname);
133         if (class_name)
134         {
135                 return cvar_string(sprintf("g_random_items_replace_%s", class_name));
136         }
137         return "";
138 }
139
140 /// \brief Returns a random classname of the instagib map item.
141 /// \return Random classname of the instagib map item.
142 string RandomItems_GetRandomInstagibMapItemClassName()
143 {
144         RandomSelection_Init();
145         #define X(classname) \
146                 RandomSelection_AddString( \
147                         classname, \
148                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
149                         1 \
150                 )
151         X("item_vaporizer_cells");
152         X("item_invisibility");
153         X("item_extralife");
154         X("item_speed");
155         #undef X
156         return RandomSelection_chosen_string;
157 }
158
159 /// \brief Returns a random classname of the overkill map item.
160 /// \return Random classname of the overkill map item.
161 string RandomItems_GetRandomOverkillMapItemClassName()
162 {
163         RandomSelection_Init();
164         string varname;
165         #define X(classname) MACRO_BEGIN \
166                 if ((varname = RandomItems_GetItemVarName(classname))) \
167                 { \
168                         RandomSelection_AddString( \
169                                 classname, \
170                                 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
171                                 1 \
172                         ); \
173                 } \
174         MACRO_END
175         X("item_health_mega");
176         X("item_armor_small");
177         X("item_armor_medium");
178         X("item_armor_big");
179         X("item_armor_mega");
180         X("weapon_hmg");
181         X("weapon_rpc");
182         #undef X
183         return RandomSelection_chosen_string;
184 }
185
186 /// \brief Returns a random classname of the map item.
187 /// \return Random classname of the map item.
188 string RandomItems_GetRandomMapItemClassName()
189 {
190         if (autocvar_g_instagib)
191         {
192                 return RandomItems_GetRandomInstagibMapItemClassName();
193         }
194         if (expr_evaluate(autocvar_g_overkill))
195         {
196                 return RandomItems_GetRandomOverkillMapItemClassName();
197         }
198         RandomSelection_Init();
199         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
200                 autocvar_g_random_items_health_probability, 1);
201         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
202                 autocvar_g_random_items_armor_probability, 1);
203         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
204                 autocvar_g_random_items_resource_probability, 1);
205         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON,
206                 autocvar_g_random_items_weapon_probability, 1);
207         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP,
208                 autocvar_g_random_items_powerup_probability, 1);
209         int item_type = RandomSelection_chosen_float;
210         switch (item_type)
211         {
212                 case RANDOM_ITEM_TYPE_HEALTH:
213                 {
214                         RandomSelection_Init();
215                         FOREACH(Items, it.instanceOfHealth,
216                         {
217                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
218                                         cvar(sprintf("g_random_items_%s_probability",
219                                         it.m_canonical_spawnfunc)), 1);
220                         });
221                         return RandomSelection_chosen_string;
222                 }
223                 case RANDOM_ITEM_TYPE_ARMOR:
224                 {
225                         RandomSelection_Init();
226                         FOREACH(Items, it.instanceOfArmor,
227                         {
228                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
229                                         cvar(sprintf("g_random_items_%s_probability",
230                                         it.m_canonical_spawnfunc)), 1);
231                         });
232                         return RandomSelection_chosen_string;
233                 }
234                 case RANDOM_ITEM_TYPE_RESOURCE:
235                 {
236                         RandomSelection_Init();
237                         FOREACH(Items, it.instanceOfAmmo,
238                         {
239                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
240                                         cvar(sprintf("g_random_items_%s_probability",
241                                         it.m_canonical_spawnfunc)), 1);
242                         });
243                         return RandomSelection_chosen_string;
244                 }
245                 case RANDOM_ITEM_TYPE_WEAPON:
246                 {
247                         RandomSelection_Init();
248                         FOREACH(Weapons, it != WEP_Null &&
249                                 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
250                         {
251                                 string cvar_name = sprintf("g_random_items_%s_probability",
252                                         it.m_canonical_spawnfunc);
253                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
254                                         cvar(cvar_name), 1);
255                         });
256                         return RandomSelection_chosen_string;
257                 }
258                 case RANDOM_ITEM_TYPE_POWERUP:
259                 {
260                         RandomSelection_Init();
261                         #define X(classname) \
262                                 RandomSelection_AddString( \
263                                         classname, \
264                                         cvar(sprintf("g_random_items_%s_probability", classname)), \
265                                         1 \
266                                 )
267                         X("item_strength");
268                         X("item_shield");
269                         X("item_fuel_regen");
270                         X("item_jetpack");
271                         #undef X
272                         return RandomSelection_chosen_string;
273                 }
274         }
275         return "";
276 }
277
278 /// \brief Replaces a map item.
279 /// \param[in] item Item to replace.
280 /// \return Spawned item on success, NULL otherwise.
281 entity RandomItems_ReplaceMapItem(entity item)
282 {
283         //PrintToChatAll(strcat("Replacing ", item.classname));
284         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
285         if (new_classnames == "")
286         {
287                 return NULL;
288         }
289         string new_classname;
290         if (new_classnames == "random")
291         {
292                 new_classname = RandomItems_GetRandomMapItemClassName();
293                 if (new_classname == "")
294                 {
295                         return NULL;
296                 }
297         }
298         else
299         {
300                 int num_new_classnames = tokenize_console(new_classnames);
301                 if (num_new_classnames == 1)
302                 {
303                         new_classname = new_classnames;
304                 }
305                 else
306                 {
307                         int classname_index = floor(random() * num_new_classnames);
308                         new_classname = argv(classname_index);
309                 }
310         }
311         //PrintToChatAll(strcat("Replacing with ", new_classname));
312         if (new_classname == item.classname)
313         {
314                 return NULL;
315         }
316         random_items_is_spawning = true;
317         entity new_item;
318         if (!expr_evaluate(autocvar_g_overkill))
319         {
320                 new_item = Item_Create(strzone(new_classname), item.origin);
321                 random_items_is_spawning = false;
322                 if (new_item == NULL)
323                 {
324                         return NULL;
325                 }
326         }
327         else
328         {
329                 new_item = spawn();
330                 new_item.classname = strzone(new_classname);
331                 new_item.spawnfunc_checked = true;
332                 new_item.ok_item = true;
333                 Item_Initialize(new_item, new_classname);
334                 random_items_is_spawning = false;
335                 if (wasfreed(new_item))
336                 {
337                         return NULL;
338                 }
339                 setorigin(new_item, item.origin);
340         }
341         if (item.team)
342         {
343                 new_item.team = item.team;
344         }
345         return new_item;
346 }
347
348 /// \brief Returns a random classname of the instagib loot item.
349 /// \return Random classname of the instagib loot item.
350 string RandomItems_GetRandomInstagibLootItemClassName()
351 {
352         RandomSelection_Init();
353         #define X(classname) \
354                 RandomSelection_AddString( \
355                         classname, \
356                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
357                         1 \
358                 )
359         X("item_vaporizer_cells");
360         X("item_invisibility");
361         X("item_extralife");
362         X("item_speed");
363         #undef X
364         return RandomSelection_chosen_string;
365 }
366
367 /// \brief Returns a random classname of the overkill loot item.
368 /// \return Random classname of the overkill loot item.
369 string RandomItems_GetRandomOverkillLootItemClassName()
370 {
371         RandomSelection_Init();
372         string varname;
373         #define X(classname) MACRO_BEGIN \
374                 if ((varname = RandomItems_GetItemVarName(classname))) \
375                 { \
376                         RandomSelection_AddString( \
377                                 classname, \
378                                 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
379                                 1 \
380                         ); \
381                 } \
382         MACRO_END
383         X("item_health_mega");
384         X("item_armor_small");
385         X("item_armor_medium");
386         X("item_armor_big");
387         X("item_armor_mega");
388         X("weapon_hmg");
389         X("weapon_rpc");
390         #undef X
391         return RandomSelection_chosen_string;
392 }
393
394 /// \brief Returns a random classname of the loot item.
395 /// \return Random classname of the loot item.
396 string RandomItems_GetRandomLootItemClassName()
397 {
398         if (autocvar_g_instagib)
399         {
400                 return RandomItems_GetRandomInstagibLootItemClassName();
401         }
402         if (expr_evaluate(autocvar_g_overkill))
403         {
404                 return RandomItems_GetRandomOverkillLootItemClassName();
405         }
406         RandomSelection_Init();
407         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
408                 autocvar_g_random_loot_health_probability, 1);
409         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
410                 autocvar_g_random_loot_armor_probability, 1);
411         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
412                 autocvar_g_random_loot_resource_probability, 1);
413         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON,
414                 autocvar_g_random_loot_weapon_probability, 1);
415         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP,
416                 autocvar_g_random_loot_powerup_probability, 1);
417         int item_type = RandomSelection_chosen_float;
418         switch (item_type)
419         {
420                 case RANDOM_ITEM_TYPE_HEALTH:
421                 {
422                         RandomSelection_Init();
423                         FOREACH(Items, it.instanceOfHealth,
424                         {
425                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
426                                         cvar(sprintf("g_random_loot_%s_probability",
427                                         it.m_canonical_spawnfunc)), 1);
428                         });
429                         return RandomSelection_chosen_string;
430                 }
431                 case RANDOM_ITEM_TYPE_ARMOR:
432                 {
433                         RandomSelection_Init();
434                         FOREACH(Items, it.instanceOfArmor,
435                         {
436                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
437                                         cvar(sprintf("g_random_loot_%s_probability",
438                                         it.m_canonical_spawnfunc)), 1);
439                         });
440                         return RandomSelection_chosen_string;
441                 }
442                 case RANDOM_ITEM_TYPE_RESOURCE:
443                 {
444                         RandomSelection_Init();
445                         FOREACH(Items, it.instanceOfAmmo,
446                         {
447                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
448                                         cvar(sprintf("g_random_loot_%s_probability",
449                                         it.m_canonical_spawnfunc)), 1);
450                         });
451                         return RandomSelection_chosen_string;
452                 }
453                 case RANDOM_ITEM_TYPE_WEAPON:
454                 {
455                         RandomSelection_Init();
456                         FOREACH(Weapons, it != WEP_Null &&
457                                 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
458                         {
459                                 string cvar_name = sprintf("g_random_loot_%s_probability",
460                                         it.m_canonical_spawnfunc);
461                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
462                                         cvar(cvar_name), 1);
463                         });
464                         return RandomSelection_chosen_string;
465                 }
466                 case RANDOM_ITEM_TYPE_POWERUP:
467                 {
468                         RandomSelection_Init();
469                         #define X(classname) \
470                                 RandomSelection_AddString( \
471                                         classname, \
472                                         cvar(sprintf("g_random_loot_%s_probability", classname)), \
473                                         1 \
474                                 )
475                         X("item_strength");
476                         X("item_shield");
477                         X("item_jetpack");
478                         X("item_fuel_regen");
479                         #undef X
480                         return RandomSelection_chosen_string;
481                 }
482         }
483         return "";
484 }
485
486 /// \brief Spawns a random loot item.
487 /// \param[in] position Position of the item.
488 /// \return No return.
489 void RandomItems_SpawnLootItem(vector position)
490 {
491         string class_name = RandomItems_GetRandomLootItemClassName();
492         if (class_name == "")
493         {
494                 return;
495         }
496         vector spread = '0 0 0';
497         spread.z = autocvar_g_random_loot_spread / 2;
498         spread += randomvec() * autocvar_g_random_loot_spread;
499         random_items_is_spawning = true;
500         if (!expr_evaluate(autocvar_g_overkill))
501         {
502                 Item_CreateLoot(class_name, position, spread,
503                         autocvar_g_random_loot_time);
504         }
505         else
506         {
507                 entity item = spawn();
508                 item.ok_item = true;
509                 item.classname = class_name;
510                 Item_InitializeLoot(item, class_name, position, spread,
511                         autocvar_g_random_loot_time);
512         }
513         random_items_is_spawning = false;
514 }
515
516 //============================= Hooks ========================================
517
518 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
519         autocvar_g_random_loot));
520
521 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
522 {
523         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
524 }
525
526 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
527 {
528         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
529 }
530
531 /// \brief Hook that is called when an item is about to spawn.
532 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
533 {
534         //PrintToChatAll("FilterItem");
535         if (!autocvar_g_random_items)
536         {
537                 return false;
538         }
539         if (random_items_is_spawning == true)
540         {
541                 return false;
542         }
543         entity item = M_ARGV(0, entity);
544         if (Item_IsLoot(item))
545         {
546                 return false;
547         }
548         if (RandomItems_ReplaceMapItem(item) == NULL)
549         {
550                 return false;
551         }
552         return true;
553 }
554
555 /// \brief Hook that is called after the player has touched an item.
556 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
557 {
558         //PrintToChatAll("ItemTouched");
559         if (!autocvar_g_random_items)
560         {
561                 return;
562         }
563         entity item = M_ARGV(0, entity);
564         if (Item_IsLoot(item))
565         {
566                 return;
567         }
568         entity new_item = RandomItems_ReplaceMapItem(item);
569         if (new_item == NULL)
570         {
571                 return;
572         }
573         Item_ScheduleRespawn(new_item);
574         delete(item);
575 }
576
577 /// \brief Hook which is called when the player dies.
578 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
579 {
580         //PrintToChatAll("PlayerDies");
581         if (!autocvar_g_random_loot)
582         {
583                 return;
584         }
585         entity victim = M_ARGV(2, entity);
586         vector loot_position = victim.origin + '0 0 32';
587         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
588                 autocvar_g_random_loot_max);
589         for (int item_index = 0; item_index < num_loot_items; ++item_index)
590         {
591                 RandomItems_SpawnLootItem(loot_position);
592         }
593 }