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 if (startsWith(class_name, "weapon_"))
84 FOREACH(Weapons, sprintf("weapon_%s", it.netname) == class_name, {
85 if (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)
92 bool is_ok = expr_evaluate(autocvar_g_overkill);
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);
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);
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);
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);
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);
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)
135 string class_name = RandomItems_GetItemVarName(item.classname);
138 return cvar_string(sprintf("g_random_items_replace_%s", class_name));
140 if (item.classname == "replacedweapon")
142 Weapon w = Weapons_from(item.weapon);
145 return cvar_string(sprintf("g_random_items_replace_weapon_%s", w.netname));
151 /// \brief Returns a random classname of the instagib map item.
152 /// \return Random classname of the instagib map item.
153 string RandomItems_GetRandomInstagibMapItemClassName()
155 RandomSelection_Init();
156 #define X(classname) \
157 RandomSelection_AddString( \
159 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
162 X("item_vaporizer_cells");
163 X("item_invisibility");
167 return RandomSelection_chosen_string;
170 /// \brief Returns a random classname of the overkill map item.
171 /// \return Random classname of the overkill map item.
172 string RandomItems_GetRandomOverkillMapItemClassName()
174 RandomSelection_Init();
176 #define X(classname) MACRO_BEGIN \
177 if ((varname = RandomItems_GetItemVarName(classname))) \
179 RandomSelection_AddString( \
181 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
186 X("item_health_mega");
187 X("item_armor_small");
188 X("item_armor_medium");
190 X("item_armor_mega");
194 return RandomSelection_chosen_string;
197 /// \brief Returns a random classname of the map item.
198 /// \return Random classname of the map item.
199 string RandomItems_GetRandomMapItemClassName()
201 if (autocvar_g_instagib)
203 return RandomItems_GetRandomInstagibMapItemClassName();
205 if (expr_evaluate(autocvar_g_overkill))
207 return RandomItems_GetRandomOverkillMapItemClassName();
209 RandomSelection_Init();
210 #define X(type, name) \
211 RandomSelection_AddFloat( \
212 RANDOM_ITEM_TYPE_##type, \
213 autocvar_g_random_items_##name##_probability, \
218 X(RESOURCE, resource);
222 int item_type = RandomSelection_chosen_float;
225 case RANDOM_ITEM_TYPE_HEALTH:
227 RandomSelection_Init();
228 #define X(classname) \
229 RandomSelection_AddString( \
231 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
234 FOREACH(Items, it.instanceOfHealth, {
235 X(sprintf("item_%s", it.netname));
238 return RandomSelection_chosen_string;
240 case RANDOM_ITEM_TYPE_ARMOR:
242 RandomSelection_Init();
243 #define X(classname) \
244 RandomSelection_AddString( \
246 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
249 FOREACH(Items, it.instanceOfArmor, {
250 X(sprintf("item_%s", it.netname));
253 return RandomSelection_chosen_string;
255 case RANDOM_ITEM_TYPE_RESOURCE:
257 RandomSelection_Init();
258 #define X(classname) \
259 RandomSelection_AddString( \
261 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
264 FOREACH(Items, it.instanceOfAmmo, {
265 X(sprintf("item_%s", it.netname));
268 return RandomSelection_chosen_string;
270 case RANDOM_ITEM_TYPE_WEAPON:
272 RandomSelection_Init();
273 FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
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))
282 RandomSelection_AddString(class_name, cvar(cvar_name), 1);
284 return RandomSelection_chosen_string;
286 case RANDOM_ITEM_TYPE_POWERUP:
288 RandomSelection_Init();
289 #define X(classname) \
290 RandomSelection_AddString( \
292 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
296 X("item_invincible");
297 X("item_fuel_regen");
300 return RandomSelection_chosen_string;
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)
311 //PrintToChatAll(strcat("Replacing ", item.classname));
312 string new_classnames = RandomItems_GetItemReplacementClassNames(item);
313 if (new_classnames == "")
317 string new_classname;
318 if (new_classnames == "random")
320 new_classname = RandomItems_GetRandomMapItemClassName();
321 if (new_classname == "")
328 int num_new_classnames = tokenize_console(new_classnames);
329 if (num_new_classnames == 1)
331 new_classname = new_classnames;
335 int classname_index = floor(random() * num_new_classnames);
336 new_classname = argv(classname_index);
339 //PrintToChatAll(strcat("Replacing with ", new_classname));
340 if (new_classname == item.classname)
344 random_items_is_spawning = true;
346 if (!expr_evaluate(autocvar_g_overkill))
348 new_item = Item_Create(strzone(new_classname), item.origin);
353 new_item.classname = strzone(new_classname);
354 new_item.spawnfunc_checked = true;
355 new_item.ok_item = true;
356 Item_Initialize(new_item, new_classname);
357 if (wasfreed(new_item))
361 setorigin(new_item, item.origin);
363 random_items_is_spawning = false;
367 /// \brief Returns a random classname of the instagib loot item.
368 /// \return Random classname of the instagib loot item.
369 string RandomItems_GetRandomInstagibLootItemClassName()
371 RandomSelection_Init();
372 #define X(classname) \
373 RandomSelection_AddString( \
375 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
378 X("item_vaporizer_cells");
379 X("item_invisibility");
383 return RandomSelection_chosen_string;
386 /// \brief Returns a random classname of the overkill loot item.
387 /// \return Random classname of the overkill loot item.
388 string RandomItems_GetRandomOverkillLootItemClassName()
390 RandomSelection_Init();
392 #define X(classname) MACRO_BEGIN \
393 if ((varname = RandomItems_GetItemVarName(classname))) \
395 RandomSelection_AddString( \
397 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
402 X("item_health_mega");
403 X("item_armor_small");
404 X("item_armor_medium");
406 X("item_armor_mega");
410 return RandomSelection_chosen_string;
413 /// \brief Returns a random classname of the loot item.
414 /// \return Random classname of the loot item.
415 string RandomItems_GetRandomLootItemClassName()
417 if (autocvar_g_instagib)
419 return RandomItems_GetRandomInstagibLootItemClassName();
421 if (expr_evaluate(autocvar_g_overkill))
423 return RandomItems_GetRandomOverkillLootItemClassName();
425 RandomSelection_Init();
426 #define X(type, name) \
427 RandomSelection_AddFloat( \
428 RANDOM_ITEM_TYPE_##type, \
429 autocvar_g_random_loot_##name##_probability, \
434 X(RESOURCE, resource);
438 int item_type = RandomSelection_chosen_float;
441 case RANDOM_ITEM_TYPE_HEALTH:
443 RandomSelection_Init();
444 #define X(classname) \
445 RandomSelection_AddString( \
447 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
450 FOREACH(Items, it.instanceOfHealth, {
451 X(sprintf("item_%s", it.netname));
454 return RandomSelection_chosen_string;
456 case RANDOM_ITEM_TYPE_ARMOR:
458 RandomSelection_Init();
459 #define X(classname) \
460 RandomSelection_AddString( \
462 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
465 FOREACH(Items, it.instanceOfArmor, {
466 X(sprintf("item_%s", it.netname));
469 return RandomSelection_chosen_string;
471 case RANDOM_ITEM_TYPE_RESOURCE:
473 RandomSelection_Init();
474 #define X(classname) \
475 RandomSelection_AddString( \
477 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
480 FOREACH(Items, it.instanceOfAmmo, {
481 X(sprintf("item_%s", it.netname));
484 return RandomSelection_chosen_string;
486 case RANDOM_ITEM_TYPE_WEAPON:
488 RandomSelection_Init();
489 FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
491 string class_name = strcat("weapon_", it.netname);
492 string cvar_name = sprintf(
493 "g_random_loot_%s_probability", class_name);
494 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
498 RandomSelection_AddString(class_name, cvar(cvar_name), 1);
500 return RandomSelection_chosen_string;
502 case RANDOM_ITEM_TYPE_POWERUP:
504 RandomSelection_Init();
505 #define X(classname) \
506 RandomSelection_AddString( \
508 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
512 X("item_invincible");
514 X("item_fuel_regen");
516 return RandomSelection_chosen_string;
522 /// \brief Spawns a random loot item.
523 /// \param[in] position Position of the item.
524 /// \return No return.
525 void RandomItems_SpawnLootItem(vector position)
527 string class_name = RandomItems_GetRandomLootItemClassName();
528 if (class_name == "")
532 vector spread = '0 0 0';
533 spread.z = autocvar_g_random_loot_spread / 2;
534 spread += randomvec() * autocvar_g_random_loot_spread;
535 random_items_is_spawning = true;
536 if (!expr_evaluate(autocvar_g_overkill))
538 Item_CreateLoot(class_name, position, spread,
539 autocvar_g_random_loot_time);
543 entity item = spawn();
545 item.classname = class_name;
546 Item_InitializeLoot(item, class_name, position, spread,
547 autocvar_g_random_loot_time);
549 random_items_is_spawning = false;
552 //============================= Hooks ========================================
554 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
555 autocvar_g_random_loot));
557 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
559 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
562 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
564 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
567 /// \brief Hook that is called when an item is about to spawn.
568 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
570 //PrintToChatAll("FilterItem");
571 if (!autocvar_g_random_items)
575 if (random_items_is_spawning == true)
579 entity item = M_ARGV(0, entity);
580 if (Item_IsLoot(item))
584 if (RandomItems_ReplaceMapItem(item) == NULL)
591 /// \brief Hook that is called after the player has touched an item.
592 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
594 //PrintToChatAll("ItemTouched");
595 if (!autocvar_g_random_items)
599 entity item = M_ARGV(0, entity);
600 if (Item_IsLoot(item))
604 entity new_item = RandomItems_ReplaceMapItem(item);
605 if (new_item == NULL)
609 Item_ScheduleRespawn(new_item);
613 /// \brief Hook which is called when the player dies.
614 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
616 //PrintToChatAll("PlayerDies");
617 if (!autocvar_g_random_loot)
621 entity victim = M_ARGV(2, entity);
622 vector loot_position = victim.origin + '0 0 32';
623 int num_loot_items = floor(autocvar_g_random_loot_min + random() *
624 autocvar_g_random_loot_max);
625 for (int item_index = 0; item_index < num_loot_items; ++item_index)
627 RandomItems_SpawnLootItem(loot_position);