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, health_small);
98 X(item_health_medium, health_medium);
99 X(item_health_big, health_big);
100 XCOND(item_health_mega, health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
102 X(item_armor_small, armor_small);
103 XCOND(item_armor_medium, armor_medium, !is_ok || !autocvar_g_overkill_filter_armormedium);
104 XCOND(item_armor_big, armor_big, !is_ok || !autocvar_g_overkill_filter_armorbig);
105 XCOND(item_armor_mega, armor_mega, !is_ok || !autocvar_g_overkill_filter_armormega);
107 X(item_shells, resource_shells);
108 X(item_bullets, resource_bullets);
109 X(item_rockets, resource_rockets);
110 X(item_cells, resource_cells);
111 X(item_plasma, resource_plasma);
112 X(item_fuel, resource_fuel);
114 X(item_strength, strength);
115 X(item_invincible, shield);
116 X(item_fuel_regen, fuel_regen);
117 X(item_jetpack, jetpack);
119 X(item_vaporizer_cells, vaporizer_cells);
120 X(item_invisibility, invisibility);
121 X(item_extralife, extralife);
122 X(item_speed, 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 #define X(classname) \
274 RandomSelection_AddString( \
276 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
279 FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), {
280 X(sprintf("weapon_%s", it.netname));
283 return RandomSelection_chosen_string;
285 case RANDOM_ITEM_TYPE_POWERUP:
287 RandomSelection_Init();
288 #define X(classname) \
289 RandomSelection_AddString( \
291 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
295 X("item_invincible");
296 X("item_fuel_regen");
299 return RandomSelection_chosen_string;
305 /// \brief Replaces a map item.
306 /// \param[in] item Item to replace.
307 /// \return Spawned item on success, NULL otherwise.
308 entity RandomItems_ReplaceMapItem(entity item)
310 //PrintToChatAll(strcat("Replacing ", item.classname));
311 string new_classnames = RandomItems_GetItemReplacementClassNames(item);
312 if (new_classnames == "")
316 string new_classname;
317 if (new_classnames == "random")
319 new_classname = RandomItems_GetRandomMapItemClassName();
320 if (new_classname == "")
327 int num_new_classnames = tokenize_console(new_classnames);
328 if (num_new_classnames == 1)
330 new_classname = new_classnames;
334 int classname_index = floor(random() * num_new_classnames);
335 new_classname = argv(classname_index);
338 //PrintToChatAll(strcat("Replacing with ", new_classname));
339 if (new_classname == item.classname)
343 random_items_is_spawning = true;
345 if (!expr_evaluate(autocvar_g_overkill))
347 new_item = Item_Create(strzone(new_classname), item.origin);
352 new_item.classname = strzone(new_classname);
353 new_item.spawnfunc_checked = true;
354 new_item.ok_item = true;
355 Item_Initialize(new_item, new_classname);
356 if (wasfreed(new_item))
360 setorigin(new_item, item.origin);
362 random_items_is_spawning = false;
366 /// \brief Returns a random classname of the instagib loot item.
367 /// \return Random classname of the instagib loot item.
368 string RandomItems_GetRandomInstagibLootItemClassName()
370 RandomSelection_Init();
371 #define X(classname) \
372 RandomSelection_AddString( \
374 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
377 X("item_vaporizer_cells");
378 X("item_invisibility");
382 return RandomSelection_chosen_string;
385 /// \brief Returns a random classname of the overkill loot item.
386 /// \return Random classname of the overkill loot item.
387 string RandomItems_GetRandomOverkillLootItemClassName()
389 RandomSelection_Init();
391 #define X(classname) MACRO_BEGIN \
392 if ((varname = RandomItems_GetItemVarName(classname))) \
394 RandomSelection_AddString( \
396 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
401 X("item_health_mega");
402 X("item_armor_small");
403 X("item_armor_medium");
405 X("item_armor_mega");
409 return RandomSelection_chosen_string;
412 /// \brief Returns a random classname of the loot item.
413 /// \return Random classname of the loot item.
414 string RandomItems_GetRandomLootItemClassName()
416 if (autocvar_g_instagib)
418 return RandomItems_GetRandomInstagibLootItemClassName();
420 if (expr_evaluate(autocvar_g_overkill))
422 return RandomItems_GetRandomOverkillLootItemClassName();
424 RandomSelection_Init();
425 #define X(type, name) \
426 RandomSelection_AddFloat( \
427 RANDOM_ITEM_TYPE_##type, \
428 autocvar_g_random_loot_##name##_probability, \
433 X(RESOURCE, resource);
437 int item_type = RandomSelection_chosen_float;
440 case RANDOM_ITEM_TYPE_HEALTH:
442 RandomSelection_Init();
443 #define X(classname) \
444 RandomSelection_AddString( \
446 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
449 FOREACH(Items, it.instanceOfHealth, {
450 X(sprintf("item_%s", it.netname));
453 return RandomSelection_chosen_string;
455 case RANDOM_ITEM_TYPE_ARMOR:
457 RandomSelection_Init();
458 #define X(classname) \
459 RandomSelection_AddString( \
461 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
464 FOREACH(Items, it.instanceOfArmor, {
465 X(sprintf("item_%s", it.netname));
468 return RandomSelection_chosen_string;
470 case RANDOM_ITEM_TYPE_RESOURCE:
472 RandomSelection_Init();
473 #define X(classname) \
474 RandomSelection_AddString( \
476 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
479 FOREACH(Items, it.instanceOfAmmo, {
480 X(sprintf("item_%s", it.netname));
483 return RandomSelection_chosen_string;
485 case RANDOM_ITEM_TYPE_WEAPON:
487 RandomSelection_Init();
488 #define X(classname) \
489 RandomSelection_AddString( \
491 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
494 FOREACH(Weapons, !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), {
495 X(sprintf("weapon_%s", it.netname));
498 return RandomSelection_chosen_string;
500 case RANDOM_ITEM_TYPE_POWERUP:
502 RandomSelection_Init();
503 #define X(classname) \
504 RandomSelection_AddString( \
506 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
510 X("item_invincible");
512 X("item_fuel_regen");
514 return RandomSelection_chosen_string;
520 /// \brief Spawns a random loot item.
521 /// \param[in] position Position of the item.
522 /// \return No return.
523 void RandomItems_SpawnLootItem(vector position)
525 string class_name = RandomItems_GetRandomLootItemClassName();
526 if (class_name == "")
530 vector spread = '0 0 0';
531 spread.z = autocvar_g_random_loot_spread / 2;
532 spread += randomvec() * autocvar_g_random_loot_spread;
533 random_items_is_spawning = true;
534 if (!expr_evaluate(autocvar_g_overkill))
536 Item_CreateLoot(class_name, position, spread,
537 autocvar_g_random_loot_time);
541 entity item = spawn();
543 item.classname = class_name;
544 Item_InitializeLoot(item, class_name, position, spread,
545 autocvar_g_random_loot_time);
547 random_items_is_spawning = false;
550 //============================= Hooks ========================================
552 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
553 autocvar_g_random_loot));
555 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
557 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
560 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
562 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
565 /// \brief Hook that is called when an item is about to spawn.
566 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
568 //PrintToChatAll("FilterItem");
569 if (!autocvar_g_random_items)
573 if (random_items_is_spawning == true)
577 entity item = M_ARGV(0, entity);
578 if (Item_IsLoot(item))
582 if (RandomItems_ReplaceMapItem(item) == NULL)
589 /// \brief Hook that is called after the player has touched an item.
590 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
592 //PrintToChatAll("ItemTouched");
593 if (!autocvar_g_random_items)
597 entity item = M_ARGV(0, entity);
598 if (Item_IsLoot(item))
602 entity new_item = RandomItems_ReplaceMapItem(item);
603 if (new_item == NULL)
607 Item_ScheduleRespawn(new_item);
611 /// \brief Hook which is called when the player dies.
612 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
614 //PrintToChatAll("PlayerDies");
615 if (!autocvar_g_random_loot)
619 entity victim = M_ARGV(2, entity);
620 vector loot_position = victim.origin + '0 0 32';
621 int num_loot_items = floor(autocvar_g_random_loot_min + random() *
622 autocvar_g_random_loot_max);
623 for (int item_index = 0; item_index < num_loot_items; ++item_index)
625 RandomItems_SpawnLootItem(loot_position);