1 #include "sv_random_items.qh"
3 /// \brief Source file that contains implementation of the random items mutator.
5 /// \copyright GNU GPLv2 or any later version.
7 //============================ Constants ======================================
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
18 //======================= Global variables ====================================
20 bool autocvar_g_random_items; ///< Whether to enable random items.
24 /// \brief Classnames to replace %s with.
25 /// string autocvar_g_random_items_replace_%s;
27 // Map probability cvars
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;
40 /// \brief Probability of random %s spawning in the map.
41 /// float autocvar_g_random_items_%s_probability;
43 /// \brief Probability of random %s spawning in the map during overkill.
44 /// float autocvar_g_random_items_overkill_%s_probability;
48 bool autocvar_g_random_loot; ///< Whether to enable random loot.
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.
55 // Loot probability cvars
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;
68 /// \brief Probability of random %s spawning as loot.
69 /// float autocvar_g_random_loot_weapon_%s_probability;
71 /// \brief Probability of random %s spawning as loot during overkill.
72 /// float autocvar_g_random_loot_overkill_%s_probability;
74 /// \brief Holds whether random item is spawning. Used to prevent infinite
76 bool random_items_is_spawning = false;
78 //========================= Free functions ====================================
80 string RandomItems_GetItemVarName(string class_name)
82 bool is_ok = expr_evaluate(autocvar_g_overkill);
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);
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);
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);
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);
125 X(item_strength, strength);
126 X(item_invincible, shield);
127 X(item_fuel_regen, fuel_regen);
128 X(item_jetpack, jetpack);
130 X(item_vaporizer_cells, vaporizer_cells);
131 X(item_invisibility, invisibility);
132 X(item_extralife, extralife);
133 X(item_speed, speed);
135 XCOND(weapon_hmg, weapon_hmg, is_ok);
136 XCOND(weapon_rpc, weapon_rpc, is_ok);
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)
148 string class_name = RandomItems_GetItemVarName(item.classname);
151 return cvar_string(sprintf("g_random_items_replace_%s", class_name));
153 if (item.classname == "replacedweapon")
155 Weapon w = Weapons_from(item.weapon);
158 return cvar_string(sprintf("g_random_items_replace_weapon_%s", w.netname));
164 /// \brief Returns a random classname of the instagib map item.
165 /// \return Random classname of the instagib map item.
166 string RandomItems_GetRandomInstagibMapItemClassName()
168 RandomSelection_Init();
169 #define X(classname) \
170 RandomSelection_AddString( \
172 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
175 X("item_vaporizer_cells");
176 X("item_invisibility");
180 return RandomSelection_chosen_string;
183 /// \brief Returns a random classname of the overkill map item.
184 /// \return Random classname of the overkill map item.
185 string RandomItems_GetRandomOverkillMapItemClassName()
187 RandomSelection_Init();
189 #define X(classname) MACRO_BEGIN \
190 if ((varname = RandomItems_GetItemVarName(classname))) \
192 RandomSelection_AddString( \
194 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
199 X("item_health_mega");
200 X("item_armor_small");
201 X("item_armor_medium");
203 X("item_armor_mega");
207 return RandomSelection_chosen_string;
210 /// \brief Returns a random classname of the map item.
211 /// \return Random classname of the map item.
212 string RandomItems_GetRandomMapItemClassName()
214 if (autocvar_g_instagib)
216 return RandomItems_GetRandomInstagibMapItemClassName();
218 if (expr_evaluate(autocvar_g_overkill))
220 return RandomItems_GetRandomOverkillMapItemClassName();
222 RandomSelection_Init();
223 #define X(type, name) \
224 RandomSelection_AddFloat( \
225 RANDOM_ITEM_TYPE_##type, \
226 autocvar_g_random_items_##name##_probability, \
231 X(RESOURCE, resource);
235 int item_type = RandomSelection_chosen_float;
238 case RANDOM_ITEM_TYPE_HEALTH:
240 RandomSelection_Init();
241 #define X(classname) \
242 RandomSelection_AddString( \
244 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
247 X("item_health_small");
248 X("item_health_medium");
249 X("item_health_big");
250 X("item_health_mega");
252 return RandomSelection_chosen_string;
254 case RANDOM_ITEM_TYPE_ARMOR:
256 RandomSelection_Init();
257 #define X(classname) \
258 RandomSelection_AddString( \
260 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
263 X("item_health_small");
264 X("item_health_medium");
265 X("item_health_big");
266 X("item_health_mega");
268 return RandomSelection_chosen_string;
270 case RANDOM_ITEM_TYPE_RESOURCE:
272 RandomSelection_Init();
273 #define X(classname) \
274 RandomSelection_AddString( \
276 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
286 return RandomSelection_chosen_string;
288 case RANDOM_ITEM_TYPE_WEAPON:
290 RandomSelection_Init();
291 #define X(classname) \
292 RandomSelection_AddString( \
294 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
299 X("weapon_machinegun");
305 X("weapon_devastator");
306 X("weapon_shockwave");
311 X("weapon_fireball");
312 X("weapon_minelayer");
316 X("weapon_vaporizer");
318 return RandomSelection_chosen_string;
320 case RANDOM_ITEM_TYPE_POWERUP:
322 RandomSelection_Init();
323 #define X(classname) \
324 RandomSelection_AddString( \
326 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
330 X("item_invincible");
331 X("item_fuel_regen");
334 return RandomSelection_chosen_string;
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)
345 //PrintToChatAll(strcat("Replacing ", item.classname));
346 string new_classnames = RandomItems_GetItemReplacementClassNames(item);
347 if (new_classnames == "")
351 string new_classname;
352 if (new_classnames == "random")
354 new_classname = RandomItems_GetRandomMapItemClassName();
355 if (new_classname == "")
362 int num_new_classnames = tokenize_console(new_classnames);
363 if (num_new_classnames == 1)
365 new_classname = new_classnames;
369 int classname_index = floor(random() * num_new_classnames);
370 new_classname = argv(classname_index);
373 //PrintToChatAll(strcat("Replacing with ", new_classname));
374 if (new_classname == item.classname)
378 random_items_is_spawning = true;
380 if (!expr_evaluate(autocvar_g_overkill))
382 new_item = Item_Create(strzone(new_classname), item.origin);
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))
395 setorigin(new_item, item.origin);
397 random_items_is_spawning = false;
401 /// \brief Returns a random classname of the instagib loot item.
402 /// \return Random classname of the instagib loot item.
403 string RandomItems_GetRandomInstagibLootItemClassName()
405 RandomSelection_Init();
406 #define X(classname) \
407 RandomSelection_AddString( \
409 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
412 X("item_vaporizer_cells");
413 X("item_invisibility");
417 return RandomSelection_chosen_string;
420 /// \brief Returns a random classname of the overkill loot item.
421 /// \return Random classname of the overkill loot item.
422 string RandomItems_GetRandomOverkillLootItemClassName()
424 RandomSelection_Init();
426 #define X(classname) MACRO_BEGIN \
427 if ((varname = RandomItems_GetItemVarName(classname))) \
429 RandomSelection_AddString( \
431 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
436 X("item_health_mega");
437 X("item_armor_small");
438 X("item_armor_medium");
440 X("item_armor_mega");
444 return RandomSelection_chosen_string;
447 /// \brief Returns a random classname of the loot item.
448 /// \return Random classname of the loot item.
449 string RandomItems_GetRandomLootItemClassName()
451 if (autocvar_g_instagib)
453 return RandomItems_GetRandomInstagibLootItemClassName();
455 if (expr_evaluate(autocvar_g_overkill))
457 return RandomItems_GetRandomOverkillLootItemClassName();
459 RandomSelection_Init();
460 #define X(type, name) \
461 RandomSelection_AddFloat( \
462 RANDOM_ITEM_TYPE_##type, \
463 autocvar_g_random_loot_##name##_probability, \
468 X(RESOURCE, resource);
472 int item_type = RandomSelection_chosen_float;
475 case RANDOM_ITEM_TYPE_HEALTH:
477 RandomSelection_Init();
478 #define X(classname) \
479 RandomSelection_AddString( \
481 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
484 X("item_health_small");
485 X("item_health_medium");
486 X("item_health_big");
487 X("item_health_mega");
489 return RandomSelection_chosen_string;
491 case RANDOM_ITEM_TYPE_ARMOR:
493 RandomSelection_Init();
494 #define X(classname) \
495 RandomSelection_AddString( \
497 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
500 X("item_armor_small");
501 X("item_armor_medium");
503 X("item_armor_mega");
505 return RandomSelection_chosen_string;
507 case RANDOM_ITEM_TYPE_RESOURCE:
509 RandomSelection_Init();
510 #define X(classname) \
511 RandomSelection_AddString( \
513 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
523 return RandomSelection_chosen_string;
525 case RANDOM_ITEM_TYPE_WEAPON:
527 RandomSelection_Init();
528 #define X(classname) \
529 RandomSelection_AddString( \
531 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
536 X("weapon_machinegun");
542 X("weapon_devastator");
543 X("weapon_shockwave");
548 X("weapon_fireball");
549 X("weapon_minelayer");
553 X("weapon_vaporizer");
555 return RandomSelection_chosen_string;
557 case RANDOM_ITEM_TYPE_POWERUP:
559 RandomSelection_Init();
560 #define X(classname) \
561 RandomSelection_AddString( \
563 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
567 X("item_invincible");
569 X("item_fuel_regen");
571 return RandomSelection_chosen_string;
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)
582 string class_name = RandomItems_GetRandomLootItemClassName();
583 if (class_name == "")
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))
593 Item_CreateLoot(class_name, position, spread,
594 autocvar_g_random_loot_time);
598 entity item = spawn();
600 item.classname = class_name;
601 Item_InitializeLoot(item, class_name, position, spread,
602 autocvar_g_random_loot_time);
604 random_items_is_spawning = false;
607 //============================= Hooks ========================================
609 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
610 autocvar_g_random_loot));
612 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
614 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
617 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
619 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
622 /// \brief Hook that is called when an item is about to spawn.
623 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
625 //PrintToChatAll("FilterItem");
626 if (!autocvar_g_random_items)
630 if (random_items_is_spawning == true)
634 entity item = M_ARGV(0, entity);
635 if (Item_IsLoot(item))
639 if (RandomItems_ReplaceMapItem(item) == NULL)
646 /// \brief Hook that is called after the player has touched an item.
647 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
649 //PrintToChatAll("ItemTouched");
650 if (!autocvar_g_random_items)
654 entity item = M_ARGV(0, entity);
655 if (Item_IsLoot(item))
659 entity new_item = RandomItems_ReplaceMapItem(item);
660 if (new_item == NULL)
664 Item_ScheduleRespawn(new_item);
668 /// \brief Hook which is called when the player dies.
669 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
671 //PrintToChatAll("PlayerDies");
672 if (!autocvar_g_random_loot)
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)
682 RandomItems_SpawnLootItem(loot_position);