]> 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: more diet
[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, health_small);
98                 X(item_health_medium, health_medium);
99                 X(item_health_big, health_big);
100                 XCOND(item_health_mega, health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
101
102                 X(item_armor_small, armor_small);
103                 XCOND(item_armor_medium, armor_medium, !is_ok || !autocvar_g_overkill_filter_armormedium);
104                 XCOND(item_armor_big, armor_big, !is_ok || !autocvar_g_overkill_filter_armorbig);
105                 XCOND(item_armor_mega, armor_mega, !is_ok || !autocvar_g_overkill_filter_armormega);
106
107                 X(item_shells, resource_shells);
108                 X(item_bullets, resource_bullets);
109                 X(item_rockets, resource_rockets);
110                 X(item_cells, resource_cells);
111                 X(item_plasma, resource_plasma);
112                 X(item_fuel, resource_fuel);
113
114                 X(item_strength, strength);
115                 X(item_invincible, shield);
116                 X(item_fuel_regen, fuel_regen);
117                 X(item_jetpack, jetpack);
118
119                 X(item_vaporizer_cells, vaporizer_cells);
120                 X(item_invisibility, invisibility);
121                 X(item_extralife, extralife);
122                 X(item_speed, 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                         #define X(classname) \
274                                 RandomSelection_AddString( \
275                                         classname, \
276                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
277                                         1 \
278                                 )
279                         FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), {
280                                 X(sprintf("weapon_%s", it.netname));
281                         });
282                         #undef X
283                         return RandomSelection_chosen_string;
284                 }
285                 case RANDOM_ITEM_TYPE_POWERUP:
286                 {
287                         RandomSelection_Init();
288                         #define X(classname) \
289                                 RandomSelection_AddString( \
290                                         classname, \
291                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
292                                         1 \
293                                 )
294                         X("item_strength");
295                         X("item_invincible");
296                         X("item_fuel_regen");
297                         X("item_jetpack");
298                         #undef X
299                         return RandomSelection_chosen_string;
300                 }
301         }
302         return "";
303 }
304
305 /// \brief Replaces a map item.
306 /// \param[in] item Item to replace.
307 /// \return Spawned item on success, NULL otherwise.
308 entity RandomItems_ReplaceMapItem(entity item)
309 {
310         //PrintToChatAll(strcat("Replacing ", item.classname));
311         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
312         if (new_classnames == "")
313         {
314                 return NULL;
315         }
316         string new_classname;
317         if (new_classnames == "random")
318         {
319                 new_classname = RandomItems_GetRandomMapItemClassName();
320                 if (new_classname == "")
321                 {
322                         return NULL;
323                 }
324         }
325         else
326         {
327                 int num_new_classnames = tokenize_console(new_classnames);
328                 if (num_new_classnames == 1)
329                 {
330                         new_classname = new_classnames;
331                 }
332                 else
333                 {
334                         int classname_index = floor(random() * num_new_classnames);
335                         new_classname = argv(classname_index);
336                 }
337         }
338         //PrintToChatAll(strcat("Replacing with ", new_classname));
339         if (new_classname == item.classname)
340         {
341                 return NULL;
342         }
343         random_items_is_spawning = true;
344         entity new_item;
345         if (!expr_evaluate(autocvar_g_overkill))
346         {
347                 new_item = Item_Create(strzone(new_classname), item.origin);
348         }
349         else
350         {
351                 new_item = spawn();
352                 new_item.classname = strzone(new_classname);
353                 new_item.spawnfunc_checked = true;
354                 new_item.ok_item = true;
355                 Item_Initialize(new_item, new_classname);
356                 if (wasfreed(new_item))
357                 {
358                         return NULL;
359                 }
360                 setorigin(new_item, item.origin);
361         }
362         random_items_is_spawning = false;
363         return new_item;
364 }
365
366 /// \brief Returns a random classname of the instagib loot item.
367 /// \return Random classname of the instagib loot item.
368 string RandomItems_GetRandomInstagibLootItemClassName()
369 {
370         RandomSelection_Init();
371         #define X(classname) \
372                 RandomSelection_AddString( \
373                         classname, \
374                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
375                         1 \
376                 )
377         X("item_vaporizer_cells");
378         X("item_invisibility");
379         X("item_extralife");
380         X("item_speed");
381         #undef X
382         return RandomSelection_chosen_string;
383 }
384
385 /// \brief Returns a random classname of the overkill loot item.
386 /// \return Random classname of the overkill loot item.
387 string RandomItems_GetRandomOverkillLootItemClassName()
388 {
389         RandomSelection_Init();
390         string varname;
391         #define X(classname) MACRO_BEGIN \
392                 if ((varname = RandomItems_GetItemVarName(classname))) \
393                 { \
394                         RandomSelection_AddString( \
395                                 classname, \
396                                 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
397                                 1 \
398                         ); \
399                 } \
400         MACRO_END
401         X("item_health_mega");
402         X("item_armor_small");
403         X("item_armor_medium");
404         X("item_armor_big");
405         X("item_armor_mega");
406         X("weapon_hmg");
407         X("weapon_rpc");
408         #undef X
409         return RandomSelection_chosen_string;
410 }
411
412 /// \brief Returns a random classname of the loot item.
413 /// \return Random classname of the loot item.
414 string RandomItems_GetRandomLootItemClassName()
415 {
416         if (autocvar_g_instagib)
417         {
418                 return RandomItems_GetRandomInstagibLootItemClassName();
419         }
420         if (expr_evaluate(autocvar_g_overkill))
421         {
422                 return RandomItems_GetRandomOverkillLootItemClassName();
423         }
424         RandomSelection_Init();
425         #define X(type, name) \
426                 RandomSelection_AddFloat( \
427                         RANDOM_ITEM_TYPE_##type, \
428                         autocvar_g_random_loot_##name##_probability, \
429                         1 \
430                 )
431         X(HEALTH, health);
432         X(ARMOR, armor);
433         X(RESOURCE, resource);
434         X(WEAPON, weapon);
435         X(POWERUP, powerup);
436         #undef X
437         int item_type = RandomSelection_chosen_float;
438         switch (item_type)
439         {
440                 case RANDOM_ITEM_TYPE_HEALTH:
441                 {
442                         RandomSelection_Init();
443                         #define X(classname) \
444                                 RandomSelection_AddString( \
445                                         classname, \
446                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
447                                         1 \
448                                 )
449                         FOREACH(Items, it.instanceOfHealth, {
450                                 X(sprintf("item_%s", it.netname));
451                         });
452                         #undef X
453                         return RandomSelection_chosen_string;
454                 }
455                 case RANDOM_ITEM_TYPE_ARMOR:
456                 {
457                         RandomSelection_Init();
458                         #define X(classname) \
459                                 RandomSelection_AddString( \
460                                         classname, \
461                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
462                                         1 \
463                                 )
464                         FOREACH(Items, it.instanceOfArmor, {
465                                 X(sprintf("item_%s", it.netname));
466                         });
467                         #undef X
468                         return RandomSelection_chosen_string;
469                 }
470                 case RANDOM_ITEM_TYPE_RESOURCE:
471                 {
472                         RandomSelection_Init();
473                         #define X(classname) \
474                                 RandomSelection_AddString( \
475                                         classname, \
476                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
477                                         1 \
478                                 )
479                         FOREACH(Items, it.instanceOfAmmo, {
480                                 X(sprintf("item_%s", it.netname));
481                         });
482                         #undef X
483                         return RandomSelection_chosen_string;
484                 }
485                 case RANDOM_ITEM_TYPE_WEAPON:
486                 {
487                         RandomSelection_Init();
488                         #define X(classname) \
489                                 RandomSelection_AddString( \
490                                         classname, \
491                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
492                                         1 \
493                                 )
494                         FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), {
495                                 X(sprintf("weapon_%s", it.netname));
496                         });
497                         #undef X
498                         return RandomSelection_chosen_string;
499                 }
500                 case RANDOM_ITEM_TYPE_POWERUP:
501                 {
502                         RandomSelection_Init();
503                         #define X(classname) \
504                                 RandomSelection_AddString( \
505                                         classname, \
506                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
507                                         1 \
508                                 )
509                         X("item_strength");
510                         X("item_invincible");
511                         X("item_jetpack");
512                         X("item_fuel_regen");
513                         #undef X
514                         return RandomSelection_chosen_string;
515                 }
516         }
517         return "";
518 }
519
520 /// \brief Spawns a random loot item.
521 /// \param[in] position Position of the item.
522 /// \return No return.
523 void RandomItems_SpawnLootItem(vector position)
524 {
525         string class_name = RandomItems_GetRandomLootItemClassName();
526         if (class_name == "")
527         {
528                 return;
529         }
530         vector spread = '0 0 0';
531         spread.z = autocvar_g_random_loot_spread / 2;
532         spread += randomvec() * autocvar_g_random_loot_spread;
533         random_items_is_spawning = true;
534         if (!expr_evaluate(autocvar_g_overkill))
535         {
536                 Item_CreateLoot(class_name, position, spread,
537                         autocvar_g_random_loot_time);
538         }
539         else
540         {
541                 entity item = spawn();
542                 item.ok_item = true;
543                 item.classname = class_name;
544                 Item_InitializeLoot(item, class_name, position, spread,
545                         autocvar_g_random_loot_time);
546         }
547         random_items_is_spawning = false;
548 }
549
550 //============================= Hooks ========================================
551
552 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
553         autocvar_g_random_loot));
554
555 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
556 {
557         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
558 }
559
560 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
561 {
562         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
563 }
564
565 /// \brief Hook that is called when an item is about to spawn.
566 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
567 {
568         //PrintToChatAll("FilterItem");
569         if (!autocvar_g_random_items)
570         {
571                 return false;
572         }
573         if (random_items_is_spawning == true)
574         {
575                 return false;
576         }
577         entity item = M_ARGV(0, entity);
578         if (Item_IsLoot(item))
579         {
580                 return false;
581         }
582         if (RandomItems_ReplaceMapItem(item) == NULL)
583         {
584                 return false;
585         }
586         return true;
587 }
588
589 /// \brief Hook that is called after the player has touched an item.
590 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
591 {
592         //PrintToChatAll("ItemTouched");
593         if (!autocvar_g_random_items)
594         {
595                 return;
596         }
597         entity item = M_ARGV(0, entity);
598         if (Item_IsLoot(item))
599         {
600                 return;
601         }
602         entity new_item = RandomItems_ReplaceMapItem(item);
603         if (new_item == NULL)
604         {
605                 return;
606         }
607         Item_ScheduleRespawn(new_item);
608         delete(item);
609 }
610
611 /// \brief Hook which is called when the player dies.
612 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
613 {
614         //PrintToChatAll("PlayerDies");
615         if (!autocvar_g_random_loot)
616         {
617                 return;
618         }
619         entity victim = M_ARGV(2, entity);
620         vector loot_position = victim.origin + '0 0 32';
621         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
622                 autocvar_g_random_loot_max);
623         for (int item_index = 0; item_index < num_loot_items; ++item_index)
624         {
625                 RandomItems_SpawnLootItem(loot_position);
626         }
627 }