1 #include "sv_random_items.qh"
4 /// \brief Source file that contains implementation of the random items mutator.
6 /// \copyright GNU GPLv2 or any later version.
8 //============================ Constants ======================================
12 RANDOM_ITEM_TYPE_HEALTH = 1,
13 RANDOM_ITEM_TYPE_ARMOR,
14 RANDOM_ITEM_TYPE_RESOURCE,
15 RANDOM_ITEM_TYPE_WEAPON,
16 RANDOM_ITEM_TYPE_POWERUP
19 //======================= Global variables ====================================
23 /// \brief Classnames to replace %s with.
24 /// string autocvar_g_random_items_replace_%s;
26 // Map probability cvars
28 /// \brief Probability of random health items spawning in the map.
29 float autocvar_g_random_items_health_probability;
30 /// \brief Probability of random armor items spawning in the map.
31 float autocvar_g_random_items_armor_probability;
32 /// \brief Probability of random resource items spawning in the map.
33 float autocvar_g_random_items_resource_probability;
34 /// \brief Probability of random weapons spawning in the map.
35 float autocvar_g_random_items_weapon_probability;
36 /// \brief Probability of random powerups spawning in the map.
37 float autocvar_g_random_items_powerup_probability;
39 /// \brief Probability of random %s spawning in the map.
40 /// float autocvar_g_random_items_%s_probability;
42 /// \brief Probability of random %s spawning in the map during overkill.
43 /// float autocvar_g_random_items_overkill_%s_probability;
47 bool autocvar_g_random_loot; ///< Whether to enable random loot.
49 float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
50 float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
51 float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
52 float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
54 // Loot probability cvars
56 /// \brief Probability of random health items spawning as loot.
57 float autocvar_g_random_loot_health_probability;
58 /// \brief Probability of random armor items spawning as loot.
59 float autocvar_g_random_loot_armor_probability;
60 /// \brief Probability of random resource items spawning as loot.
61 float autocvar_g_random_loot_resource_probability;
62 /// \brief Probability of random weapons spawning as loot.
63 float autocvar_g_random_loot_weapon_probability;
64 /// \brief Probability of random powerups spawning as loot.
65 float autocvar_g_random_loot_powerup_probability;
67 /// \brief Probability of random %s spawning as loot.
68 /// float autocvar_g_random_loot_weapon_%s_probability;
70 /// \brief Probability of random %s spawning as loot during overkill.
71 /// float autocvar_g_random_loot_overkill_%s_probability;
73 /// \brief Holds whether random item is spawning. Used to prevent infinite
75 bool random_items_is_spawning = false;
77 //========================= Free functions ====================================
79 string RandomItems_GetItemVarName(string class_name)
81 if (startsWith(class_name, "weapon_"))
83 FOREACH(Weapons, it.m_canonical_spawnfunc == class_name, {
84 if (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)
91 bool is_ok = expr_evaluate(autocvar_g_overkill);
94 #define XCOND(classname, expr) case #classname: if (expr) return #classname; else break
95 case "item_health_small": return "item_health_small";
96 case "item_health_medium": return "item_health_medium";
97 case "item_health_big": return "item_health_big";
98 XCOND(item_health_mega, !is_ok || !autocvar_g_overkill_filter_healthmega);
100 case "item_armor_small": return "item_armor_small";
101 XCOND(item_armor_medium, !is_ok || !autocvar_g_overkill_filter_armormedium);
102 XCOND(item_armor_big, !is_ok || !autocvar_g_overkill_filter_armorbig);
103 XCOND(item_armor_mega, !is_ok || !autocvar_g_overkill_filter_armormega);
105 case "item_shells": return "item_shells";
106 case "item_bullets": return "item_bullets";
107 case "item_rockets": return "item_rockets";
108 case "item_cells": return "item_cells";
109 case "item_plasma": return "item_plasma";
110 case "item_fuel": return "item_fuel";
112 case "item_strength": return "item_strength";
113 case "item_shield": return "item_shield";
114 case "item_fuel_regen": return "item_fuel_regen";
115 case "item_jetpack": return "item_jetpack";
117 case "item_vaporizer_cells": return "item_vaporizer_cells";
118 case "item_invisibility": return "item_invisibility";
119 case "item_extralife": return "item_extralife";
120 case "item_speed": return "item_speed";
127 /// \brief Returns list of classnames to replace a map item with.
128 /// \param[in] item Item to inspect.
129 /// \return List of classnames to replace a map item with.
130 string RandomItems_GetItemReplacementClassNames(entity item)
132 string class_name = RandomItems_GetItemVarName(item.classname);
135 return cvar_string(sprintf("g_random_items_replace_%s", class_name));
140 /// \brief Returns a random classname of the instagib map item.
141 /// \return Random classname of the instagib map item.
142 string RandomItems_GetRandomInstagibMapItemClassName()
144 RandomSelection_Init();
145 #define X(classname) \
146 RandomSelection_AddString( \
148 cvar(sprintf("g_random_items_%s_probability", RandomItems_GetItemVarName(classname))), \
151 X("item_vaporizer_cells");
152 X("item_invisibility");
156 return RandomSelection_chosen_string;
159 /// \brief Returns a random classname of the overkill map item.
160 /// \return Random classname of the overkill map item.
161 string RandomItems_GetRandomOverkillMapItemClassName()
163 RandomSelection_Init();
165 #define X(classname) MACRO_BEGIN \
166 if ((varname = RandomItems_GetItemVarName(classname))) \
168 RandomSelection_AddString( \
170 cvar(sprintf("g_random_items_overkill_%s_probability", varname)), \
175 X("item_health_mega");
176 X("item_armor_small");
177 X("item_armor_medium");
179 X("item_armor_mega");
183 return RandomSelection_chosen_string;
186 /// \brief Returns a random classname of the map item.
187 /// \return Random classname of the map item.
188 string RandomItems_GetRandomMapItemClassName()
190 if (autocvar_g_instagib)
192 return RandomItems_GetRandomInstagibMapItemClassName();
194 if (expr_evaluate(autocvar_g_overkill))
196 return RandomItems_GetRandomOverkillMapItemClassName();
198 RandomSelection_Init();
199 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
200 autocvar_g_random_items_health_probability, 1);
201 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
202 autocvar_g_random_items_armor_probability, 1);
203 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
204 autocvar_g_random_items_resource_probability, 1);
205 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON,
206 autocvar_g_random_items_weapon_probability, 1);
207 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP,
208 autocvar_g_random_items_powerup_probability, 1);
209 int item_type = RandomSelection_chosen_float;
212 case RANDOM_ITEM_TYPE_HEALTH:
214 RandomSelection_Init();
215 FOREACH(Items, it.instanceOfHealth,
217 RandomSelection_AddString(it.m_canonical_spawnfunc,
218 cvar(sprintf("g_random_items_%s_probability",
219 it.m_canonical_spawnfunc)), 1);
221 return RandomSelection_chosen_string;
223 case RANDOM_ITEM_TYPE_ARMOR:
225 RandomSelection_Init();
226 FOREACH(Items, it.instanceOfArmor,
228 RandomSelection_AddString(it.m_canonical_spawnfunc,
229 cvar(sprintf("g_random_items_%s_probability",
230 it.m_canonical_spawnfunc)), 1);
232 return RandomSelection_chosen_string;
234 case RANDOM_ITEM_TYPE_RESOURCE:
236 RandomSelection_Init();
237 FOREACH(Items, it.instanceOfAmmo,
239 RandomSelection_AddString(it.m_canonical_spawnfunc,
240 cvar(sprintf("g_random_items_%s_probability",
241 it.m_canonical_spawnfunc)), 1);
243 return RandomSelection_chosen_string;
245 case RANDOM_ITEM_TYPE_WEAPON:
247 RandomSelection_Init();
248 FOREACH(Weapons, it != WEP_Null &&
249 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
251 string cvar_name = sprintf("g_random_items_%s_probability",
252 it.m_canonical_spawnfunc);
253 RandomSelection_AddString(it.m_canonical_spawnfunc,
256 return RandomSelection_chosen_string;
258 case RANDOM_ITEM_TYPE_POWERUP:
260 RandomSelection_Init();
261 #define X(classname) \
262 RandomSelection_AddString( \
264 cvar(sprintf("g_random_items_%s_probability", classname)), \
269 X("item_fuel_regen");
272 return RandomSelection_chosen_string;
278 /// \brief Replaces a map item.
279 /// \param[in] item Item to replace.
280 /// \return Spawned item on success, NULL otherwise.
281 entity RandomItems_ReplaceMapItem(entity item)
283 //PrintToChatAll(strcat("Replacing ", item.classname));
284 string new_classnames = RandomItems_GetItemReplacementClassNames(item);
285 if (new_classnames == "")
289 string new_classname;
290 if (new_classnames == "random")
292 new_classname = RandomItems_GetRandomMapItemClassName();
293 if (new_classname == "")
300 int num_new_classnames = tokenize_console(new_classnames);
301 if (num_new_classnames == 1)
303 new_classname = new_classnames;
307 int classname_index = floor(random() * num_new_classnames);
308 new_classname = argv(classname_index);
311 //PrintToChatAll(strcat("Replacing with ", new_classname));
312 if (new_classname == item.classname)
316 random_items_is_spawning = true;
318 if (!expr_evaluate(autocvar_g_overkill))
320 new_item = Item_Create(strzone(new_classname), item.origin);
321 random_items_is_spawning = false;
322 if (new_item == NULL)
330 new_item.classname = strzone(new_classname);
331 new_item.spawnfunc_checked = true;
332 new_item.ok_item = true;
333 Item_Initialize(new_item, new_classname);
334 random_items_is_spawning = false;
335 if (wasfreed(new_item))
339 setorigin(new_item, item.origin);
343 new_item.team = item.team;
348 /// \brief Returns a random classname of the instagib loot item.
349 /// \return Random classname of the instagib loot item.
350 string RandomItems_GetRandomInstagibLootItemClassName()
352 RandomSelection_Init();
353 #define X(classname) \
354 RandomSelection_AddString( \
356 cvar(sprintf("g_random_loot_%s_probability", RandomItems_GetItemVarName(classname))), \
359 X("item_vaporizer_cells");
360 X("item_invisibility");
364 return RandomSelection_chosen_string;
367 /// \brief Returns a random classname of the overkill loot item.
368 /// \return Random classname of the overkill loot item.
369 string RandomItems_GetRandomOverkillLootItemClassName()
371 RandomSelection_Init();
373 #define X(classname) MACRO_BEGIN \
374 if ((varname = RandomItems_GetItemVarName(classname))) \
376 RandomSelection_AddString( \
378 cvar(sprintf("g_random_loot_overkill_%s_probability", varname)), \
383 X("item_health_mega");
384 X("item_armor_small");
385 X("item_armor_medium");
387 X("item_armor_mega");
391 return RandomSelection_chosen_string;
394 /// \brief Returns a random classname of the loot item.
395 /// \return Random classname of the loot item.
396 string RandomItems_GetRandomLootItemClassName()
398 if (autocvar_g_instagib)
400 return RandomItems_GetRandomInstagibLootItemClassName();
402 if (expr_evaluate(autocvar_g_overkill))
404 return RandomItems_GetRandomOverkillLootItemClassName();
406 RandomSelection_Init();
407 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
408 autocvar_g_random_loot_health_probability, 1);
409 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
410 autocvar_g_random_loot_armor_probability, 1);
411 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
412 autocvar_g_random_loot_resource_probability, 1);
413 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON,
414 autocvar_g_random_loot_weapon_probability, 1);
415 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP,
416 autocvar_g_random_loot_powerup_probability, 1);
417 int item_type = RandomSelection_chosen_float;
420 case RANDOM_ITEM_TYPE_HEALTH:
422 RandomSelection_Init();
423 FOREACH(Items, it.instanceOfHealth,
425 RandomSelection_AddString(it.m_canonical_spawnfunc,
426 cvar(sprintf("g_random_loot_%s_probability",
427 it.m_canonical_spawnfunc)), 1);
429 return RandomSelection_chosen_string;
431 case RANDOM_ITEM_TYPE_ARMOR:
433 RandomSelection_Init();
434 FOREACH(Items, it.instanceOfArmor,
436 RandomSelection_AddString(it.m_canonical_spawnfunc,
437 cvar(sprintf("g_random_loot_%s_probability",
438 it.m_canonical_spawnfunc)), 1);
440 return RandomSelection_chosen_string;
442 case RANDOM_ITEM_TYPE_RESOURCE:
444 RandomSelection_Init();
445 FOREACH(Items, it.instanceOfAmmo,
447 RandomSelection_AddString(it.m_canonical_spawnfunc,
448 cvar(sprintf("g_random_loot_%s_probability",
449 it.m_canonical_spawnfunc)), 1);
451 return RandomSelection_chosen_string;
453 case RANDOM_ITEM_TYPE_WEAPON:
455 RandomSelection_Init();
456 FOREACH(Weapons, it != WEP_Null &&
457 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
459 string cvar_name = sprintf("g_random_loot_%s_probability",
460 it.m_canonical_spawnfunc);
461 RandomSelection_AddString(it.m_canonical_spawnfunc,
464 return RandomSelection_chosen_string;
466 case RANDOM_ITEM_TYPE_POWERUP:
468 RandomSelection_Init();
469 #define X(classname) \
470 RandomSelection_AddString( \
472 cvar(sprintf("g_random_loot_%s_probability", classname)), \
478 X("item_fuel_regen");
480 return RandomSelection_chosen_string;
486 /// \brief Spawns a random loot item.
487 /// \param[in] position Position of the item.
488 /// \return No return.
489 void RandomItems_SpawnLootItem(vector position)
491 string class_name = RandomItems_GetRandomLootItemClassName();
492 if (class_name == "")
496 vector spread = '0 0 0';
497 spread.z = autocvar_g_random_loot_spread / 2;
498 spread += randomvec() * autocvar_g_random_loot_spread;
499 random_items_is_spawning = true;
500 if (!expr_evaluate(autocvar_g_overkill))
502 Item_CreateLoot(class_name, position, spread,
503 autocvar_g_random_loot_time);
507 entity item = spawn();
509 item.classname = class_name;
510 Item_InitializeLoot(item, class_name, position, spread,
511 autocvar_g_random_loot_time);
513 random_items_is_spawning = false;
516 //============================= Hooks ========================================
518 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
519 autocvar_g_random_loot));
521 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
523 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
526 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
528 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
531 /// \brief Hook that is called when an item is about to spawn.
532 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
534 //PrintToChatAll("FilterItem");
535 if (!autocvar_g_random_items)
539 if (random_items_is_spawning == true)
543 entity item = M_ARGV(0, entity);
544 if (Item_IsLoot(item))
548 if (RandomItems_ReplaceMapItem(item) == NULL)
555 /// \brief Hook that is called after the player has touched an item.
556 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
558 //PrintToChatAll("ItemTouched");
559 if (!autocvar_g_random_items)
563 entity item = M_ARGV(0, entity);
564 if (Item_IsLoot(item))
568 entity new_item = RandomItems_ReplaceMapItem(item);
569 if (new_item == NULL)
573 Item_ScheduleRespawn(new_item);
577 /// \brief Hook which is called when the player dies.
578 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
580 //PrintToChatAll("PlayerDies");
581 if (!autocvar_g_random_loot)
585 entity victim = M_ARGV(2, entity);
586 vector loot_position = victim.origin + '0 0 32';
587 int num_loot_items = floor(autocvar_g_random_loot_min + random() *
588 autocvar_g_random_loot_max);
589 for (int item_index = 0; item_index < num_loot_items; ++item_index)
591 RandomItems_SpawnLootItem(loot_position);