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 %s spawning in the map.
29 /// float autocvar_g_random_items_%s_probability;
31 /// \brief Probability of random %s spawning in the map during overkill.
32 /// float autocvar_g_random_items_overkill_%s_probability;
36 bool autocvar_g_random_loot; ///< Whether to enable random loot.
38 float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
39 float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
40 float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
41 float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
43 // Loot probability cvars
45 /// \brief Probability of random %s spawning as loot.
46 /// float autocvar_g_random_loot_%s_probability;
48 /// \brief Probability of random %s spawning as loot during overkill.
49 /// float autocvar_g_random_loot_overkill_%s_probability;
51 /// \brief Holds whether random item is spawning. Used to prevent infinite
53 bool random_items_is_spawning = false;
55 //====================== Forward declarations =================================
57 /// \brief Returns a random classname of the item with specific property.
58 /// \param[in] prefix Prefix of the cvars that hold probabilities.
59 /// \return Random classname of the item.
60 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
63 string RandomItems_GetItemVarName(string class_name);
65 //=========================== Public API ======================================
67 string RandomItems_GetRandomItemClassName(string prefix)
69 if (autocvar_g_instagib)
71 return RandomItems_GetRandomInstagibItemClassName(prefix);
73 if (expr_evaluate(autocvar_g_overkill))
75 return RandomItems_GetRandomOverkillItemClassName(prefix);
77 return RandomItems_GetRandomVanillaItemClassName(prefix);
80 string RandomItems_GetRandomVanillaItemClassName(string prefix)
82 RandomSelection_Init();
83 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
84 cvar(sprintf("g_%s_health_probability", prefix)), 1);
85 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
86 cvar(sprintf("g_%s_armor_probability", prefix)), 1);
87 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
88 cvar(sprintf("g_%s_resource_probability", prefix)), 1);
89 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON,
90 cvar(sprintf("g_%s_weapon_probability", prefix)), 1);
91 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP,
92 cvar(sprintf("g_%s_powerup_probability", prefix)), 1);
93 int item_type = RandomSelection_chosen_float;
96 case RANDOM_ITEM_TYPE_HEALTH:
98 return RandomItems_GetRandomItemClassNameWithProperty(prefix,
101 case RANDOM_ITEM_TYPE_ARMOR:
103 return RandomItems_GetRandomItemClassNameWithProperty(prefix,
106 case RANDOM_ITEM_TYPE_RESOURCE:
108 return RandomItems_GetRandomItemClassNameWithProperty(prefix,
111 case RANDOM_ITEM_TYPE_WEAPON:
113 RandomSelection_Init();
114 FOREACH(Weapons, it != WEP_Null &&
115 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
117 string cvar_name = sprintf("g_%s_%s_probability", prefix,
118 it.m_canonical_spawnfunc);
119 RandomSelection_AddString(it.m_canonical_spawnfunc,
122 return RandomSelection_chosen_string;
124 case RANDOM_ITEM_TYPE_POWERUP:
126 RandomSelection_Init();
127 #define X(classname) \
128 RandomSelection_AddString( \
130 cvar(sprintf("g_%s_%s_probability", prefix, classname)), \
135 X("item_fuel_regen");
138 return RandomSelection_chosen_string;
144 string RandomItems_GetRandomInstagibItemClassName(string prefix)
146 RandomSelection_Init();
147 FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB,
149 RandomSelection_AddString(it.m_canonical_spawnfunc,
150 cvar(sprintf("g_%s_%s_probability", prefix,
151 it.m_canonical_spawnfunc)), 1);
153 return RandomSelection_chosen_string;
156 string RandomItems_GetRandomOverkillItemClassName(string prefix)
158 RandomSelection_Init();
160 #define X(classname) MACRO_BEGIN \
161 if ((varname = RandomItems_GetItemVarName(classname))) \
163 RandomSelection_AddString( \
165 cvar(sprintf("g_%s_overkill_%s_probability", prefix, varname)), \
170 X("item_health_mega");
171 X("item_armor_small");
172 X("item_armor_medium");
174 X("item_armor_mega");
178 return RandomSelection_chosen_string;
181 //========================= Free functions ====================================
183 string RandomItems_GetItemVarName(string class_name)
185 if (startsWith(class_name, "weapon_"))
187 FOREACH(Weapons, it.m_canonical_spawnfunc == class_name, {
188 if (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)
197 #define XCOND(classname, expr) case #classname: if (expr) return #classname; else break
198 XCOND(item_health_mega, !autocvar_g_overkill_filter_healthmega);
199 case "item_armor_small": return "item_armor_small";
200 XCOND(item_armor_medium, !autocvar_g_overkill_filter_armormedium);
201 XCOND(item_armor_big, !autocvar_g_overkill_filter_armorbig);
202 XCOND(item_armor_mega, !autocvar_g_overkill_filter_armormega);
208 /// \brief Returns list of classnames to replace a map item with.
209 /// \param[in] item Item to inspect.
210 /// \return List of classnames to replace a map item with.
211 string RandomItems_GetItemReplacementClassNames(entity item)
213 return cvar_string(sprintf("g_random_items_replace_%s", item.classname));
216 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
219 RandomSelection_Init();
220 FOREACH(Items, it.item_property,
222 RandomSelection_AddString(it.m_canonical_spawnfunc,
223 cvar(sprintf("g_%s_%s_probability", prefix,
224 it.m_canonical_spawnfunc)), 1);
226 return RandomSelection_chosen_string;
229 /// \brief Replaces a map item.
230 /// \param[in] item Item to replace.
231 /// \return Spawned item on success, NULL otherwise.
232 entity RandomItems_ReplaceMapItem(entity item)
234 //PrintToChatAll(strcat("Replacing ", item.classname));
235 string new_classnames = RandomItems_GetItemReplacementClassNames(item);
236 if (new_classnames == "")
240 string new_classname;
241 if (new_classnames == "random")
243 new_classname = RandomItems_GetRandomItemClassName("random_items");
244 if (new_classname == "")
251 int num_new_classnames = tokenize_console(new_classnames);
252 if (num_new_classnames == 1)
254 new_classname = new_classnames;
258 int classname_index = floor(random() * num_new_classnames);
259 new_classname = argv(classname_index);
262 //PrintToChatAll(strcat("Replacing with ", new_classname));
263 if (new_classname == item.classname)
267 random_items_is_spawning = true;
269 if (!expr_evaluate(autocvar_g_overkill))
271 new_item = Item_Create(strzone(new_classname), item.origin);
272 random_items_is_spawning = false;
273 if (new_item == NULL)
281 new_item.classname = strzone(new_classname);
282 new_item.spawnfunc_checked = true;
283 new_item.ok_item = true;
284 Item_Initialize(new_item, new_classname);
285 random_items_is_spawning = false;
286 if (wasfreed(new_item))
290 setorigin(new_item, item.origin);
294 new_item.team = item.team;
299 /// \brief Spawns a random loot item.
300 /// \param[in] position Position of the item.
301 /// \return No return.
302 void RandomItems_SpawnLootItem(vector position)
304 string class_name = RandomItems_GetRandomItemClassName("random_loot");
305 if (class_name == "")
309 vector spread = '0 0 0';
310 spread.z = autocvar_g_random_loot_spread / 2;
311 spread += randomvec() * autocvar_g_random_loot_spread;
312 random_items_is_spawning = true;
313 if (!expr_evaluate(autocvar_g_overkill))
315 Item_CreateLoot(class_name, position, spread,
316 autocvar_g_random_loot_time);
320 entity item = spawn();
322 item.classname = class_name;
323 Item_InitializeLoot(item, class_name, position, spread,
324 autocvar_g_random_loot_time);
326 random_items_is_spawning = false;
329 //============================= Hooks ========================================
331 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
332 autocvar_g_random_loot));
334 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
336 M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
339 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
341 M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
344 /// \brief Hook that is called when an item is about to spawn.
345 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
347 //PrintToChatAll("FilterItem");
348 if (!autocvar_g_random_items)
352 if (random_items_is_spawning == true)
356 entity item = M_ARGV(0, entity);
357 if (Item_IsLoot(item))
361 if (RandomItems_ReplaceMapItem(item) == NULL)
368 /// \brief Hook that is called after the player has touched an item.
369 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
371 //PrintToChatAll("ItemTouched");
372 if (!autocvar_g_random_items)
376 entity item = M_ARGV(0, entity);
377 if (Item_IsLoot(item))
381 entity new_item = RandomItems_ReplaceMapItem(item);
382 if (new_item == NULL)
386 Item_ScheduleRespawn(new_item);
390 /// \brief Hook which is called when the player dies.
391 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
393 //PrintToChatAll("PlayerDies");
394 if (!autocvar_g_random_loot)
398 entity victim = M_ARGV(2, entity);
399 vector loot_position = victim.origin + '0 0 32';
400 int num_loot_items = floor(autocvar_g_random_loot_min + random() *
401 autocvar_g_random_loot_max);
402 for (int item_index = 0; item_index < num_loot_items; ++item_index)
404 RandomItems_SpawnLootItem(loot_position);