]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/mutator/random_items/sv_random_items.qc
Added ITEM_FLAG_NORMAL.
[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         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_HEALTH,
82                 cvar(sprintf("g_%s_health_probability", prefix)), 1);
83         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_ARMOR,
84                 cvar(sprintf("g_%s_armor_probability", prefix)), 1);
85         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_RESOURCE,
86                 cvar(sprintf("g_%s_resource_probability", prefix)), 1);
87         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_WEAPON,
88                 cvar(sprintf("g_%s_weapon_probability", prefix)), 1);
89         RandomSelection_AddFloat(RANDOM_ITEM_TYPE_POWERUP,
90                 cvar(sprintf("g_%s_powerup_probability", prefix)), 1);
91         int item_type = RandomSelection_chosen_float;
92         switch (item_type)
93         {
94                 case RANDOM_ITEM_TYPE_HEALTH:
95                 {
96                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
97                                 instanceOfHealth);
98                 }
99                 case RANDOM_ITEM_TYPE_ARMOR:
100                 {
101                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
102                                 instanceOfArmor);
103                 }
104                 case RANDOM_ITEM_TYPE_RESOURCE:
105                 {
106                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
107                                 instanceOfAmmo);
108                 }
109                 case RANDOM_ITEM_TYPE_WEAPON:
110                 {
111                         RandomSelection_Init();
112                         FOREACH(Weapons, it != WEP_Null &&
113                                 !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED),
114                         {
115                                 string cvar_name = sprintf("g_%s_%s_probability", prefix,
116                                         it.m_canonical_spawnfunc);
117                                 RandomSelection_AddString(it.m_canonical_spawnfunc,
118                                         cvar(cvar_name), 1);
119                         });
120                         return RandomSelection_chosen_string;
121                 }
122                 case RANDOM_ITEM_TYPE_POWERUP:
123                 {
124                         return RandomItems_GetRandomItemClassNameWithProperty(prefix,
125                                 instanceOfPowerup);
126                 }
127         }
128         return "";
129 }
130
131 string RandomItems_GetRandomInstagibItemClassName(string prefix)
132 {
133         RandomSelection_Init();
134         FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB,
135         {
136                 RandomSelection_AddString(it.m_canonical_spawnfunc,
137                         cvar(sprintf("g_%s_%s_probability", prefix,
138                         it.m_canonical_spawnfunc)), 1);
139         });
140         return RandomSelection_chosen_string;
141 }
142
143 string RandomItems_GetRandomOverkillItemClassName(string prefix)
144 {
145         RandomSelection_Init();
146         FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) &&
147                 !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED),
148         {
149                 RandomSelection_AddString(it.m_canonical_spawnfunc,
150                         cvar(sprintf("g_%s_overkill_%s_probability", prefix,
151                         it.m_canonical_spawnfunc)), 1);
152         });
153         RandomSelection_AddString("weapon_hmg",
154                 cvar(sprintf("g_%s_overkill_weapon_hmg_probability", prefix)), 1);
155         RandomSelection_AddString("weapon_rpc",
156                 cvar(sprintf("g_%s_overkill_weapon_rpc_probability", prefix)), 1);
157         return RandomSelection_chosen_string;
158 }
159
160 //========================= Free functions ====================================
161
162 /// \brief Returns list of classnames to replace a map item with.
163 /// \param[in] item Item to inspect.
164 /// \return List of classnames to replace a map item with.
165 string RandomItems_GetItemReplacementClassNames(entity item)
166 {
167         return cvar_string(sprintf("g_random_items_replace_%s", item.classname));
168 }
169
170 string RandomItems_GetRandomItemClassNameWithProperty(string prefix,
171         .bool item_property)
172 {
173         RandomSelection_Init();
174         FOREACH(Items, it.item_property && (it.spawnflags & ITEM_FLAG_NORMAL),
175         {
176                 RandomSelection_AddString(it.m_canonical_spawnfunc,
177                         cvar(sprintf("g_%s_%s_probability", prefix,
178                         it.m_canonical_spawnfunc)), 1);
179         });
180         return RandomSelection_chosen_string;
181 }
182
183 /// \brief Replaces a map item.
184 /// \param[in] item Item to replace.
185 /// \return Spawned item on success, NULL otherwise.
186 entity RandomItems_ReplaceMapItem(entity item)
187 {
188         //PrintToChatAll(strcat("Replacing ", item.classname));
189         string new_classnames = RandomItems_GetItemReplacementClassNames(item);
190         if (new_classnames == "")
191         {
192                 return NULL;
193         }
194         string new_classname;
195         if (new_classnames == "random")
196         {
197                 new_classname = RandomItems_GetRandomItemClassName("random_items");
198                 if (new_classname == "")
199                 {
200                         return NULL;
201                 }
202         }
203         else
204         {
205                 int num_new_classnames = tokenize_console(new_classnames);
206                 if (num_new_classnames == 1)
207                 {
208                         new_classname = new_classnames;
209                 }
210                 else
211                 {
212                         int classname_index = floor(random() * num_new_classnames);
213                         new_classname = argv(classname_index);
214                 }
215         }
216         //PrintToChatAll(strcat("Replacing with ", new_classname));
217         if (new_classname == item.classname)
218         {
219                 return NULL;
220         }
221         random_items_is_spawning = true;
222         entity new_item;
223         if (!expr_evaluate(autocvar_g_overkill))
224         {
225                 new_item = Item_Create(strzone(new_classname), item.origin);
226                 random_items_is_spawning = false;
227                 if (new_item == NULL)
228                 {
229                         return NULL;
230                 }
231         }
232         else
233         {
234                 new_item = spawn();
235                 new_item.classname = strzone(new_classname);
236                 new_item.spawnfunc_checked = true;
237                 new_item.ok_item = true;
238                 Item_Initialize(new_item, new_classname);
239                 random_items_is_spawning = false;
240                 if (wasfreed(new_item))
241                 {
242                         return NULL;
243                 }
244                 setorigin(new_item, item.origin);
245         }
246         if (item.team)
247         {
248                 new_item.team = item.team;
249         }
250         return new_item;
251 }
252
253 /// \brief Spawns a random loot item.
254 /// \param[in] position Position of the item.
255 /// \return No return.
256 void RandomItems_SpawnLootItem(vector position)
257 {
258         string class_name = RandomItems_GetRandomItemClassName("random_loot");
259         if (class_name == "")
260         {
261                 return;
262         }
263         vector spread = '0 0 0';
264         spread.z = autocvar_g_random_loot_spread / 2;
265         spread += randomvec() * autocvar_g_random_loot_spread;
266         random_items_is_spawning = true;
267         if (!expr_evaluate(autocvar_g_overkill))
268         {
269                 Item_CreateLoot(class_name, position, spread,
270                         autocvar_g_random_loot_time);
271         }
272         else
273         {
274                 entity item = spawn();
275                 item.ok_item = true;
276                 item.classname = class_name;
277                 Item_InitializeLoot(item, class_name, position, spread,
278                         autocvar_g_random_loot_time);
279         }
280         random_items_is_spawning = false;
281 }
282
283 //============================= Hooks ========================================
284
285 REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
286         autocvar_g_random_loot));
287
288 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsString)
289 {
290         M_ARGV(0, string) = strcat(M_ARGV(0, string), ":random_items");
291 }
292
293 MUTATOR_HOOKFUNCTION(random_items, BuildMutatorsPrettyString)
294 {
295         M_ARGV(0, string) = strcat(M_ARGV(0, string), ", Random items");
296 }
297
298 /// \brief Hook that is called when an item is about to spawn.
299 MUTATOR_HOOKFUNCTION(random_items, FilterItem, CBC_ORDER_LAST)
300 {
301         //PrintToChatAll("FilterItem");
302         if (!autocvar_g_random_items)
303         {
304                 return false;
305         }
306         if (random_items_is_spawning == true)
307         {
308                 return false;
309         }
310         entity item = M_ARGV(0, entity);
311         if (Item_IsLoot(item))
312         {
313                 return false;
314         }
315         if (RandomItems_ReplaceMapItem(item) == NULL)
316         {
317                 return false;
318         }
319         return true;
320 }
321
322 /// \brief Hook that is called after the player has touched an item.
323 MUTATOR_HOOKFUNCTION(random_items, ItemTouched, CBC_ORDER_LAST)
324 {
325         //PrintToChatAll("ItemTouched");
326         if (!autocvar_g_random_items)
327         {
328                 return;
329         }
330         entity item = M_ARGV(0, entity);
331         if (Item_IsLoot(item))
332         {
333                 return;
334         }
335         entity new_item = RandomItems_ReplaceMapItem(item);
336         if (new_item == NULL)
337         {
338                 return;
339         }
340         Item_ScheduleRespawn(new_item);
341         delete(item);
342 }
343
344 /// \brief Hook which is called when the player dies.
345 MUTATOR_HOOKFUNCTION(random_items, PlayerDies)
346 {
347         //PrintToChatAll("PlayerDies");
348         if (!autocvar_g_random_loot)
349         {
350                 return;
351         }
352         entity victim = M_ARGV(2, entity);
353         vector loot_position = victim.origin + '0 0 32';
354         int num_loot_items = floor(autocvar_g_random_loot_min + random() *
355                 autocvar_g_random_loot_max);
356         for (int item_index = 0; item_index < num_loot_items; ++item_index)
357         {
358                 RandomItems_SpawnLootItem(loot_position);
359         }
360 }