Merge remote-tracking branch 'origin/master' into morosophos/rankings_cnt
[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 //======================= Global variables ====================================
11
12 // Replace cvars
13
14 /// \brief Classnames to replace %s with.
15 /// string autocvar_g_random_items_replace_%s;
16
17 // Map probability cvars
18
19 /// \brief Probability of random %s spawning in the map.
20 /// float autocvar_g_random_items_%s_probability;
21
22 /// \brief Probability of random %s spawning in the map during overkill.
23 /// float autocvar_g_random_items_overkill_%s_probability;
24
25 // Loot
26
27 float autocvar_g_random_loot_min; ///< Minimum amount of loot items.
28 float autocvar_g_random_loot_max; ///< Maximum amount of loot items.
29 float autocvar_g_random_loot_time; ///< Amount of time the loot will stay.
30 float autocvar_g_random_loot_spread; ///< How far can loot be thrown.
31
32 // Loot probability cvars
33
34 /// \brief Probability of random %s spawning as loot.
35 /// float autocvar_g_random_loot_%s_probability;
36
37 /// \brief Probability of random %s spawning as loot during overkill.
38 /// float autocvar_g_random_loot_overkill_%s_probability;
39
40 /// \brief Holds whether random item is spawning. Used to prevent infinite
41 /// recursion.
42 bool random_items_is_spawning = false;
43
44 //====================== Forward declarations =================================
45
46 /// \brief Returns a random classname of the item with specific property.
47 /// \param[in] prefix Prefix of the cvars that hold probabilities.
48 /// \return Random classname of the item.
49 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
50         .bool item_property);
51
52 //=========================== Public API ======================================
53
54 string RandomItems_GetRandomItemClassName(string prefix)
55 {
56         if (MUTATOR_IS_ENABLED(mutator_instagib))
57         {
58                 return RandomItems_GetRandomInstagibItemClassName(prefix);
59         }
60         if (MUTATOR_IS_ENABLED(ok))
61         {
62                 return RandomItems_GetRandomOverkillItemClassName(prefix);
63         }
64         return RandomItems_GetRandomVanillaItemClassName(prefix,
65                 RANDOM_ITEM_TYPE_ALL);
66 }
67
68 string RandomItems_GetRandomVanillaItemClassName(string prefix, int types)
69 {
70         if (types == 0)
71         {
72                 return "";
73         }
74         while (types != 0)
75         {
76                 string cvar_name;
77                 RandomSelection_Init();
78                 if (types & RANDOM_ITEM_TYPE_HEALTH)
79                 {
80                         cvar_name = sprintf("g_%s_health_probability", prefix);
81                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
82                         {
83                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
84                         }
85                         else
86                         {
87                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
88                                         cvar(cvar_name), 1);
89                         }
90                 }
91                 if (types & RANDOM_ITEM_TYPE_ARMOR)
92                 {
93                         cvar_name = sprintf("g_%s_armor_probability", prefix);
94                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
95                         {
96                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
97                         }
98                         else
99                         {
100                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
101                                         cvar(cvar_name), 1);
102                         }
103                 }
104                 if (types & RANDOM_ITEM_TYPE_RESOURCE)
105                 {
106                         cvar_name = sprintf("g_%s_resource_probability", prefix);
107                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
108                         {
109                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
110                         }
111                         else
112                         {
113                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
114                                         cvar(cvar_name), 1);
115                         }
116                 }
117                 if (types & RANDOM_ITEM_TYPE_WEAPON)
118                 {
119                         cvar_name = sprintf("g_%s_weapon_probability", prefix);
120                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
121                         {
122                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
123                         }
124                         else
125                         {
126                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON, cvar(cvar_name), 1);
127                         }
128                 }
129                 if (types & RANDOM_ITEM_TYPE_POWERUP)
130                 {
131                         cvar_name = sprintf("g_%s_powerup_probability", prefix);
132                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
133                         {
134                                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
135                         }
136                         else
137                         {
138                                 RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP, cvar(cvar_name), 1);
139                         }
140                 }
141                 int item_type = RandomSelection_chosen_float;
142                 string class_name = "";
143                 switch (item_type)
144                 {
145                         case RANDOM_ITEM_TYPE_HEALTH:
146                         {
147                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
148                                         prefix, instanceOfHealth);
149                                 break;
150                         }
151                         case RANDOM_ITEM_TYPE_ARMOR:
152                         {
153                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
154                                         prefix, instanceOfArmor);
155                                 break;
156                         }
157                         case RANDOM_ITEM_TYPE_RESOURCE:
158                         {
159                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
160                                         prefix, instanceOfAmmo);
161                                 break;
162                         }
163                         case RANDOM_ITEM_TYPE_WEAPON:
164                         {
165                                 RandomSelection_Init();
166                                 FOREACH(Weapons, it != WEP_Null &&
167                                         !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
168                                 {
169                                         cvar_name = sprintf("g_%s_%s_probability", prefix,
170                                                 it.m_canonical_spawnfunc);
171                                         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
172                                         {
173                                                 LOG_WARNF("Random items: cvar %s doesn't exist.",
174                                                         cvar_name);
175                                                 continue;
176                                         }
177                                         RandomSelection_AddString(it.m_canonical_spawnfunc,
178                                                 cvar(cvar_name), 1);
179                                 });
180                                 class_name = RandomSelection_chosen_string;
181                                 break;
182                         }
183                         case RANDOM_ITEM_TYPE_POWERUP:
184                         {
185                                 class_name = RandomItems_GetRandomItemClassNameWithProperty(
186                                         prefix, instanceOfPowerup);
187                                 break;
188                         }
189                 }
190                 if (class_name != "")
191                 {
192                         return class_name;
193                 }
194                 types &= ~item_type;
195         }
196         return "";
197 }
198
199 string RandomItems_GetRandomInstagibItemClassName(string prefix)
200 {
201         RandomSelection_Init();
202         FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB &&
203                 Item_IsDefinitionAllowed(it),
204         {
205                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
206                         it.m_canonical_spawnfunc);
207                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
208                 {
209                         LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
210                         continue;
211                 }
212                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
213         });
214         return RandomSelection_chosen_string;
215 }
216
217 string RandomItems_GetRandomOverkillItemClassName(string prefix)
218 {
219         RandomSelection_Init();
220         FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) &&
221                 !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) &&
222                 Item_IsDefinitionAllowed(it),
223         {
224                 string cvar_name = sprintf("g_%s_overkill_%s_probability", prefix,
225                         it.m_canonical_spawnfunc);
226                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
227                 {
228                         LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
229                         continue;
230                 }
231                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
232         });
233         string cvar_name = sprintf("g_%s_overkill_weapon_okhmg_probability", prefix);
234         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
235         {
236                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
237         }
238         else
239         {
240                 RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1);
241         }
242         cvar_name = sprintf("g_%s_overkill_weapon_okrpc_probability", prefix);
243         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
244         {
245                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
246         }
247         else
248         {
249                 RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1);
250         }
251         return RandomSelection_chosen_string;
252 }
253
254 //========================= Free functions ====================================
255
256 /// \brief Returns list of classnames to replace a map item with.
257 /// \param[in] item Item to inspect.
258 /// \return List of classnames to replace a map item with.
259 string RandomItems_GetItemReplacementClassNames(entity item)
260 {
261         string cvar_name = sprintf("g_random_items_replace_%s", item.classname);
262         if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
263         {
264                 LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
265                 return "";
266         }
267         return cvar_string(cvar_name);
268 }
269
270 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
271         .bool item_property)
272 {
273         RandomSelection_Init();
274         FOREACH(Items, it.item_property && (it.spawnflags & ITEM_FLAG_NORMAL) &&
275                 Item_IsDefinitionAllowed(it),
276         {
277                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
278                         it.m_canonical_spawnfunc);
279                 if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
280                 {
281                         LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
282                         continue;
283                 }
284                 RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
285         });
286         return RandomSelection_chosen_string;
287 }
288
289 /// \brief Replaces a map item.
290 /// \param[in] item Item to replace.
291 /// \return Spawned item on success, NULL otherwise.
292 entity RandomItems_ReplaceMapItem(entity item)
293 {
294         //PrintToChatAll(strcat("Replacing ", item.classname));
295         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
296         if (new_classnames == "")
297         {
298                 return NULL;
299         }
300         string new_classname;
301         if (new_classnames == "random")
302         {
303                 new_classname = RandomItems_GetRandomItemClassName("random_items");
304                 if (new_classname == "")
305                 {
306                         return NULL;
307                 }
308         }
309         else
310         {
311                 int num_new_classnames = tokenize_console(new_classnames);
312                 if (num_new_classnames == 1)
313                 {
314                         new_classname = new_classnames;
315                 }
316                 else
317                 {
318                         int classname_index = floor(random() * num_new_classnames);
319                         new_classname = argv(classname_index);
320                 }
321         }
322         //PrintToChatAll(strcat("Replacing with ", new_classname));
323         if (new_classname == item.classname)
324         {
325                 return NULL;
326         }
327         random_items_is_spawning = true;
328         entity new_item;
329         if (!MUTATOR_IS_ENABLED(ok))
330         {
331                 new_item = Item_Create(strzone(new_classname), item.origin,
332                         Item_ShouldKeepPosition(item));
333                 random_items_is_spawning = false;
334                 if (new_item == NULL)
335                 {
336                         return NULL;
337                 }
338         }
339         else
340         {
341                 new_item = spawn();
342                 new_item.classname = strzone(new_classname);
343                 new_item.spawnfunc_checked = true;
344                 new_item.noalign = Item_ShouldKeepPosition(item);
345                 new_item.ok_item = true;
346                 Item_Initialize(new_item, new_classname);
347                 random_items_is_spawning = false;
348                 if (wasfreed(new_item))
349                 {
350                         return NULL;
351                 }
352                 setorigin(new_item, item.origin);
353         }
354         if (item.team)
355         {
356                 new_item.team = item.team;
357         }
358         return new_item;
359 }
360
361 /// \brief Spawns a random loot item.
362 /// \param[in] position Position of the item.
363 /// \return No return.
364 void RandomItems_SpawnLootItem(vector position)
365 {
366         string class_name = RandomItems_GetRandomItemClassName("random_loot");
367         if (class_name == "")
368         {
369                 return;
370         }
371         vector spread = '0 0 0';
372         spread.z = autocvar_g_random_loot_spread / 2;
373         spread += randomvec() * autocvar_g_random_loot_spread;
374         random_items_is_spawning = true;
375         if (!MUTATOR_IS_ENABLED(ok))
376         {
377                 Item_CreateLoot(class_name, position, spread,
378                         autocvar_g_random_loot_time);
379         }
380         else
381         {
382                 entity item = spawn();
383                 item.ok_item = true;
384                 item.classname = class_name;
385                 Item_InitializeLoot(item, class_name, position, spread,
386                         autocvar_g_random_loot_time);
387         }
388         random_items_is_spawning = false;
389 }
390
391 //============================= Hooks ========================================
392
393 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
394 {
395         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
396 }
397
398 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
399 {
400         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
401 }
402
403 /// \brief Hook that is called when an item is about to spawn.
404 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
405 {
406         //PrintToChatAll("FilterItem");
407         if (!autocvar_g_random_items)
408         {
409                 return false;
410         }
411         if (random_items_is_spawning == true)
412         {
413                 return false;
414         }
415         entity item = M_ARGV(0, entity);
416         if (Item_IsLoot(item))
417         {
418                 return false;
419         }
420         if (RandomItems_ReplaceMapItem(item) == NULL)
421         {
422                 return false;
423         }
424         return true;
425 }
426
427 /// \brief Hook that is called after the player has touched an item.
428 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
429 {
430         //PrintToChatAll("ItemTouched");
431         if (!autocvar_g_random_items)
432         {
433                 return;
434         }
435         entity item = M_ARGV(0, entity);
436         if (Item_IsLoot(item))
437         {
438                 return;
439         }
440         entity new_item = RandomItems_ReplaceMapItem(item);
441         if (new_item == NULL)
442         {
443                 return;
444         }
445         Item_ScheduleRespawn(new_item);
446         delete(item);
447 }
448
449 /// \brief Hook which is called when the player dies.
450 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
451 {
452         //PrintToChatAll("PlayerDies");
453         if (!autocvar_g_random_loot)
454         {
455                 return;
456         }
457         entity victim = M_ARGV(2, entity);
458         vector loot_position = victim.origin + '0 0 32';
459         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
460                 autocvar_g_random_loot_max);
461         for (int item_index = 0; item_index < num_loot_items; ++item_index)
462         {
463                 RandomItems_SpawnLootItem(loot_position);
464         }
465 }