]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
sv_random_items.qc: start weight loss program ;)
[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         bool is_ok = expr_evaluate(autocvar_g_overkill);
83         switch (class_name)
84         {
85                 #define X(classname, var) case #classname: return #var
86                 #define XCOND(classname, var, expr) case #classname: if (expr) return #var; else break
87                 X(item_health_small, health_small);
88                 X(item_health_medium, health_medium);
89                 X(item_health_big, health_big);
90                 XCOND(item_health_mega, health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
91
92                 X(item_armor_small, armor_small);
93                 XCOND(item_armor_medium, armor_medium, !is_ok || !autocvar_g_overkill_filter_armormedium);
94                 XCOND(item_armor_big, armor_big, !is_ok || !autocvar_g_overkill_filter_armorbig);
95                 XCOND(item_armor_mega, armor_mega, !is_ok || !autocvar_g_overkill_filter_armormega);
96
97                 X(item_shells, resource_shells);
98                 X(item_bullets, resource_bullets);
99                 X(item_rockets, resource_rockets);
100                 X(item_cells, resource_cells);
101                 X(item_plasma, resource_plasma);
102                 X(item_fuel, resource_fuel);
103
104                 X(weapon_blaster, weapon_blaster);
105                 X(weapon_shotgun, weapon_shotgun);
106                 X(weapon_machinegun, weapon_machinegun);
107                 X(weapon_mortar, weapon_mortar);
108                 X(weapon_electro, weapon_electro);
109                 X(weapon_crylink, weapon_crylink);
110                 X(weapon_vortex, weapon_vortex);
111                 X(weapon_hagar, weapon_hagar);
112                 X(weapon_devastator, weapon_devastator);
113                 X(weapon_shockwave, weapon_shockwave);
114                 X(weapon_arc, weapon_arc);
115                 X(weapon_hook, weapon_hook);
116                 X(weapon_tuba, weapon_tuba);
117                 X(weapon_porto, weapon_porto);
118                 X(weapon_fireball, weapon_fireball);
119                 X(weapon_minelayer, weapon_minelayer);
120                 X(weapon_hlac, weapon_hlac);
121                 X(weapon_rifle, weapon_rifle);
122                 X(weapon_seeker, weapon_seeker);
123                 X(weapon_vaporizer, weapon_vaporizer);
124
125                 X(item_strength, strength);
126                 X(item_invincible, shield);
127                 X(item_fuel_regen, fuel_regen);
128                 X(item_jetpack, jetpack);
129
130                 X(item_vaporizer_cells, vaporizer_cells);
131                 X(item_invisibility, invisibility);
132                 X(item_extralife, extralife);
133                 X(item_speed, speed);
134
135                 XCOND(weapon_hmg, weapon_hmg, is_ok);
136                 XCOND(weapon_rpc, weapon_rpc, is_ok);
137                 #undef X
138                 #undef XCOND
139         }
140         return "";
141 }
142
143 /// \brief Returns list of classnames to replace a map item with.
144 /// \param[in] item Item to inspect.
145 /// \return List of classnames to replace a map item with.
146 string RandomItems_GetItemReplacementClassNames(entity item)
147 {
148         string class_name = RandomItems_GetItemVarName(item.classname);
149         if (class_name)
150         {
151                 return cvar_string(sprintf("g_random_items_replace_%s", class_name));
152         }
153         if (item.classname == "replacedweapon")
154         {
155                 Weapon w = Weapons_from(item.weapon);
156                 if (w != WEP_Null)
157                 {
158                         return cvar_string(sprintf("g_random_items_replace_weapon_%s", w.netname));
159                 }
160         }
161         return "";
162 }
163
164 /// \brief Returns a random classname of the instagib map item.
165 /// \return Random classname of the instagib map item.
166 string RandomItems_GetRandomInstagibMapItemClassName()
167 {
168         RandomSelection_Init();
169         #define X(classname) \
170                 RandomSelection_AddString( \
171                         classname, \
172                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
173                         1 \
174                 )
175         X("item_vaporizer_cells");
176         X("item_invisibility");
177         X("item_extralife");
178         X("item_speed");
179         #undef X
180         return RandomSelection_chosen_string;
181 }
182
183 /// \brief Returns a random classname of the overkill map item.
184 /// \return Random classname of the overkill map item.
185 string RandomItems_GetRandomOverkillMapItemClassName()
186 {
187         RandomSelection_Init();
188         string varname;
189         #define X(classname) MACRO_BEGIN \
190                 if ((varname = RandomItems_GetItemVarName(classname))) \
191                 { \
192                         RandomSelection_AddString( \
193                                 classname, \
194                                 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
195                                 1 \
196                         ); \
197                 } \
198         MACRO_END
199         X("item_health_mega");
200         X("item_armor_small");
201         X("item_armor_medium");
202         X("item_armor_big");
203         X("item_armor_mega");
204         X("weapon_hmg");
205         X("weapon_rpc");
206         #undef X
207         return RandomSelection_chosen_string;
208 }
209
210 /// \brief Returns a random classname of the map item.
211 /// \return Random classname of the map item.
212 string RandomItems_GetRandomMapItemClassName()
213 {
214         if (autocvar_g_instagib)
215         {
216                 return RandomItems_GetRandomInstagibMapItemClassName();
217         }
218         if (expr_evaluate(autocvar_g_overkill))
219         {
220                 return RandomItems_GetRandomOverkillMapItemClassName();
221         }
222         RandomSelection_Init();
223         #define X(type, name) \
224                 RandomSelection_AddFloat( \
225                         RANDOM_ITEM_TYPE_##type, \
226                         autocvar_g_random_items_##name##_probability, \
227                         1 \
228                 )
229         X(HEALTH, health);
230         X(ARMOR, armor);
231         X(RESOURCE, resource);
232         X(WEAPON, weapon);
233         X(POWERUP, powerup);
234         #undef X
235         int item_type = RandomSelection_chosen_float;
236         switch (item_type)
237         {
238                 case RANDOM_ITEM_TYPE_HEALTH:
239                 {
240                         RandomSelection_Init();
241                         #define X(classname) \
242                                 RandomSelection_AddString( \
243                                         classname, \
244                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
245                                         1 \
246                                 )
247                         X("item_health_small");
248                         X("item_health_medium");
249                         X("item_health_big");
250                         X("item_health_mega");
251                         #undef X
252                         return RandomSelection_chosen_string;
253                 }
254                 case RANDOM_ITEM_TYPE_ARMOR:
255                 {
256                         RandomSelection_Init();
257                         #define X(classname) \
258                                 RandomSelection_AddString( \
259                                         classname, \
260                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
261                                         1 \
262                                 )
263                         X("item_health_small");
264                         X("item_health_medium");
265                         X("item_health_big");
266                         X("item_health_mega");
267                         #undef X
268                         return RandomSelection_chosen_string;
269                 }
270                 case RANDOM_ITEM_TYPE_RESOURCE:
271                 {
272                         RandomSelection_Init();
273                         #define X(classname) \
274                                 RandomSelection_AddString( \
275                                         classname, \
276                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
277                                         1 \
278                                 )
279                         X("item_shells");
280                         X("item_bullets");
281                         X("item_rockets");
282                         X("item_cells");
283                         X("item_plasma");
284                         X("item_fuel");
285                         #undef X
286                         return RandomSelection_chosen_string;
287                 }
288                 case RANDOM_ITEM_TYPE_WEAPON:
289                 {
290                         RandomSelection_Init();
291                         #define X(classname) \
292                                 RandomSelection_AddString( \
293                                         classname, \
294                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
295                                         1 \
296                                 )
297                         X("weapon_blaster");
298                         X("weapon_shotgun");
299                         X("weapon_machinegun");
300                         X("weapon_mortar");
301                         X("weapon_electro");
302                         X("weapon_crylink");
303                         X("weapon_vortex");
304                         X("weapon_hagar");
305                         X("weapon_devastator");
306                         X("weapon_shockwave");
307                         X("weapon_arc");
308                         X("weapon_hook");
309                         X("weapon_tuba");
310                         X("weapon_porto");
311                         X("weapon_fireball");
312                         X("weapon_minelayer");
313                         X("weapon_hlac");
314                         X("weapon_rifle");
315                         X("weapon_seeker");
316                         X("weapon_vaporizer");
317                         #undef X
318                         return RandomSelection_chosen_string;
319                 }
320                 case RANDOM_ITEM_TYPE_POWERUP:
321                 {
322                         RandomSelection_Init();
323                         #define X(classname) \
324                                 RandomSelection_AddString( \
325                                         classname, \
326                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
327                                         1 \
328                                 )
329                         X("item_strength");
330                         X("item_invincible");
331                         X("item_fuel_regen");
332                         X("item_jetpack");
333                         #undef X
334                         return RandomSelection_chosen_string;
335                 }
336         }
337         return "";
338 }
339
340 /// \brief Replaces a map item.
341 /// \param[in] item Item to replace.
342 /// \return Spawned item on success, NULL otherwise.
343 entity RandomItems_ReplaceMapItem(entity item)
344 {
345         //PrintToChatAll(strcat("Replacing ", item.classname));
346         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
347         if (new_classnames == "")
348         {
349                 return NULL;
350         }
351         string new_classname;
352         if (new_classnames == "random")
353         {
354                 new_classname = RandomItems_GetRandomMapItemClassName();
355                 if (new_classname == "")
356                 {
357                         return NULL;
358                 }
359         }
360         else
361         {
362                 int num_new_classnames = tokenize_console(new_classnames);
363                 if (num_new_classnames == 1)
364                 {
365                         new_classname = new_classnames;
366                 }
367                 else
368                 {
369                         int classname_index = floor(random() * num_new_classnames);
370                         new_classname = argv(classname_index);
371                 }
372         }
373         //PrintToChatAll(strcat("Replacing with ", new_classname));
374         if (new_classname == item.classname)
375         {
376                 return NULL;
377         }
378         random_items_is_spawning = true;
379         entity new_item;
380         if (!expr_evaluate(autocvar_g_overkill))
381         {
382                 new_item = Item_Create(strzone(new_classname), item.origin);
383         }
384         else
385         {
386                 new_item = spawn();
387                 new_item.classname = strzone(new_classname);
388                 new_item.spawnfunc_checked = true;
389                 new_item.ok_item = true;
390                 Item_Initialize(new_item, new_classname);
391                 if (wasfreed(new_item))
392                 {
393                         return NULL;
394                 }
395                 setorigin(new_item, item.origin);
396         }
397         random_items_is_spawning = false;
398         return new_item;
399 }
400
401 /// \brief Returns a random classname of the instagib loot item.
402 /// \return Random classname of the instagib loot item.
403 string RandomItems_GetRandomInstagibLootItemClassName()
404 {
405         RandomSelection_Init();
406         #define X(classname) \
407                 RandomSelection_AddString( \
408                         classname, \
409                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
410                         1 \
411                 )
412         X("item_vaporizer_cells");
413         X("item_invisibility");
414         X("item_extralife");
415         X("item_speed");
416         #undef X
417         return RandomSelection_chosen_string;
418 }
419
420 /// \brief Returns a random classname of the overkill loot item.
421 /// \return Random classname of the overkill loot item.
422 string RandomItems_GetRandomOverkillLootItemClassName()
423 {
424         RandomSelection_Init();
425         string varname;
426         #define X(classname) MACRO_BEGIN \
427                 if ((varname = RandomItems_GetItemVarName(classname))) \
428                 { \
429                         RandomSelection_AddString( \
430                                 classname, \
431                                 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
432                                 1 \
433                         ); \
434                 } \
435         MACRO_END
436         X("item_health_mega");
437         X("item_armor_small");
438         X("item_armor_medium");
439         X("item_armor_big");
440         X("item_armor_mega");
441         X("weapon_hmg");
442         X("weapon_rpc");
443         #undef X
444         return RandomSelection_chosen_string;
445 }
446
447 /// \brief Returns a random classname of the loot item.
448 /// \return Random classname of the loot item.
449 string RandomItems_GetRandomLootItemClassName()
450 {
451         if (autocvar_g_instagib)
452         {
453                 return RandomItems_GetRandomInstagibLootItemClassName();
454         }
455         if (expr_evaluate(autocvar_g_overkill))
456         {
457                 return RandomItems_GetRandomOverkillLootItemClassName();
458         }
459         RandomSelection_Init();
460         #define X(type, name) \
461                 RandomSelection_AddFloat( \
462                         RANDOM_ITEM_TYPE_##type, \
463                         autocvar_g_random_loot_##name##_probability, \
464                         1 \
465                 )
466         X(HEALTH, health);
467         X(ARMOR, armor);
468         X(RESOURCE, resource);
469         X(WEAPON, weapon);
470         X(POWERUP, powerup);
471         #undef X
472         int item_type = RandomSelection_chosen_float;
473         switch (item_type)
474         {
475                 case RANDOM_ITEM_TYPE_HEALTH:
476                 {
477                         RandomSelection_Init();
478                         #define X(classname) \
479                                 RandomSelection_AddString( \
480                                         classname, \
481                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
482                                         1 \
483                                 )
484                         X("item_health_small");
485                         X("item_health_medium");
486                         X("item_health_big");
487                         X("item_health_mega");
488                         #undef X
489                         return RandomSelection_chosen_string;
490                 }
491                 case RANDOM_ITEM_TYPE_ARMOR:
492                 {
493                         RandomSelection_Init();
494                         #define X(classname) \
495                                 RandomSelection_AddString( \
496                                         classname, \
497                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
498                                         1 \
499                                 )
500                         X("item_armor_small");
501                         X("item_armor_medium");
502                         X("item_armor_big");
503                         X("item_armor_mega");
504                         #undef X
505                         return RandomSelection_chosen_string;
506                 }
507                 case RANDOM_ITEM_TYPE_RESOURCE:
508                 {
509                         RandomSelection_Init();
510                         #define X(classname) \
511                                 RandomSelection_AddString( \
512                                         classname, \
513                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
514                                         1 \
515                                 )
516                         X("item_shells");
517                         X("item_bullets");
518                         X("item_rockets");
519                         X("item_cells");
520                         X("item_plasma");
521                         X("item_fuel");
522                         #undef X
523                         return RandomSelection_chosen_string;
524                 }
525                 case RANDOM_ITEM_TYPE_WEAPON:
526                 {
527                         RandomSelection_Init();
528                         #define X(classname) \
529                                 RandomSelection_AddString( \
530                                         classname, \
531                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
532                                         1 \
533                                 )
534                         X("weapon_blaster");
535                         X("weapon_shotgun");
536                         X("weapon_machinegun");
537                         X("weapon_mortar");
538                         X("weapon_electro");
539                         X("weapon_crylink");
540                         X("weapon_vortex");
541                         X("weapon_hagar");
542                         X("weapon_devastator");
543                         X("weapon_shockwave");
544                         X("weapon_arc");
545                         X("weapon_hook");
546                         X("weapon_tuba");
547                         X("weapon_porto");
548                         X("weapon_fireball");
549                         X("weapon_minelayer");
550                         X("weapon_hlac");
551                         X("weapon_rifle");
552                         X("weapon_seeker");
553                         X("weapon_vaporizer");
554                         #undef X
555                         return RandomSelection_chosen_string;
556                 }
557                 case RANDOM_ITEM_TYPE_POWERUP:
558                 {
559                         RandomSelection_Init();
560                         #define X(classname) \
561                                 RandomSelection_AddString( \
562                                         classname, \
563                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
564                                         1 \
565                                 )
566                         X("item_strength");
567                         X("item_invincible");
568                         X("item_jetpack");
569                         X("item_fuel_regen");
570                         #undef X
571                         return RandomSelection_chosen_string;
572                 }
573         }
574         return "";
575 }
576
577 /// \brief Spawns a random loot item.
578 /// \param[in] position Position of the item.
579 /// \return No return.
580 void RandomItems_SpawnLootItem(vector position)
581 {
582         string class_name = RandomItems_GetRandomLootItemClassName();
583         if (class_name == "")
584         {
585                 return;
586         }
587         vector spread = '0 0 0';
588         spread.z = autocvar_g_random_loot_spread / 2;
589         spread += randomvec() * autocvar_g_random_loot_spread;
590         random_items_is_spawning = true;
591         if (!expr_evaluate(autocvar_g_overkill))
592         {
593                 Item_CreateLoot(class_name, position, spread,
594                         autocvar_g_random_loot_time);
595         }
596         else
597         {
598                 entity item = spawn();
599                 item.ok_item = true;
600                 item.classname = class_name;
601                 Item_InitializeLoot(item, class_name, position, spread,
602                         autocvar_g_random_loot_time);
603         }
604         random_items_is_spawning = false;
605 }
606
607 //============================= Hooks ========================================
608
609 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
610         autocvar_g_random_loot));
611
612 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
613 {
614         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
615 }
616
617 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
618 {
619         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
620 }
621
622 /// \brief Hook that is called when an item is about to spawn.
623 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
624 {
625         //PrintToChatAll("FilterItem");
626         if (!autocvar_g_random_items)
627         {
628                 return false;
629         }
630         if (random_items_is_spawning == true)
631         {
632                 return false;
633         }
634         entity item = M_ARGV(0, entity);
635         if (Item_IsLoot(item))
636         {
637                 return false;
638         }
639         if (RandomItems_ReplaceMapItem(item) == NULL)
640         {
641                 return false;
642         }
643         return true;
644 }
645
646 /// \brief Hook that is called after the player has touched an item.
647 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
648 {
649         //PrintToChatAll("ItemTouched");
650         if (!autocvar_g_random_items)
651         {
652                 return;
653         }
654         entity item = M_ARGV(0, entity);
655         if (Item_IsLoot(item))
656         {
657                 return;
658         }
659         entity new_item = RandomItems_ReplaceMapItem(item);
660         if (new_item == NULL)
661         {
662                 return;
663         }
664         Item_ScheduleRespawn(new_item);
665         delete(item);
666 }
667
668 /// \brief Hook which is called when the player dies.
669 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
670 {
671         //PrintToChatAll("PlayerDies");
672         if (!autocvar_g_random_loot)
673         {
674                 return;
675         }
676         entity victim = M_ARGV(2, entity);
677         vector loot_position = victim.origin + '0 0 32';
678         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
679                 autocvar_g_random_loot_max);
680         for (int item_index = 0; item_index < num_loot_items; ++item_index)
681         {
682                 RandomItems_SpawnLootItem(loot_position);
683         }
684 }