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