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