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, it.m_canonical_spawnfunc == class_name, {
85 if (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)
92 bool is_ok = expr_evaluate(autocvar_g_overkill);
95 #define X(classname) case #classname: return #classname
96 #define XCOND(classname, var, expr) case #classname: if (expr) return #var; else break
98 X(item_health_medium);
100 XCOND(item_health_mega, item_health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
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);
115 case "item_invincible" : return "item_shield";
119 X(item_vaporizer_cells);
120 X(item_invisibility);
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));
143 /// \brief Returns a random classname of the instagib map item.
144 /// \return Random classname of the instagib map item.
145 string RandomItems_GetRandomInstagibMapItemClassName()
147 RandomSelection_Init();
148 #define X(classname) \
149 RandomSelection_AddString( \
151 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
154 X("item_vaporizer_cells");
155 X("item_invisibility");
159 return RandomSelection_chosen_string;
162 /// \brief Returns a random classname of the overkill map item.
163 /// \return Random classname of the overkill map item.
164 string RandomItems_GetRandomOverkillMapItemClassName()
166 RandomSelection_Init();
168 #define X(classname) MACRO_BEGIN \
169 if ((varname = RandomItems_GetItemVarName(classname))) \
171 RandomSelection_AddString( \
173 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
178 X("item_health_mega");
179 X("item_armor_small");
180 X("item_armor_medium");
182 X("item_armor_mega");
186 return RandomSelection_chosen_string;
189 /// \brief Returns a random classname of the map item.
190 /// \return Random classname of the map item.
191 string RandomItems_GetRandomMapItemClassName()
193 if (autocvar_g_instagib)
195 return RandomItems_GetRandomInstagibMapItemClassName();
197 if (expr_evaluate(autocvar_g_overkill))
199 return RandomItems_GetRandomOverkillMapItemClassName();
201 RandomSelection_Init();
202 #define X(type, name) \
203 RandomSelection_AddFloat( \
204 RANDOM_ITEM_TYPE_##type, \
205 autocvar_g_random_items_##name##_probability, \
210 X(RESOURCE, resource);
214 int item_type = RandomSelection_chosen_float;
217 case RANDOM_ITEM_TYPE_HEALTH:
219 RandomSelection_Init();
220 FOREACH(Items, it.instanceOfHealth,
222 RandomSelection_AddString(it.m_canonical_spawnfunc,
223 cvar(sprintf("g_random_items_%s_probability",
224 RandomItems_GetItemVarName(it.m_canonical_spawnfunc))), 1);
226 return RandomSelection_chosen_string;
228 case RANDOM_ITEM_TYPE_ARMOR:
230 RandomSelection_Init();
231 FOREACH(Items, it.instanceOfArmor,
233 RandomSelection_AddString(it.m_canonical_spawnfunc,
234 cvar(sprintf("g_random_items_%s_probability",
235 RandomItems_GetItemVarName(it.m_canonical_spawnfunc))), 1);
237 return RandomSelection_chosen_string;
239 case RANDOM_ITEM_TYPE_RESOURCE:
241 RandomSelection_Init();
242 FOREACH(Items, it.instanceOfAmmo,
244 RandomSelection_AddString(it.m_canonical_spawnfunc,
245 cvar(sprintf("g_random_items_%s_probability",
246 RandomItems_GetItemVarName(it.m_canonical_spawnfunc))), 1);
248 return RandomSelection_chosen_string;
250 case RANDOM_ITEM_TYPE_WEAPON:
252 RandomSelection_Init();
253 FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
255 string cvar_name = sprintf("g_random_items_%s_probability",
256 it.m_canonical_spawnfunc);
257 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
261 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
263 return RandomSelection_chosen_string;
265 case RANDOM_ITEM_TYPE_POWERUP:
267 RandomSelection_Init();
268 #define X(classname) \
269 RandomSelection_AddString( \
271 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
275 X("item_invincible");
276 X("item_fuel_regen");
279 return RandomSelection_chosen_string;
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)
290 //PrintToChatAll(strcat("Replacing ", item.classname));
291 string new_classnames = RandomItems_GetItemReplacementClassNames(item);
292 if (new_classnames == "")
296 string new_classname;
297 if (new_classnames == "random")
299 new_classname = RandomItems_GetRandomMapItemClassName();
300 if (new_classname == "")
307 int num_new_classnames = tokenize_console(new_classnames);
308 if (num_new_classnames == 1)
310 new_classname = new_classnames;
314 int classname_index = floor(random() * num_new_classnames);
315 new_classname = argv(classname_index);
318 //PrintToChatAll(strcat("Replacing with ", new_classname));
319 if (new_classname == item.classname)
323 random_items_is_spawning = true;
325 if (!expr_evaluate(autocvar_g_overkill))
327 new_item = Item_Create(strzone(new_classname), item.origin);
328 random_items_is_spawning = false;
329 if (new_item == NULL)
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))
346 setorigin(new_item, item.origin);
350 new_item.team = item.team;
355 /// \brief Returns a random classname of the instagib loot item.
356 /// \return Random classname of the instagib loot item.
357 string RandomItems_GetRandomInstagibLootItemClassName()
359 RandomSelection_Init();
360 #define X(classname) \
361 RandomSelection_AddString( \
363 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
366 X("item_vaporizer_cells");
367 X("item_invisibility");
371 return RandomSelection_chosen_string;
374 /// \brief Returns a random classname of the overkill loot item.
375 /// \return Random classname of the overkill loot item.
376 string RandomItems_GetRandomOverkillLootItemClassName()
378 RandomSelection_Init();
380 #define X(classname) MACRO_BEGIN \
381 if ((varname = RandomItems_GetItemVarName(classname))) \
383 RandomSelection_AddString( \
385 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
390 X("item_health_mega");
391 X("item_armor_small");
392 X("item_armor_medium");
394 X("item_armor_mega");
398 return RandomSelection_chosen_string;
401 /// \brief Returns a random classname of the loot item.
402 /// \return Random classname of the loot item.
403 string RandomItems_GetRandomLootItemClassName()
405 if (autocvar_g_instagib)
407 return RandomItems_GetRandomInstagibLootItemClassName();
409 if (expr_evaluate(autocvar_g_overkill))
411 return RandomItems_GetRandomOverkillLootItemClassName();
413 RandomSelection_Init();
414 #define X(type, name) \
415 RandomSelection_AddFloat( \
416 RANDOM_ITEM_TYPE_##type, \
417 autocvar_g_random_loot_##name##_probability, \
422 X(RESOURCE, resource);
426 int item_type = RandomSelection_chosen_float;
429 case RANDOM_ITEM_TYPE_HEALTH:
431 RandomSelection_Init();
432 #define X(classname) \
433 RandomSelection_AddString( \
435 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
438 FOREACH(Items, it.instanceOfHealth, {
439 X(sprintf("item_%s", it.netname));
442 return RandomSelection_chosen_string;
444 case RANDOM_ITEM_TYPE_ARMOR:
446 RandomSelection_Init();
447 #define X(classname) \
448 RandomSelection_AddString( \
450 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
453 FOREACH(Items, it.instanceOfArmor, {
454 X(sprintf("item_%s", it.netname));
457 return RandomSelection_chosen_string;
459 case RANDOM_ITEM_TYPE_RESOURCE:
461 RandomSelection_Init();
462 #define X(classname) \
463 RandomSelection_AddString( \
465 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
468 FOREACH(Items, it.instanceOfAmmo, {
469 X(sprintf("item_%s", it.netname));
472 return RandomSelection_chosen_string;
474 case RANDOM_ITEM_TYPE_WEAPON:
476 RandomSelection_Init();
477 FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
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))
486 RandomSelection_AddString(class_name, cvar(cvar_name), 1);
488 return RandomSelection_chosen_string;
490 case RANDOM_ITEM_TYPE_POWERUP:
492 RandomSelection_Init();
493 #define X(classname) \
494 RandomSelection_AddString( \
496 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
500 X("item_invincible");
502 X("item_fuel_regen");
504 return RandomSelection_chosen_string;
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)
515 string class_name = RandomItems_GetRandomLootItemClassName();
516 if (class_name == "")
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))
526 Item_CreateLoot(class_name, position, spread,
527 autocvar_g_random_loot_time);
531 entity item = spawn();
533 item.classname = class_name;
534 Item_InitializeLoot(item, class_name, position, spread,
535 autocvar_g_random_loot_time);
537 random_items_is_spawning = false;
540 //============================= Hooks ========================================
542 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
543 autocvar_g_random_loot));
545 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
547 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
550 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
552 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
555 /// \brief Hook that is called when an item is about to spawn.
556 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
558 //PrintToChatAll("FilterItem");
559 if (!autocvar_g_random_items)
563 if (random_items_is_spawning == true)
567 entity item = M_ARGV(0, entity);
568 if (Item_IsLoot(item))
572 if (RandomItems_ReplaceMapItem(item) == NULL)
579 /// \brief Hook that is called after the player has touched an item.
580 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
582 //PrintToChatAll("ItemTouched");
583 if (!autocvar_g_random_items)
587 entity item = M_ARGV(0, entity);
588 if (Item_IsLoot(item))
592 entity new_item = RandomItems_ReplaceMapItem(item);
593 if (new_item == NULL)
597 Item_ScheduleRespawn(new_item);
601 /// \brief Hook which is called when the player dies.
602 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
604 //PrintToChatAll("PlayerDies");
605 if (!autocvar_g_random_loot)
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)
615 RandomItems_SpawnLootItem(loot_position);