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