]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
2cf4b0a7738497ba50dc8917d84c26fc11367ce9
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / mutator / random_items / sv_random_items.qc
1 #include "sv_random_items.qh"
2
3 /// \file
4 /// \brief Source file that contains implementation of the random items mutator.
5 /// \author Lyberta
6 /// \copyright GNU GPLv2 or any later version.
7
8 //============================ Constants ======================================
9
10 enum
11 {
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
17 };
18
19 //======================= Global variables ====================================
20
21 // Replace cvars
22
23 /// \brief Classnames to replace %s with.
24 /// string autocvar_g_random_items_replace_%s;
25
26 // Map probability cvars
27
28 /// \brief Probability of random %s spawning in the map.
29 /// float autocvar_g_random_items_%s_probability;
30
31 /// \brief Probability of random %s spawning in the map during overkill.
32 /// float autocvar_g_random_items_overkill_%s_probability;
33
34 // Loot
35
36 bool autocvar_g_random_loot; ///< Whether to enable random loot.
37
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.
42
43 // Loot probability cvars
44
45 /// \brief Probability of random %s spawning as loot.
46 /// float autocvar_g_random_loot_%s_probability;
47
48 /// \brief Probability of random %s spawning as loot during overkill.
49 /// float autocvar_g_random_loot_overkill_%s_probability;
50
51 /// \brief Holds whether random item is spawning. Used to prevent infinite
52 /// recursion.
53 bool random_items_is_spawning = false;
54
55 //====================== Forward declarations =================================
56
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,
61         .bool item_property);
62
63 string RandomItems_GetItemVarName(string class_name);
64
65 //=========================== Public API ======================================
66
67 string RandomItems_GetRandomItemClassName(string prefix)
68 {
69         if (autocvar_g_instagib)
70         {
71                 return RandomItems_GetRandomInstagibItemClassName(prefix);
72         }
73         if (expr_evaluate(autocvar_g_overkill))
74         {
75                 return RandomItems_GetRandomOverkillItemClassName(prefix);
76         }
77         return RandomItems_GetRandomVanillaItemClassName(prefix);
78 }
79
80 string RandomItems_GetRandomVanillaItemClassName(string prefix)
81 {
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;
94         switch (item_type)
95         {
96                 case RANDOM_ITEM_TYPE_HEALTH:
97                 {
98                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
99                                 instanceOfHealth);
100                 }
101                 case RANDOM_ITEM_TYPE_ARMOR:
102                 {
103                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
104                                 instanceOfArmor);
105                 }
106                 case RANDOM_ITEM_TYPE_RESOURCE:
107                 {
108                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
109                                 instanceOfAmmo);
110                 }
111                 case RANDOM_ITEM_TYPE_WEAPON:
112                 {
113                         RandomSelection_Init();
114                         FOREACH(Weapons, it != WEP_Null &&
115                                 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
116                         {
117                                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
118                                         it.m_canonical_spawnfunc);
119                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
120                                         cvar(cvar_name), 1);
121                         });
122                         return RandomSelection_chosen_string;
123                 }
124                 case RANDOM_ITEM_TYPE_POWERUP:
125                 {
126                         RandomSelection_Init();
127                         #define X(classname) \
128                                 RandomSelection_AddString( \
129                                         classname, \
130                                         cvar(sprintf("g_%s_%s_probability", prefix, classname)), \
131                                         1 \
132                                 )
133                         X("item_strength");
134                         X("item_shield");
135                         X("item_fuel_regen");
136                         X("item_jetpack");
137                         #undef X
138                         return RandomSelection_chosen_string;
139                 }
140         }
141         return "";
142 }
143
144 string RandomItems_GetRandomInstagibItemClassName(string prefix)
145 {
146         RandomSelection_Init();
147         FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB,
148         {
149                 RandomSelection_AddString(it.m_canonical_spawnfunc,
150                         cvar(sprintf("g_%s_%s_probability", prefix,
151                         it.m_canonical_spawnfunc)), 1);
152         });
153         return RandomSelection_chosen_string;
154 }
155
156 string RandomItems_GetRandomOverkillItemClassName(string prefix)
157 {
158         RandomSelection_Init();
159         string varname;
160         #define X(classname) MACRO_BEGIN \
161                 if ((varname = RandomItems_GetItemVarName(classname))) \
162                 { \
163                         RandomSelection_AddString( \
164                                 classname, \
165                                 cvar(sprintf("g_%s_overkill_%s_probability", prefix, varname)), \
166                                 1 \
167                         ); \
168                 } \
169         MACRO_END
170         X("item_health_mega");
171         X("item_armor_small");
172         X("item_armor_medium");
173         X("item_armor_big");
174         X("item_armor_mega");
175         X("weapon_hmg");
176         X("weapon_rpc");
177         #undef X
178         return RandomSelection_chosen_string;
179 }
180
181 //========================= Free functions ====================================
182
183 string RandomItems_GetItemVarName(string class_name)
184 {
185         if (startsWith(class_name, "weapon_"))
186         {
187                 FOREACH(Weapons, it.m_canonical_spawnfunc == class_name, {
188                         if (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)
189                         {
190                                 return "";
191                         }
192                         return class_name;
193                 });
194         }
195         switch (class_name)
196         {
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);
203                 #undef XCOND
204         }
205         return "";
206 }
207
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)
212 {
213         return cvar_string(sprintf("g_random_items_replace_%s", item.classname));
214 }
215
216 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
217         .bool item_property)
218 {
219         RandomSelection_Init();
220         FOREACH(Items, it.item_property,
221         {
222                 RandomSelection_AddString(it.m_canonical_spawnfunc,
223                         cvar(sprintf("g_%s_%s_probability", prefix,
224                         it.m_canonical_spawnfunc)), 1);
225         });
226         return RandomSelection_chosen_string;
227 }
228
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)
233 {
234         //PrintToChatAll(strcat("Replacing ", item.classname));
235         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
236         if (new_classnames == "")
237         {
238                 return NULL;
239         }
240         string new_classname;
241         if (new_classnames == "random")
242         {
243                 new_classname = RandomItems_GetRandomItemClassName("random_items");
244                 if (new_classname == "")
245                 {
246                         return NULL;
247                 }
248         }
249         else
250         {
251                 int num_new_classnames = tokenize_console(new_classnames);
252                 if (num_new_classnames == 1)
253                 {
254                         new_classname = new_classnames;
255                 }
256                 else
257                 {
258                         int classname_index = floor(random() * num_new_classnames);
259                         new_classname = argv(classname_index);
260                 }
261         }
262         //PrintToChatAll(strcat("Replacing with ", new_classname));
263         if (new_classname == item.classname)
264         {
265                 return NULL;
266         }
267         random_items_is_spawning = true;
268         entity new_item;
269         if (!expr_evaluate(autocvar_g_overkill))
270         {
271                 new_item = Item_Create(strzone(new_classname), item.origin);
272                 random_items_is_spawning = false;
273                 if (new_item == NULL)
274                 {
275                         return NULL;
276                 }
277         }
278         else
279         {
280                 new_item = spawn();
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))
287                 {
288                         return NULL;
289                 }
290                 setorigin(new_item, item.origin);
291         }
292         if (item.team)
293         {
294                 new_item.team = item.team;
295         }
296         return new_item;
297 }
298
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)
303 {
304         string class_name = RandomItems_GetRandomItemClassName("random_loot");
305         if (class_name == "")
306         {
307                 return;
308         }
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))
314         {
315                 Item_CreateLoot(class_name, position, spread,
316                         autocvar_g_random_loot_time);
317         }
318         else
319         {
320                 entity item = spawn();
321                 item.ok_item = true;
322                 item.classname = class_name;
323                 Item_InitializeLoot(item, class_name, position, spread,
324                         autocvar_g_random_loot_time);
325         }
326         random_items_is_spawning = false;
327 }
328
329 //============================= Hooks ========================================
330
331 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
332         autocvar_g_random_loot));
333
334 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
335 {
336         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
337 }
338
339 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
340 {
341         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
342 }
343
344 /// \brief Hook that is called when an item is about to spawn.
345 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
346 {
347         //PrintToChatAll("FilterItem");
348         if (!autocvar_g_random_items)
349         {
350                 return false;
351         }
352         if (random_items_is_spawning == true)
353         {
354                 return false;
355         }
356         entity item = M_ARGV(0, entity);
357         if (Item_IsLoot(item))
358         {
359                 return false;
360         }
361         if (RandomItems_ReplaceMapItem(item) == NULL)
362         {
363                 return false;
364         }
365         return true;
366 }
367
368 /// \brief Hook that is called after the player has touched an item.
369 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
370 {
371         //PrintToChatAll("ItemTouched");
372         if (!autocvar_g_random_items)
373         {
374                 return;
375         }
376         entity item = M_ARGV(0, entity);
377         if (Item_IsLoot(item))
378         {
379                 return;
380         }
381         entity new_item = RandomItems_ReplaceMapItem(item);
382         if (new_item == NULL)
383         {
384                 return;
385         }
386         Item_ScheduleRespawn(new_item);
387         delete(item);
388 }
389
390 /// \brief Hook which is called when the player dies.
391 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
392 {
393         //PrintToChatAll("PlayerDies");
394         if (!autocvar_g_random_loot)
395         {
396                 return;
397         }
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)
403         {
404                 RandomItems_SpawnLootItem(loot_position);
405         }
406 }