]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
Renamed item_invincible to item_shield.
[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, it.m_canonical_spawnfunc == 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) case #classname: return #classname
96                 #define XCOND(classname, var, expr) case #classname: if (expr) return #var; else break
97                 X(item_health_small);
98                 X(item_health_medium);
99                 X(item_health_big);
100                 XCOND(item_health_mega, item_health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
101
102                 X(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);
108                 X(item_bullets);
109                 X(item_rockets);
110                 X(item_cells);
111                 X(item_plasma);
112                 X(item_fuel);
113
114                 X(item_strength);
115                 X(item_shield);
116                 X(item_fuel_regen);
117                 X(item_jetpack);
118
119                 X(item_vaporizer_cells);
120                 X(item_invisibility);
121                 X(item_extralife);
122                 X(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         return "";
141 }
142
143 /// \brief Returns a random classname of the instagib map item.
144 /// \return Random classname of the instagib map item.
145 string RandomItems_GetRandomInstagibMapItemClassName()
146 {
147         RandomSelection_Init();
148         #define X(classname) \
149                 RandomSelection_AddString( \
150                         classname, \
151                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
152                         1 \
153                 )
154         X("item_vaporizer_cells");
155         X("item_invisibility");
156         X("item_extralife");
157         X("item_speed");
158         #undef X
159         return RandomSelection_chosen_string;
160 }
161
162 /// \brief Returns a random classname of the overkill map item.
163 /// \return Random classname of the overkill map item.
164 string RandomItems_GetRandomOverkillMapItemClassName()
165 {
166         RandomSelection_Init();
167         string varname;
168         #define X(classname) MACRO_BEGIN \
169                 if ((varname = RandomItems_GetItemVarName(classname))) \
170                 { \
171                         RandomSelection_AddString( \
172                                 classname, \
173                                 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
174                                 1 \
175                         ); \
176                 } \
177         MACRO_END
178         X("item_health_mega");
179         X("item_armor_small");
180         X("item_armor_medium");
181         X("item_armor_big");
182         X("item_armor_mega");
183         X("weapon_hmg");
184         X("weapon_rpc");
185         #undef X
186         return RandomSelection_chosen_string;
187 }
188
189 /// \brief Returns a random classname of the map item.
190 /// \return Random classname of the map item.
191 string RandomItems_GetRandomMapItemClassName()
192 {
193         if (autocvar_g_instagib)
194         {
195                 return RandomItems_GetRandomInstagibMapItemClassName();
196         }
197         if (expr_evaluate(autocvar_g_overkill))
198         {
199                 return RandomItems_GetRandomOverkillMapItemClassName();
200         }
201         RandomSelection_Init();
202         #define X(type, name) \
203                 RandomSelection_AddFloat( \
204                         RANDOM_ITEM_TYPE_##type, \
205                         autocvar_g_random_items_##name##_probability, \
206                         1 \
207                 )
208         X(HEALTH, health);
209         X(ARMOR, armor);
210         X(RESOURCE, resource);
211         X(WEAPON, weapon);
212         X(POWERUP, powerup);
213         #undef X
214         int item_type = RandomSelection_chosen_float;
215         switch (item_type)
216         {
217                 case RANDOM_ITEM_TYPE_HEALTH:
218                 {
219                         RandomSelection_Init();
220                         FOREACH(Items, it.instanceOfHealth,
221                         {
222                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
223                                         cvar(sprintf("g_random_items_%s_probability",
224                                         RandomItems_GetItemVarName(it.m_canonical_spawnfunc))), 1);
225                         });
226                         return RandomSelection_chosen_string;
227                 }
228                 case RANDOM_ITEM_TYPE_ARMOR:
229                 {
230                         RandomSelection_Init();
231                         FOREACH(Items, it.instanceOfArmor,
232                         {
233                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
234                                         cvar(sprintf("g_random_items_%s_probability",
235                                         RandomItems_GetItemVarName(it.m_canonical_spawnfunc))), 1);
236                         });
237                         return RandomSelection_chosen_string;
238                 }
239                 case RANDOM_ITEM_TYPE_RESOURCE:
240                 {
241                         RandomSelection_Init();
242                         FOREACH(Items, it.instanceOfAmmo,
243                         {
244                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
245                                         cvar(sprintf("g_random_items_%s_probability",
246                                         RandomItems_GetItemVarName(it.m_canonical_spawnfunc))), 1);
247                         });
248                         return RandomSelection_chosen_string;
249                 }
250                 case RANDOM_ITEM_TYPE_WEAPON:
251                 {
252                         RandomSelection_Init();
253                         FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
254                         {
255                                 string cvar_name = sprintf("g_random_items_%s_probability",
256                                         it.m_canonical_spawnfunc);
257                                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
258                                 {
259                                         continue;
260                                 }
261                                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
262                         });
263                         return RandomSelection_chosen_string;
264                 }
265                 case RANDOM_ITEM_TYPE_POWERUP:
266                 {
267                         RandomSelection_Init();
268                         #define X(classname) \
269                                 RandomSelection_AddString( \
270                                         classname, \
271                                         cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
272                                         1 \
273                                 )
274                         X("item_strength");
275                         X("item_shield");
276                         X("item_fuel_regen");
277                         X("item_jetpack");
278                         #undef X
279                         return RandomSelection_chosen_string;
280                 }
281         }
282         return "";
283 }
284
285 /// \brief Replaces a map item.
286 /// \param[in] item Item to replace.
287 /// \return Spawned item on success, NULL otherwise.
288 entity RandomItems_ReplaceMapItem(entity item)
289 {
290         //PrintToChatAll(strcat("Replacing ", item.classname));
291         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
292         if (new_classnames == "")
293         {
294                 return NULL;
295         }
296         string new_classname;
297         if (new_classnames == "random")
298         {
299                 new_classname = RandomItems_GetRandomMapItemClassName();
300                 if (new_classname == "")
301                 {
302                         return NULL;
303                 }
304         }
305         else
306         {
307                 int num_new_classnames = tokenize_console(new_classnames);
308                 if (num_new_classnames == 1)
309                 {
310                         new_classname = new_classnames;
311                 }
312                 else
313                 {
314                         int classname_index = floor(random() * num_new_classnames);
315                         new_classname = argv(classname_index);
316                 }
317         }
318         //PrintToChatAll(strcat("Replacing with ", new_classname));
319         if (new_classname == item.classname)
320         {
321                 return NULL;
322         }
323         random_items_is_spawning = true;
324         entity new_item;
325         if (!expr_evaluate(autocvar_g_overkill))
326         {
327                 new_item = Item_Create(strzone(new_classname), item.origin);
328                 random_items_is_spawning = false;
329                 if (new_item == NULL)
330                 {
331                         return NULL;
332                 }
333         }
334         else
335         {
336                 new_item = spawn();
337                 new_item.classname = strzone(new_classname);
338                 new_item.spawnfunc_checked = true;
339                 new_item.ok_item = true;
340                 Item_Initialize(new_item, new_classname);
341                 random_items_is_spawning = false;
342                 if (wasfreed(new_item))
343                 {
344                         return NULL;
345                 }
346                 setorigin(new_item, item.origin);
347         }
348         if (item.team)
349         {
350                 new_item.team = item.team;
351         }
352         return new_item;
353 }
354
355 /// \brief Returns a random classname of the instagib loot item.
356 /// \return Random classname of the instagib loot item.
357 string RandomItems_GetRandomInstagibLootItemClassName()
358 {
359         RandomSelection_Init();
360         #define X(classname) \
361                 RandomSelection_AddString( \
362                         classname, \
363                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
364                         1 \
365                 )
366         X("item_vaporizer_cells");
367         X("item_invisibility");
368         X("item_extralife");
369         X("item_speed");
370         #undef X
371         return RandomSelection_chosen_string;
372 }
373
374 /// \brief Returns a random classname of the overkill loot item.
375 /// \return Random classname of the overkill loot item.
376 string RandomItems_GetRandomOverkillLootItemClassName()
377 {
378         RandomSelection_Init();
379         string varname;
380         #define X(classname) MACRO_BEGIN \
381                 if ((varname = RandomItems_GetItemVarName(classname))) \
382                 { \
383                         RandomSelection_AddString( \
384                                 classname, \
385                                 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
386                                 1 \
387                         ); \
388                 } \
389         MACRO_END
390         X("item_health_mega");
391         X("item_armor_small");
392         X("item_armor_medium");
393         X("item_armor_big");
394         X("item_armor_mega");
395         X("weapon_hmg");
396         X("weapon_rpc");
397         #undef X
398         return RandomSelection_chosen_string;
399 }
400
401 /// \brief Returns a random classname of the loot item.
402 /// \return Random classname of the loot item.
403 string RandomItems_GetRandomLootItemClassName()
404 {
405         if (autocvar_g_instagib)
406         {
407                 return RandomItems_GetRandomInstagibLootItemClassName();
408         }
409         if (expr_evaluate(autocvar_g_overkill))
410         {
411                 return RandomItems_GetRandomOverkillLootItemClassName();
412         }
413         RandomSelection_Init();
414         #define X(type, name) \
415                 RandomSelection_AddFloat( \
416                         RANDOM_ITEM_TYPE_##type, \
417                         autocvar_g_random_loot_##name##_probability, \
418                         1 \
419                 )
420         X(HEALTH, health);
421         X(ARMOR, armor);
422         X(RESOURCE, resource);
423         X(WEAPON, weapon);
424         X(POWERUP, powerup);
425         #undef X
426         int item_type = RandomSelection_chosen_float;
427         switch (item_type)
428         {
429                 case RANDOM_ITEM_TYPE_HEALTH:
430                 {
431                         RandomSelection_Init();
432                         #define X(classname) \
433                                 RandomSelection_AddString( \
434                                         classname, \
435                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
436                                         1 \
437                                 )
438                         FOREACH(Items, it.instanceOfHealth, {
439                                 X(sprintf("item_%s", it.netname));
440                         });
441                         #undef X
442                         return RandomSelection_chosen_string;
443                 }
444                 case RANDOM_ITEM_TYPE_ARMOR:
445                 {
446                         RandomSelection_Init();
447                         #define X(classname) \
448                                 RandomSelection_AddString( \
449                                         classname, \
450                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
451                                         1 \
452                                 )
453                         FOREACH(Items, it.instanceOfArmor, {
454                                 X(sprintf("item_%s", it.netname));
455                         });
456                         #undef X
457                         return RandomSelection_chosen_string;
458                 }
459                 case RANDOM_ITEM_TYPE_RESOURCE:
460                 {
461                         RandomSelection_Init();
462                         #define X(classname) \
463                                 RandomSelection_AddString( \
464                                         classname, \
465                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
466                                         1 \
467                                 )
468                         FOREACH(Items, it.instanceOfAmmo, {
469                                 X(sprintf("item_%s", it.netname));
470                         });
471                         #undef X
472                         return RandomSelection_chosen_string;
473                 }
474                 case RANDOM_ITEM_TYPE_WEAPON:
475                 {
476                         RandomSelection_Init();
477                         FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
478                         {
479                                 string class_name = strcat("weapon_", it.netname);
480                                 string cvar_name = sprintf(
481                                         "g_random_loot_%s_probability", class_name);
482                                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
483                                 {
484                                         continue;
485                                 }
486                                 RandomSelection_AddString(class_name, cvar(cvar_name), 1);
487                         });
488                         return RandomSelection_chosen_string;
489                 }
490                 case RANDOM_ITEM_TYPE_POWERUP:
491                 {
492                         RandomSelection_Init();
493                         #define X(classname) \
494                                 RandomSelection_AddString( \
495                                         classname, \
496                                         cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
497                                         1 \
498                                 )
499                         X("item_strength");
500                         X("item_shield");
501                         X("item_jetpack");
502                         X("item_fuel_regen");
503                         #undef X
504                         return RandomSelection_chosen_string;
505                 }
506         }
507         return "";
508 }
509
510 /// \brief Spawns a random loot item.
511 /// \param[in] position Position of the item.
512 /// \return No return.
513 void RandomItems_SpawnLootItem(vector position)
514 {
515         string class_name = RandomItems_GetRandomLootItemClassName();
516         if (class_name == "")
517         {
518                 return;
519         }
520         vector spread = '0 0 0';
521         spread.z = autocvar_g_random_loot_spread / 2;
522         spread += randomvec() * autocvar_g_random_loot_spread;
523         random_items_is_spawning = true;
524         if (!expr_evaluate(autocvar_g_overkill))
525         {
526                 Item_CreateLoot(class_name, position, spread,
527                         autocvar_g_random_loot_time);
528         }
529         else
530         {
531                 entity item = spawn();
532                 item.ok_item = true;
533                 item.classname = class_name;
534                 Item_InitializeLoot(item, class_name, position, spread,
535                         autocvar_g_random_loot_time);
536         }
537         random_items_is_spawning = false;
538 }
539
540 //============================= Hooks ========================================
541
542 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
543         autocvar_g_random_loot));
544
545 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
546 {
547         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
548 }
549
550 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
551 {
552         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
553 }
554
555 /// \brief Hook that is called when an item is about to spawn.
556 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
557 {
558         //PrintToChatAll("FilterItem");
559         if (!autocvar_g_random_items)
560         {
561                 return false;
562         }
563         if (random_items_is_spawning == true)
564         {
565                 return false;
566         }
567         entity item = M_ARGV(0, entity);
568         if (Item_IsLoot(item))
569         {
570                 return false;
571         }
572         if (RandomItems_ReplaceMapItem(item) == NULL)
573         {
574                 return false;
575         }
576         return true;
577 }
578
579 /// \brief Hook that is called after the player has touched an item.
580 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
581 {
582         //PrintToChatAll("ItemTouched");
583         if (!autocvar_g_random_items)
584         {
585                 return;
586         }
587         entity item = M_ARGV(0, entity);
588         if (Item_IsLoot(item))
589         {
590                 return;
591         }
592         entity new_item = RandomItems_ReplaceMapItem(item);
593         if (new_item == NULL)
594         {
595                 return;
596         }
597         Item_ScheduleRespawn(new_item);
598         delete(item);
599 }
600
601 /// \brief Hook which is called when the player dies.
602 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
603 {
604         //PrintToChatAll("PlayerDies");
605         if (!autocvar_g_random_loot)
606         {
607                 return;
608         }
609         entity victim = M_ARGV(2, entity);
610         vector loot_position = victim.origin + '0 0 32';
611         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
612                 autocvar_g_random_loot_max);
613         for (int item_index = 0; item_index < num_loot_items; ++item_index)
614         {
615                 RandomItems_SpawnLootItem(loot_position);
616         }
617 }