]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
Random items: Preserve team of the items.
[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                 random_items_is_spawning = false;
350                 if (new_item == NULL)
351                 {
352                         return NULL;
353                 }
354         }
355         else
356         {
357                 new_item = spawn();
358                 new_item.classname = strzone(new_classname);
359                 new_item.spawnfunc_checked = true;
360                 new_item.ok_item = true;
361                 Item_Initialize(new_item, new_classname);
362                 random_items_is_spawning = false;
363                 if (wasfreed(new_item))
364                 {
365                         return NULL;
366                 }
367                 setorigin(new_item, item.origin);
368         }
369         if (item.team)
370         {
371                 new_item.team = item.team;
372         }
373         return new_item;
374 }
375
376 /// \brief Returns a random classname of the instagib loot item.
377 /// \return Random classname of the instagib loot item.
378 string RandomItems_GetRandomInstagibLootItemClassName()
379 {
380         RandomSelection_Init();
381         #define X(classname) \
382                 RandomSelection_AddString( \
383                         classname, \
384                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
385                         1 \
386                 )
387         X("item_vaporizer_cells");
388         X("item_invisibility");
389         X("item_extralife");
390         X("item_speed");
391         #undef X
392         return RandomSelection_chosen_string;
393 }
394
395 /// \brief Returns a random classname of the overkill loot item.
396 /// \return Random classname of the overkill loot item.
397 string RandomItems_GetRandomOverkillLootItemClassName()
398 {
399         RandomSelection_Init();
400         string varname;
401         #define X(classname) MACRO_BEGIN \
402                 if ((varname = RandomItems_GetItemVarName(classname))) \
403                 { \
404                         RandomSelection_AddString( \
405                                 classname, \
406                                 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
407                                 1 \
408                         ); \
409                 } \
410         MACRO_END
411         X("item_health_mega");
412         X("item_armor_small");
413         X("item_armor_medium");
414         X("item_armor_big");
415         X("item_armor_mega");
416         X("weapon_hmg");
417         X("weapon_rpc");
418         #undef X
419         return RandomSelection_chosen_string;
420 }
421
422 /// \brief Returns a random classname of the loot item.
423 /// \return Random classname of the loot item.
424 string RandomItems_GetRandomLootItemClassName()
425 {
426         if (autocvar_g_instagib)
427         {
428                 return RandomItems_GetRandomInstagibLootItemClassName();
429         }
430         if (expr_evaluate(autocvar_g_overkill))
431         {
432                 return RandomItems_GetRandomOverkillLootItemClassName();
433         }
434         RandomSelection_Init();
435         #define X(type, name) \
436                 RandomSelection_AddFloat( \
437                         RANDOM_ITEM_TYPE_##type, \
438                         autocvar_g_random_loot_##name##_probability, \
439                         1 \
440                 )
441         X(HEALTH, health);
442         X(ARMOR, armor);
443         X(RESOURCE, resource);
444         X(WEAPON, weapon);
445         X(POWERUP, powerup);
446         #undef X
447         int item_type = RandomSelection_chosen_float;
448         switch (item_type)
449         {
450                 case RANDOM_ITEM_TYPE_HEALTH:
451                 {
452                         RandomSelection_Init();
453                         #define X(classname) \
454                                 RandomSelection_AddString( \
455                                         classname, \
456                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
457                                         1 \
458                                 )
459                         FOREACH(Items, it.instanceOfHealth, {
460                                 X(sprintf("item_%s", it.netname));
461                         });
462                         #undef X
463                         return RandomSelection_chosen_string;
464                 }
465                 case RANDOM_ITEM_TYPE_ARMOR:
466                 {
467                         RandomSelection_Init();
468                         #define X(classname) \
469                                 RandomSelection_AddString( \
470                                         classname, \
471                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
472                                         1 \
473                                 )
474                         FOREACH(Items, it.instanceOfArmor, {
475                                 X(sprintf("item_%s", it.netname));
476                         });
477                         #undef X
478                         return RandomSelection_chosen_string;
479                 }
480                 case RANDOM_ITEM_TYPE_RESOURCE:
481                 {
482                         RandomSelection_Init();
483                         #define X(classname) \
484                                 RandomSelection_AddString( \
485                                         classname, \
486                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
487                                         1 \
488                                 )
489                         FOREACH(Items, it.instanceOfAmmo, {
490                                 X(sprintf("item_%s", it.netname));
491                         });
492                         #undef X
493                         return RandomSelection_chosen_string;
494                 }
495                 case RANDOM_ITEM_TYPE_WEAPON:
496                 {
497                         RandomSelection_Init();
498                         FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
499                         {
500                                 string class_name = strcat("weapon_", it.netname);
501                                 string cvar_name = sprintf(
502                                         "g_random_loot_%s_probability", class_name);
503                                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
504                                 {
505                                         continue;
506                                 }
507                                 RandomSelection_AddString(class_name, cvar(cvar_name), 1);
508                         });
509                         return RandomSelection_chosen_string;
510                 }
511                 case RANDOM_ITEM_TYPE_POWERUP:
512                 {
513                         RandomSelection_Init();
514                         #define X(classname) \
515                                 RandomSelection_AddString( \
516                                         classname, \
517                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
518                                         1 \
519                                 )
520                         X("item_strength");
521                         X("item_invincible");
522                         X("item_jetpack");
523                         X("item_fuel_regen");
524                         #undef X
525                         return RandomSelection_chosen_string;
526                 }
527         }
528         return "";
529 }
530
531 /// \brief Spawns a random loot item.
532 /// \param[in] position Position of the item.
533 /// \return No return.
534 void RandomItems_SpawnLootItem(vector position)
535 {
536         string class_name = RandomItems_GetRandomLootItemClassName();
537         if (class_name == "")
538         {
539                 return;
540         }
541         vector spread = '0 0 0';
542         spread.z = autocvar_g_random_loot_spread / 2;
543         spread += randomvec() * autocvar_g_random_loot_spread;
544         random_items_is_spawning = true;
545         if (!expr_evaluate(autocvar_g_overkill))
546         {
547                 Item_CreateLoot(class_name, position, spread,
548                         autocvar_g_random_loot_time);
549         }
550         else
551         {
552                 entity item = spawn();
553                 item.ok_item = true;
554                 item.classname = class_name;
555                 Item_InitializeLoot(item, class_name, position, spread,
556                         autocvar_g_random_loot_time);
557         }
558         random_items_is_spawning = false;
559 }
560
561 //============================= Hooks ========================================
562
563 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
564         autocvar_g_random_loot));
565
566 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
567 {
568         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
569 }
570
571 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
572 {
573         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
574 }
575
576 /// \brief Hook that is called when an item is about to spawn.
577 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
578 {
579         //PrintToChatAll("FilterItem");
580         if (!autocvar_g_random_items)
581         {
582                 return false;
583         }
584         if (random_items_is_spawning == true)
585         {
586                 return false;
587         }
588         entity item = M_ARGV(0, entity);
589         if (Item_IsLoot(item))
590         {
591                 return false;
592         }
593         if (RandomItems_ReplaceMapItem(item) == NULL)
594         {
595                 return false;
596         }
597         return true;
598 }
599
600 /// \brief Hook that is called after the player has touched an item.
601 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
602 {
603         //PrintToChatAll("ItemTouched");
604         if (!autocvar_g_random_items)
605         {
606                 return;
607         }
608         entity item = M_ARGV(0, entity);
609         if (Item_IsLoot(item))
610         {
611                 return;
612         }
613         entity new_item = RandomItems_ReplaceMapItem(item);
614         if (new_item == NULL)
615         {
616                 return;
617         }
618         Item_ScheduleRespawn(new_item);
619         delete(item);
620 }
621
622 /// \brief Hook which is called when the player dies.
623 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
624 {
625         //PrintToChatAll("PlayerDies");
626         if (!autocvar_g_random_loot)
627         {
628                 return;
629         }
630         entity victim = M_ARGV(2, entity);
631         vector loot_position = victim.origin + '0 0 32';
632         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
633                 autocvar_g_random_loot_max);
634         for (int item_index = 0; item_index < num_loot_items; ++item_index)
635         {
636                 RandomItems_SpawnLootItem(loot_position);
637         }
638 }