]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/items/items.qc
Fix StartItem indenting
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / items / items.qc
1 #include "items.qh"
2
3 #include <common/items/_mod.qh>
4
5 #include <server/bot/api.qh>
6
7 #include <server/command/vote.qh>
8
9 #include <server/damage.qh>
10
11 #include <server/mutators/_mod.qh>
12
13 #include <server/teamplay.qh>
14
15 #include <server/weapons/common.qh>
16 #include <server/weapons/selection.qh>
17 #include <server/weapons/weaponsystem.qh>
18
19 #include <common/constants.qh>
20 #include <common/deathtypes/all.qh>
21 #include <common/notifications/all.qh>
22 #include <common/mapobjects/subs.qh>
23 #include <common/mapobjects/triggers.qh>
24 #include <common/util.qh>
25
26 #include <common/monsters/_mod.qh>
27
28 #include <common/wepent.qh>
29 #include <common/weapons/_all.qh>
30
31 #include <common/mutators/mutator/buffs/buffs.qh>
32 #include <common/mutators/mutator/buffs/sv_buffs.qh>
33
34 #include <lib/warpzone/util_server.qh>
35
36 bool ItemSend(entity this, entity to, int sf)
37 {
38         if(this.gravity)
39                 sf |= ISF_DROP;
40         else
41                 sf &= ~ISF_DROP;
42
43         WriteHeader(MSG_ENTITY, ENT_CLIENT_ITEM);
44         WriteByte(MSG_ENTITY, sf);
45
46         //WriteByte(MSG_ENTITY, this.cnt);
47         if(sf & ISF_LOCATION)
48         {
49                 WriteVector(MSG_ENTITY, this.origin);
50         }
51
52         if(sf & ISF_ANGLES)
53         {
54                 WriteAngleVector(MSG_ENTITY, this.angles);
55         }
56
57         // sets size on the client, unused on server
58         //if(sf & ISF_SIZE)
59
60         if(sf & ISF_STATUS)
61                 WriteByte(MSG_ENTITY, this.ItemStatus);
62
63         if(sf & ISF_MODEL)
64         {
65                 WriteShort(MSG_ENTITY, this.fade_end);
66                 WriteShort(MSG_ENTITY, this.fade_start);
67
68                 if(this.mdl == "")
69                         LOG_TRACE("^1WARNING!^7 this.mdl is unset for item ", this.classname, "expect a crash just about now");
70
71                 WriteString(MSG_ENTITY, this.mdl);
72         }
73
74
75         if(sf & ISF_COLORMAP)
76         {
77                 WriteShort(MSG_ENTITY, this.colormap);
78                 WriteByte(MSG_ENTITY, this.glowmod.x * 255.0);
79                 WriteByte(MSG_ENTITY, this.glowmod.y * 255.0);
80                 WriteByte(MSG_ENTITY, this.glowmod.z * 255.0);
81         }
82
83         if(sf & ISF_DROP)
84         {
85                 WriteVector(MSG_ENTITY, this.velocity);
86         }
87
88         return true;
89 }
90
91 void ItemUpdate(entity this)
92 {
93         this.oldorigin = this.origin;
94         this.SendFlags |= ISF_LOCATION;
95 }
96
97 void UpdateItemAfterTeleport(entity this)
98 {
99         if(getSendEntity(this) == ItemSend)
100                 ItemUpdate(this);
101 }
102
103 bool have_pickup_item(entity this)
104 {
105         if(this.itemdef.instanceOfPowerup)
106         {
107                 if(autocvar_g_powerups > 0)
108                         return true;
109                 if(autocvar_g_powerups == 0)
110                         return false;
111         }
112         else
113         {
114                 if(autocvar_g_pickup_items > 0)
115                         return true;
116                 if(autocvar_g_pickup_items == 0)
117                         return false;
118                 if(g_weaponarena)
119                         if(STAT(WEAPONS, this) || this.itemdef.instanceOfAmmo) // no item or ammo pickups in weaponarena
120                                 return false;
121         }
122         return true;
123 }
124
125 void Item_Show(entity e, int mode)
126 {
127         e.effects &= ~(EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST);
128         e.ItemStatus &= ~ITS_STAYWEP;
129         entity def = e.itemdef;
130         if (mode > 0)
131         {
132                 // make the item look normal, and be touchable
133                 e.model = e.mdl;
134                 e.solid = SOLID_TRIGGER;
135                 e.spawnshieldtime = 1;
136                 e.ItemStatus |= ITS_AVAILABLE;
137         }
138         else if (mode < 0)
139         {
140                 // hide the item completely
141                 e.model = string_null;
142                 e.solid = SOLID_NOT;
143                 e.spawnshieldtime = 1;
144                 e.ItemStatus &= ~ITS_AVAILABLE;
145         }
146         else
147         {
148                 bool nostay = def.instanceOfWeaponPickup ? !!(def.m_weapon.m_wepset & WEPSET_SUPERWEAPONS) : false // no weapon-stay on superweapons
149                         || e.team // weapon stay isn't supported for teamed weapons
150                         ;
151                 if(def.instanceOfWeaponPickup && !nostay && g_weapon_stay)
152                 {
153                         // make the item translucent and not touchable
154                         e.model = e.mdl;
155                         e.solid = SOLID_TRIGGER; // can STILL be picked up!
156                         e.effects |= EF_STARDUST;
157                         e.spawnshieldtime = 0; // field indicates whether picking it up may give you anything other than the weapon
158                         e.ItemStatus |= (ITS_AVAILABLE | ITS_STAYWEP);
159                 }
160                 else
161                 {
162                         //setmodel(e, "null");
163                         e.solid = SOLID_NOT;
164                         e.colormod = '0 0 0';
165                         //e.glowmod = e.colormod;
166                         e.spawnshieldtime = 1;
167                         e.ItemStatus &= ~ITS_AVAILABLE;
168                 }
169         }
170
171         if (def.m_glow)
172                 e.ItemStatus |= ITS_GLOW;
173
174         if (autocvar_g_nodepthtestitems)
175                 e.effects |= EF_NODEPTHTEST;
176
177         if (autocvar_g_fullbrightitems)
178                 e.ItemStatus |= ITS_ALLOWFB;
179         else
180                 e.ItemStatus &= ~ITS_ALLOWFB;
181
182         if (autocvar_sv_simple_items)
183                 e.ItemStatus |= ITS_ALLOWSI;
184
185         // relink entity (because solid may have changed)
186         setorigin(e, e.origin);
187         e.SendFlags |= ISF_STATUS;
188 }
189
190 void Item_Think(entity this)
191 {
192         this.nextthink = time;
193         if(this.origin != this.oldorigin)
194                 ItemUpdate(this);
195 }
196
197 bool Item_ItemsTime_SpectatorOnly(GameItem it);
198 bool Item_ItemsTime_Allow(GameItem it);
199 float Item_ItemsTime_UpdateTime(entity e, float t);
200 void Item_ItemsTime_SetTime(entity e, float t);
201 void Item_ItemsTime_SetTimesForAllPlayers();
202
203 void Item_Respawn(entity this)
204 {
205         Item_Show(this, 1);
206         sound(this, CH_TRIGGER, this.itemdef.m_respawnsound, VOL_BASE, ATTEN_NORM);     // play respawn sound
207         setorigin(this, this.origin);
208
209         if (Item_ItemsTime_Allow(this.itemdef) || (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
210         {
211                 float t = Item_ItemsTime_UpdateTime(this, 0);
212                 Item_ItemsTime_SetTime(this, t);
213                 Item_ItemsTime_SetTimesForAllPlayers();
214         }
215
216         setthink(this, Item_Think);
217         this.nextthink = time;
218
219         //Send_Effect(EFFECT_ITEM_RESPAWN, this.origin + this.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
220         Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
221 }
222
223 void Item_RespawnCountdown(entity this)
224 {
225         if(this.item_respawncounter >= ITEM_RESPAWN_TICKS)
226         {
227                 if(this.waypointsprite_attached)
228                         WaypointSprite_Kill(this.waypointsprite_attached);
229                 Item_Respawn(this);
230         }
231         else
232         {
233                 this.nextthink = time + 1;
234                 this.item_respawncounter += 1;
235                 if(this.item_respawncounter == 1)
236                 {
237                         do {
238                                 {
239                                         entity wi = REGISTRY_GET(Weapons, this.weapon);
240                                         if (wi != WEP_Null) {
241                                                 entity wp = WaypointSprite_Spawn(WP_Weapon, 0, 0, this, '0 0 64', NULL, 0, this, waypointsprite_attached, true, RADARICON_Weapon);
242                                                 wp.wp_extra = wi.m_id;
243                                                 break;
244                                         }
245                                 }
246                                 {
247                                         entity ii = this.itemdef;
248                                         if (ii != NULL) {
249                                                 entity wp = WaypointSprite_Spawn(WP_Item, 0, 0, this, '0 0 64', NULL, 0, this, waypointsprite_attached, true, RADARICON_Item);
250                                                 wp.wp_extra = ii.m_id;
251                                                 break;
252                                         }
253                                 }
254                         } while (0);
255                         bool mutator_returnvalue = MUTATOR_CALLHOOK(Item_RespawnCountdown, this);
256             if(this.waypointsprite_attached)
257             {
258                 GameItem def = this.itemdef;
259                 if (Item_ItemsTime_SpectatorOnly(def) && !mutator_returnvalue)
260                     WaypointSprite_UpdateRule(this.waypointsprite_attached, 0, SPRITERULE_SPECTATOR);
261                 WaypointSprite_UpdateBuildFinished(this.waypointsprite_attached, time + ITEM_RESPAWN_TICKS);
262             }
263                 }
264
265                 if(this.waypointsprite_attached)
266                 {
267                         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
268                                 if(this.waypointsprite_attached.waypointsprite_visible_for_player(this.waypointsprite_attached, it, it))
269                                 {
270                                         msg_entity = it;
271                                         soundto(MSG_ONE, this, CH_TRIGGER, SND(ITEMRESPAWNCOUNTDOWN), VOL_BASE, ATTEN_NORM, 0); // play respawn sound
272                                 }
273                         });
274
275                         WaypointSprite_Ping(this.waypointsprite_attached);
276                         //WaypointSprite_UpdateHealth(this.waypointsprite_attached, this.item_respawncounter);
277                 }
278         }
279 }
280
281 void Item_RespawnThink(entity this)
282 {
283         this.nextthink = time;
284         if(this.origin != this.oldorigin)
285                 ItemUpdate(this);
286
287         if(time >= this.wait)
288                 Item_Respawn(this);
289 }
290
291 void Item_ScheduleRespawnIn(entity e, float t)
292 {
293         // if the respawn time is longer than 10 seconds, show a waypoint, otherwise, just respawn normally
294         if ((Item_ItemsTime_Allow(e.itemdef) || (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS) || MUTATOR_CALLHOOK(Item_ScheduleRespawn, e, t)) && (t - ITEM_RESPAWN_TICKS) > 0)
295         {
296                 setthink(e, Item_RespawnCountdown);
297                 e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
298                 e.scheduledrespawntime = e.nextthink + ITEM_RESPAWN_TICKS;
299                 e.item_respawncounter = 0;
300                 if(Item_ItemsTime_Allow(e.itemdef) || (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS))
301                 {
302                         t = Item_ItemsTime_UpdateTime(e, e.scheduledrespawntime);
303                         Item_ItemsTime_SetTime(e, t);
304                         Item_ItemsTime_SetTimesForAllPlayers();
305                 }
306         }
307         else
308         {
309                 setthink(e, Item_RespawnThink);
310                 e.nextthink = time;
311                 e.scheduledrespawntime = time + t;
312                 e.wait = time + t;
313
314                 if(Item_ItemsTime_Allow(e.itemdef) || (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS))
315                 {
316                         t = Item_ItemsTime_UpdateTime(e, e.scheduledrespawntime);
317                         Item_ItemsTime_SetTime(e, t);
318                         Item_ItemsTime_SetTimesForAllPlayers();
319                 }
320         }
321 }
322
323 AUTOCVAR(g_pickup_respawntime_scaling_reciprocal, float, 0.0, "Multiply respawn time by `reciprocal / (p + offset) + linear` where `p` is the current number of players, takes effect with 2 or more players present, `reciprocal` (with `offset` and `linear` set to 0) can be used to achieve a constant number of items spawned *per player*");
324 AUTOCVAR(g_pickup_respawntime_scaling_offset, float, 0.0, "Multiply respawn time by `reciprocal / (p + offset) + linear` where `p` is the current number of players, takes effect with 2 or more players present, `offset` offsets the curve left or right - the results are not intuitive and I recommend plotting the respawn time and the number of items per player to see what's happening");
325 AUTOCVAR(g_pickup_respawntime_scaling_linear, float, 1.0, "Multiply respawn time by `reciprocal / (p + offset) + linear` where `p` is the current number of players, takes effect with 2 or more players present, `linear` can be used to simply scale the respawn time linearly");
326
327 /// Adjust respawn time according to the number of players.
328 float adjust_respawntime(float normal_respawntime) {
329         float r = autocvar_g_pickup_respawntime_scaling_reciprocal;
330         float o = autocvar_g_pickup_respawntime_scaling_offset;
331         float l = autocvar_g_pickup_respawntime_scaling_linear;
332
333         if (r == 0 && l == 1) {
334                 return normal_respawntime;
335         }
336
337         entity balance = TeamBalance_CheckAllowedTeams(NULL);
338         TeamBalance_GetTeamCounts(balance, NULL);
339         int players = 0;
340         for (int i = 1; i <= NUM_TEAMS; ++i)
341         {
342                 if (TeamBalance_IsTeamAllowed(balance, i))
343                 {
344                         players += TeamBalance_GetNumberOfPlayers(balance, i);
345                 }
346         }
347         TeamBalance_Destroy(balance);
348
349         if (players >= 2) {
350                 return normal_respawntime * (r / (players + o) + l);
351         } else {
352                 return normal_respawntime;
353         }
354 }
355
356 void Item_ScheduleRespawn(entity e)
357 {
358         if(e.respawntime > 0)
359         {
360                 Item_Show(e, 0);
361
362                 float adjusted_respawntime = adjust_respawntime(e.respawntime);
363                 //LOG_INFOF("item %s will respawn in %f", e.classname, adjusted_respawntime);
364
365                 // range: adjusted_respawntime - respawntimejitter .. adjusted_respawntime + respawntimejitter
366                 float respawn_in = adjusted_respawntime + crandom() * e.respawntimejitter;
367                 Item_ScheduleRespawnIn(e, respawn_in);
368         }
369         else // if respawntime is -1, this item does not respawn
370                 Item_Show(e, -1);
371 }
372
373 AUTOCVAR(g_pickup_respawntime_initial_random, int, 1,
374         "For items that don't start spawned: 0: spawn after their normal respawntime; 1: spawn after `random * respawntime` with the *same* random; 2: same as 1 but each item has separate random");
375
376 void Item_ScheduleInitialRespawn(entity e)
377 {
378         Item_Show(e, 0);
379
380         float spawn_in;
381         if (autocvar_g_pickup_respawntime_initial_random == 0)
382         {
383                 // range: respawntime .. respawntime + respawntimejitter
384                 spawn_in = e.respawntime + random() * e.respawntimejitter;
385         }
386         else
387         {
388                 float rnd;
389                 if (autocvar_g_pickup_respawntime_initial_random == 1)
390                 {
391                         static float shared_random = 0;
392                         // NOTE this code works only if items are scheduled at the same time (normal case)
393                         // NOTE2 random() can't return exactly 1 so this check always work as intended
394                         if (!shared_random || floor(time) > shared_random)
395                                 shared_random = floor(time) + random();
396                         rnd = shared_random - floor(time);
397                 }
398                 else
399                         rnd = random();
400
401                 // range:
402                 // if respawntime >= ITEM_RESPAWN_TICKS: ITEM_RESPAWN_TICKS .. respawntime + respawntimejitter
403                 // else: 0 .. ITEM_RESPAWN_TICKS
404                 // this is to prevent powerups spawning unexpectedly without waypoints
405                 spawn_in = ITEM_RESPAWN_TICKS + rnd * (e.respawntime + e.respawntimejitter - ITEM_RESPAWN_TICKS);
406         }
407
408         Item_ScheduleRespawnIn(e, max(0, game_starttime - time) + ((e.respawntimestart) ? e.respawntimestart : spawn_in));
409 }
410
411 void GiveRandomWeapons(entity receiver, int num_weapons, string weapon_names,
412         entity ammo_entity)
413 {
414         if (num_weapons == 0)
415         {
416                 return;
417         }
418         int num_potential_weapons = tokenize_console(weapon_names);
419         for (int give_attempt = 0; give_attempt < num_weapons; ++give_attempt)
420         {
421                 RandomSelection_Init();
422                 for (int weapon_index = 0; weapon_index < num_potential_weapons;
423                         ++weapon_index)
424                 {
425                         string weapon = argv(weapon_index);
426                         FOREACH(Weapons, it != WEP_Null,
427                         {
428                                 // Finding a weapon which player doesn't have.
429                                 if (!(STAT(WEAPONS, receiver) & it.m_wepset) && (it.netname == weapon))
430                                 {
431                                         RandomSelection_AddEnt(it, 1, 1);
432                                         break;
433                                 }
434                         });
435                 }
436                 if (RandomSelection_chosen_ent == NULL)
437                 {
438                         return;
439                 }
440                 STAT(WEAPONS, receiver) |= RandomSelection_chosen_ent.m_wepset;
441                 if (RandomSelection_chosen_ent.ammo_type == RES_NONE)
442                 {
443                         continue;
444                 }
445                 if (GetResource(receiver,
446                         RandomSelection_chosen_ent.ammo_type) != 0)
447                 {
448                         continue;
449                 }
450                 GiveResource(receiver, RandomSelection_chosen_ent.ammo_type,
451                         GetResource(ammo_entity,
452                         RandomSelection_chosen_ent.ammo_type));
453         }
454 }
455
456 bool Item_GiveAmmoTo(entity item, entity player, int res_type, float ammomax)
457 {
458         float amount = GetResource(item, res_type);
459         if (amount == 0)
460         {
461                 return false;
462         }
463         float player_amount = GetResource(player, res_type);
464         if (item.spawnshieldtime)
465         {
466                 if ((player_amount >= ammomax) && (item.pickup_anyway <= 0))
467                         return false;
468         }
469         else if (g_weapon_stay == 2)
470         {
471                 ammomax = min(amount, ammomax);
472                 if(player_amount >= ammomax)
473                         return false;
474         }
475         else
476                 return false;
477         if (amount < 0)
478                 TakeResourceWithLimit(player, res_type, -amount, ammomax);
479         else
480                 GiveResourceWithLimit(player, res_type, amount, ammomax);
481         return true;
482 }
483
484 bool Item_GiveTo(entity item, entity player)
485 {
486         // if nothing happens to player, just return without taking the item
487         int _switchweapon = 0;
488         // in case the player has autoswitch enabled do the following:
489         // if the player is using their best weapon before items are given, they
490         // probably want to switch to an even better weapon after items are given
491
492         if(CS(player).autoswitch)
493         {
494                 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
495                 {
496                         .entity weaponentity = weaponentities[slot];
497                         if(player.(weaponentity).m_weapon != WEP_Null || slot == 0)
498                         {
499                                 if(player.(weaponentity).m_switchweapon == w_getbestweapon(player, weaponentity))
500                                         _switchweapon |= BIT(slot);
501
502                                 if(!(STAT(WEAPONS, player) & WepSet_FromWeapon(player.(weaponentity).m_switchweapon)))
503                                         _switchweapon |= BIT(slot);
504                         }
505                 }
506         }
507         bool pickedup = false;
508         pickedup |= Item_GiveAmmoTo(item, player, RES_HEALTH, item.max_health);
509         pickedup |= Item_GiveAmmoTo(item, player, RES_ARMOR, item.max_armorvalue);
510         pickedup |= Item_GiveAmmoTo(item, player, RES_SHELLS, g_pickup_shells_max);
511         pickedup |= Item_GiveAmmoTo(item, player, RES_BULLETS, g_pickup_nails_max);
512         pickedup |= Item_GiveAmmoTo(item, player, RES_ROCKETS, g_pickup_rockets_max);
513         pickedup |= Item_GiveAmmoTo(item, player, RES_CELLS, g_pickup_cells_max);
514         pickedup |= Item_GiveAmmoTo(item, player, RES_PLASMA, g_pickup_plasma_max);
515         pickedup |= Item_GiveAmmoTo(item, player, RES_FUEL, g_pickup_fuel_max);
516         if (item.itemdef.instanceOfWeaponPickup)
517         {
518                 WepSet w;
519                 w = STAT(WEAPONS, item);
520                 w &= ~STAT(WEAPONS, player);
521
522                 if (w || (item.spawnshieldtime && item.pickup_anyway > 0))
523                 {
524                         pickedup = true;
525                         FOREACH(Weapons, it != WEP_Null, {
526                                 if(w & (it.m_wepset))
527                                 {
528                                         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
529                                         {
530                                                 .entity weaponentity = weaponentities[slot];
531                                                 if(player.(weaponentity).m_weapon != WEP_Null || slot == 0)
532                                                         W_DropEvent(wr_pickup, player, it.m_id, item, weaponentity);
533                                         }
534                                         W_GiveWeapon(player, it.m_id);
535                                 }
536                         });
537                 }
538         }
539
540         if (item.itemdef.instanceOfPowerup)
541         {
542                 if ((item.itemdef == ITEM_JetpackRegen) && !(player.items & IT_FUEL_REGEN))
543                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ITEM_FUELREGEN_GOT);
544                 else if ((item.itemdef == ITEM_Jetpack) && !(player.items & IT_JETPACK))
545                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ITEM_JETPACK_GOT);
546         }
547
548         int its;
549         if((its = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
550         {
551                 pickedup = true;
552                 player.items |= its;
553                 // TODO: we probably want to show a message in the console, but not this one!
554                 //Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname);
555         }
556
557         if (item.strength_finished)
558         {
559                 pickedup = true;
560                 STAT(STRENGTH_FINISHED, player) = max(STAT(STRENGTH_FINISHED, player), time) + item.strength_finished;
561         }
562         if (item.invincible_finished)
563         {
564                 pickedup = true;
565                 STAT(INVINCIBLE_FINISHED, player) = max(STAT(INVINCIBLE_FINISHED, player), time) + item.invincible_finished;
566         }
567         if (item.superweapons_finished)
568         {
569                 pickedup = true;
570                 STAT(SUPERWEAPONS_FINISHED, player) = max(STAT(SUPERWEAPONS_FINISHED, player), time) + item.superweapons_finished;
571         }
572
573         // always eat teamed entities
574         if(item.team)
575                 pickedup = true;
576
577         if (!pickedup)
578                 return false;
579
580         // crude hack to enforce switching weapons
581         if(g_cts && item.itemdef.instanceOfWeaponPickup && !CS(player).cvar_cl_cts_noautoswitch)
582         {
583                 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
584                 {
585                         .entity weaponentity = weaponentities[slot];
586                         if(player.(weaponentity).m_weapon != WEP_Null || slot == 0)
587                                 W_SwitchWeapon_Force(player, REGISTRY_GET(Weapons, item.weapon), weaponentity);
588                 }
589                 return true;
590         }
591
592         if(_switchweapon)
593         {
594                 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
595                 {
596                         .entity weaponentity = weaponentities[slot];
597                         if(_switchweapon & BIT(slot))
598                         if(player.(weaponentity).m_switchweapon != w_getbestweapon(player, weaponentity))
599                                 W_SwitchWeapon_Force(player, w_getbestweapon(player, weaponentity), weaponentity);
600                 }
601         }
602
603         return true;
604 }
605
606 void Item_Touch(entity this, entity toucher)
607 {
608         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
609         if (Item_IsLoot(this))
610         {
611                 if (ITEM_TOUCH_NEEDKILL())
612                 {
613                         delete(this);
614                         return;
615                 }
616         }
617
618         if(!(toucher.flags & FL_PICKUPITEMS)
619         || STAT(FROZEN, toucher)
620         || IS_DEAD(toucher)
621         || (this.solid != SOLID_TRIGGER)
622         || (this.owner == toucher)
623         || (time < this.item_spawnshieldtime)
624         ) { return; }
625
626         switch (MUTATOR_CALLHOOK(ItemTouch, this, toucher))
627         {
628                 case MUT_ITEMTOUCH_RETURN: { return; }
629                 case MUT_ITEMTOUCH_PICKUP: { toucher = M_ARGV(1, entity); goto pickup; }
630         }
631
632         toucher = M_ARGV(1, entity);
633
634         if (Item_IsExpiring(this))
635         {
636                 this.strength_finished = max(0, this.strength_finished - time);
637                 this.invincible_finished = max(0, this.invincible_finished - time);
638                 this.superweapons_finished = max(0, this.superweapons_finished - time);
639         }
640         bool gave = ITEM_HANDLE(Pickup, this.itemdef, this, toucher);
641         if (!gave)
642         {
643                 if (Item_IsExpiring(this))
644                 {
645                         // undo what we did above
646                         this.strength_finished += time;
647                         this.invincible_finished += time;
648                         this.superweapons_finished += time;
649                 }
650                 return;
651         }
652
653 LABEL(pickup)
654
655         if(this.target && this.target != "" && this.target != "###item###") // defrag support
656                 SUB_UseTargets(this, toucher, NULL);
657
658         STAT(LAST_PICKUP, toucher) = time;
659
660         Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
661         _sound (toucher, (this.itemdef.instanceOfPowerup ? CH_TRIGGER_SINGLE : CH_TRIGGER), (this.item_pickupsound ? this.item_pickupsound : Sound_fixpath(this.item_pickupsound_ent)), VOL_BASE, ATTEN_NORM);
662
663         MUTATOR_CALLHOOK(ItemTouched, this, toucher);
664         if (wasfreed(this))
665         {
666                 return;
667         }
668
669         if (Item_IsLoot(this))
670         {
671                 delete(this);
672                 return;
673         }
674         if (!this.spawnshieldtime)
675         {
676                 return;
677         }
678         entity e;
679         if (this.team)
680         {
681                 RandomSelection_Init();
682                 IL_EACH(g_items, it.team == this.team,
683                 {
684                         if (it.itemdef) // is a registered item
685                         {
686                                 Item_Show(it, -1);
687                                 it.scheduledrespawntime = 0;
688                                 RandomSelection_AddEnt(it, it.cnt, 0);
689                         }
690                 });
691                 e = RandomSelection_chosen_ent;
692                 Item_Show(e, 1); // reset its state so it is visible (extra sendflags doesn't matter, this happens anyway)
693         }
694         else
695                 e = this;
696         Item_ScheduleRespawn(e);
697 }
698
699 void Item_Reset(entity this)
700 {
701         Item_Show(this, !this.state);
702         setorigin(this, this.origin);
703
704         if (Item_IsLoot(this))
705         {
706                 return;
707         }
708         setthink(this, Item_Think);
709         this.nextthink = time;
710         if (this.waypointsprite_attached)
711         {
712                 WaypointSprite_Kill(this.waypointsprite_attached);
713         }
714         if (this.itemdef.instanceOfPowerup || (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
715         {
716                 Item_ScheduleInitialRespawn(this);
717         }
718 }
719
720 void Item_FindTeam(entity this)
721 {
722         entity e;
723
724         if(this.effects & EF_NODRAW)
725         {
726                 // marker for item team search
727                 LOG_TRACE("Initializing item team ", ftos(this.team));
728                 RandomSelection_Init();
729                 IL_EACH(g_items, it.team == this.team,
730                 {
731                         if(it.itemdef) // is a registered item
732                                 RandomSelection_AddEnt(it, it.cnt, 0);
733                 });
734
735                 e = RandomSelection_chosen_ent;
736                 if (!e)
737                         return;
738
739                 IL_EACH(g_items, it.team == this.team,
740                 {
741                         if(it.itemdef) // is a registered item
742                         {
743                                 if(it != e)
744                                 {
745                                         // make it non-spawned
746                                         Item_Show(it, -1);
747                                         it.state = 1; // state 1 = initially hidden item, apparently
748                                 }
749                                 else
750                                         Item_Reset(it);
751                                 it.effects &= ~EF_NODRAW;
752                         }
753                 });
754         }
755 }
756
757 // Savage: used for item garbage-collection
758 void RemoveItem(entity this)
759 {
760         if(wasfreed(this) || !this) { return; }
761         Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(this), '0 0 0', 1);
762         delete(this);
763 }
764
765 // pickup evaluation functions
766 // these functions decide how desirable an item is to the bots
767
768 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;}
769
770 float weapon_pickupevalfunc(entity player, entity item)
771 {
772         // See if I have it already
773         if(STAT(WEAPONS, player) & STAT(WEAPONS, item))
774         {
775                 // If I can pick it up
776                 if(!item.spawnshieldtime)
777                         return 0;
778                 return ammo_pickupevalfunc(player, item);
779         }
780
781         // reduce weapon value if bot already got a good arsenal
782         float c = 1;
783         int weapons_value = 0;
784         FOREACH(Weapons, it != WEP_Null && (STAT(WEAPONS, player) & it.m_wepset), {
785                 weapons_value += it.bot_pickupbasevalue;
786         });
787         c -= bound(0, weapons_value / 20000, 1) * 0.5;
788
789         return item.bot_pickupbasevalue * c;
790 }
791
792 float ammo_pickupevalfunc(entity player, entity item)
793 {
794         bool need_shells = false, need_nails = false, need_rockets = false, need_cells = false, need_plasma = false, need_fuel = false;
795         entity wpn = NULL;
796         float c = 0;
797         float rating = 0;
798
799         // Detect needed ammo
800         if(item.itemdef.instanceOfWeaponPickup)
801         {
802                 entity ammo = NULL;
803                 if(GetResource(item, RES_SHELLS))       { need_shells  = true; ammo = ITEM_Shells;      }
804                 else if(GetResource(item, RES_BULLETS))   { need_nails   = true; ammo = ITEM_Bullets;     }
805                 else if(GetResource(item, RES_ROCKETS)) { need_rockets = true; ammo = ITEM_Rockets;     }
806                 else if(GetResource(item, RES_CELLS))   { need_cells   = true; ammo = ITEM_Cells;       }
807                 else if(GetResource(item, RES_PLASMA))  { need_plasma  = true; ammo = ITEM_Plasma;      }
808                 else if(GetResource(item, RES_FUEL))    { need_fuel    = true; ammo = ITEM_JetpackFuel; }
809
810                 if(!ammo)
811                         return 0;
812                 wpn = item;
813                 rating = ammo.m_botvalue;
814         }
815         else
816         {
817                 FOREACH(Weapons, it != WEP_Null, {
818                         if(!(STAT(WEAPONS, player) & (it.m_wepset)))
819                                 continue;
820
821                         switch(it.ammo_type)
822                         {
823                                 case RES_SHELLS:  need_shells  = true; break;
824                                 case RES_BULLETS: need_nails   = true; break;
825                                 case RES_ROCKETS: need_rockets = true; break;
826                                 case RES_CELLS:   need_cells   = true; break;
827                                 case RES_PLASMA:  need_plasma  = true; break;
828                                 case RES_FUEL:    need_fuel    = true; break;
829                         }
830                 });
831                 rating = item.bot_pickupbasevalue;
832         }
833
834         float noammorating = 0.5;
835
836         if ((need_shells) && GetResource(item, RES_SHELLS) && (GetResource(player, RES_SHELLS) < g_pickup_shells_max))
837                 c = GetResource(item, RES_SHELLS) / max(noammorating, GetResource(player, RES_SHELLS));
838
839         if ((need_nails) && GetResource(item, RES_BULLETS) && (GetResource(player, RES_BULLETS) < g_pickup_nails_max))
840                 c = GetResource(item, RES_BULLETS) / max(noammorating, GetResource(player, RES_BULLETS));
841
842         if ((need_rockets) && GetResource(item, RES_ROCKETS) && (GetResource(player, RES_ROCKETS) < g_pickup_rockets_max))
843                 c = GetResource(item, RES_ROCKETS) / max(noammorating, GetResource(player, RES_ROCKETS));
844
845         if ((need_cells) && GetResource(item, RES_CELLS) && (GetResource(player, RES_CELLS) < g_pickup_cells_max))
846                 c = GetResource(item, RES_CELLS) / max(noammorating, GetResource(player, RES_CELLS));
847
848         if ((need_plasma) && GetResource(item, RES_PLASMA) && (GetResource(player, RES_PLASMA) < g_pickup_plasma_max))
849                 c = GetResource(item, RES_PLASMA) / max(noammorating, GetResource(player, RES_PLASMA));
850
851         if ((need_fuel) && GetResource(item, RES_FUEL) && (GetResource(player, RES_FUEL) < g_pickup_fuel_max))
852                 c = GetResource(item, RES_FUEL) / max(noammorating, GetResource(player, RES_FUEL));
853
854         rating *= min(c, 2);
855         if(wpn)
856                 rating += wpn.bot_pickupbasevalue * 0.1;
857         return rating;
858 }
859
860 float healtharmor_pickupevalfunc(entity player, entity item)
861 {
862         float c = 0;
863         float rating = item.bot_pickupbasevalue;
864
865         float itemarmor = GetResource(item, RES_ARMOR);
866         float itemhealth = GetResource(item, RES_HEALTH);
867
868         if(item.item_group)
869         {
870                 itemarmor *= min(4, item.item_group_count);
871                 itemhealth *= min(4, item.item_group_count);
872         }
873
874         if (itemarmor && (GetResource(player, RES_ARMOR) < item.max_armorvalue))
875                 c = itemarmor / max(1, GetResource(player, RES_ARMOR) * 2/3 + GetResource(player, RES_HEALTH) * 1/3);
876
877         if (itemhealth && (GetResource(player, RES_HEALTH) < item.max_health))
878                 c = itemhealth / max(1, GetResource(player, RES_HEALTH));
879
880         rating *= min(2, c);
881         return rating;
882 }
883
884 void Item_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
885 {
886         if(ITEM_DAMAGE_NEEDKILL(deathtype))
887                 RemoveItem(this);
888 }
889
890 void item_use(entity this, entity actor, entity trigger)
891 {
892         // use the touch function to handle collection
893         gettouch(this)(this, actor);
894 }
895
896 void _StartItem(entity this, entity def, float defaultrespawntime, float defaultrespawntimejitter)
897 {
898         string itemname = def.m_name;
899         Model itemmodel = def.m_model;
900         Sound pickupsound = def.m_sound;
901         float(entity player, entity item) pickupevalfunc = def.m_pickupevalfunc;
902         float pickupbasevalue = def.m_botvalue;
903         int itemflags = def.m_itemflags;
904
905         startitem_failed = false;
906
907         this.item_model_ent = itemmodel;
908         this.item_pickupsound_ent = pickupsound;
909
910         if(def.m_iteminit)
911                 def.m_iteminit(def, this);
912
913         if(!this.respawntime) // both need to be set
914         {
915                 this.respawntime = defaultrespawntime;
916                 this.respawntimejitter = defaultrespawntimejitter;
917         }
918
919         if(!this.pickup_anyway && def.m_pickupanyway)
920                 this.pickup_anyway = def.m_pickupanyway();
921
922         int itemid = def.m_itemid;
923         this.items = itemid;
924         int weaponid = def.instanceOfWeaponPickup ? def.m_weapon.m_id : 0;
925         this.weapon = weaponid;
926
927         if(!this.fade_end)
928         {
929                 this.fade_start = autocvar_g_items_mindist;
930                 this.fade_end = autocvar_g_items_maxdist;
931         }
932
933         if(weaponid)
934                 STAT(WEAPONS, this) = WepSet_FromWeapon(REGISTRY_GET(Weapons, weaponid));
935
936         this.flags = FL_ITEM | itemflags;
937         IL_PUSH(g_items, this);
938
939         if(MUTATOR_CALLHOOK(FilterItem, this)) // error means we do not want the item
940         {
941                 startitem_failed = true;
942                 delete(this);
943                 return;
944         }
945
946         precache_model(this.model);
947         precache_sound(this.item_pickupsound);
948
949         if (Item_IsLoot(this))
950         {
951                 this.reset = SUB_Remove;
952                 set_movetype(this, MOVETYPE_TOSS);
953
954                 // Savage: remove thrown items after a certain period of time ("garbage collection")
955                 setthink(this, RemoveItem);
956                 this.nextthink = time + 20;
957
958                 this.takedamage = DAMAGE_YES;
959                 this.event_damage = Item_Damage;
960
961                 if (Item_IsExpiring(this))
962                 {
963                         // if item is worthless after a timer, have it expire then
964                         this.nextthink = max(this.strength_finished, this.invincible_finished, this.superweapons_finished);
965                 }
966
967                 // don't drop if in a NODROP zone (such as lava)
968                 traceline(this.origin, this.origin, MOVE_NORMAL, this);
969                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
970                 {
971                         startitem_failed = true;
972                         delete(this);
973                         return;
974                 }
975         }
976         else
977         {
978                 if(!have_pickup_item(this))
979                 {
980                         startitem_failed = true;
981                         delete(this);
982                         return;
983                 }
984
985                 if(this.angles != '0 0 0')
986                         this.SendFlags |= ISF_ANGLES;
987
988                 this.reset = Item_Reset;
989                 // it's a level item
990                 if(this.spawnflags & 1)
991                         this.noalign = 1;
992                 if (this.noalign > 0)
993                         set_movetype(this, MOVETYPE_NONE);
994                 else
995                         set_movetype(this, MOVETYPE_TOSS);
996                 // do item filtering according to game mode and other things
997                 if (this.noalign <= 0)
998                 {
999                         // first nudge it off the floor a little bit to avoid math errors
1000                         setorigin(this, this.origin + '0 0 1');
1001                         // set item size before we spawn a spawnfunc_waypoint
1002                         setsize(this, def.m_mins, def.m_maxs);
1003                         this.SendFlags |= ISF_SIZE;
1004                         // note droptofloor returns false if stuck/or would fall too far
1005                         if (!this.noalign)
1006                                 droptofloor(this);
1007                         waypoint_spawnforitem(this);
1008                 }
1009
1010                 /*
1011                  * can't do it that way, as it would break maps
1012                  * TODO make a target_give like entity another way, that perhaps has
1013                  * the weapon name in a key
1014                 if(this.targetname)
1015                 {
1016                         // target_give not yet supported; maybe later
1017                         print("removed targeted ", this.classname, "\n");
1018                         startitem_failed = true;
1019                         delete(this);
1020                         return;
1021                 }
1022                 */
1023
1024                 if(this.targetname != "" && (this.spawnflags & 16))
1025                         this.use = item_use;
1026
1027                 if(autocvar_spawn_debug >= 2)
1028                 {
1029                         // why not flags & fl_item?
1030                         FOREACH_ENTITY_RADIUS(this.origin, 3, it.is_item, {
1031                                 LOG_TRACE("XXX Found duplicated item: ", itemname, vtos(this.origin));
1032                                 LOG_TRACE(" vs ", it.netname, vtos(it.origin));
1033                                 error("Mapper sucks.");
1034                         });
1035                         this.is_item = true;
1036                 }
1037
1038                 weaponsInMap |= WepSet_FromWeapon(REGISTRY_GET(Weapons, weaponid));
1039
1040                 if (        def.instanceOfPowerup
1041                         ||  def.instanceOfWeaponPickup
1042                         || (def.instanceOfHealth && def != ITEM_HealthSmall)
1043                         || (def.instanceOfArmor && def != ITEM_ArmorSmall)
1044                         || (itemid & (IT_KEY1 | IT_KEY2))
1045                 )
1046                 {
1047                         if(!this.target || this.target == "")
1048                                 this.target = "###item###"; // for finding the nearest item using findnearest
1049                 }
1050
1051                 Item_ItemsTime_SetTime(this, 0);
1052         }
1053
1054         this.bot_pickup = true;
1055         this.bot_pickupevalfunc = pickupevalfunc;
1056         this.bot_pickupbasevalue = pickupbasevalue;
1057         this.mdl = this.model ? this.model : strzone(this.item_model_ent.model_str());
1058         this.netname = (def.m_weapon) ? def.m_weapon.netname : def.netname;
1059         settouch(this, Item_Touch);
1060         setmodel(this, MDL_Null); // precision set below
1061         //this.effects |= EF_LOWPRECISION;
1062
1063         setsize (this, this.pos1 =  def.m_mins, this.pos2 = def.m_maxs);
1064
1065         this.SendFlags |= ISF_SIZE;
1066
1067         if (!(this.spawnflags & 1024)) {
1068                 if(def.instanceOfPowerup)
1069                         this.ItemStatus |= ITS_ANIMATE1;
1070
1071                 if(GetResource(this, RES_ARMOR) || GetResource(this, RES_HEALTH))
1072                         this.ItemStatus |= ITS_ANIMATE2;
1073         }
1074
1075         if(Item_IsLoot(this))
1076                 this.gravity = 1;
1077
1078         if(def.instanceOfWeaponPickup)
1079         {
1080                 if (!Item_IsLoot(this)) // if dropped, colormap is already set up nicely
1081                         this.colormap = 1024; // color shirt=0 pants=0 grey
1082                 if (!(this.spawnflags & 1024))
1083                         this.ItemStatus |= ITS_ANIMATE1;
1084                 this.SendFlags |= ISF_COLORMAP;
1085         }
1086
1087         this.state = 0;
1088         if(this.team)
1089         {
1090                 if(!this.cnt)
1091                         this.cnt = 1; // item probability weight
1092
1093                 this.effects |= EF_NODRAW; // marker for item team search
1094                 InitializeEntity(this, Item_FindTeam, INITPRIO_FINDTARGET);
1095         }
1096         else
1097                 Item_Reset(this);
1098
1099         Net_LinkEntity(this, !(def.instanceOfPowerup || def.instanceOfHealth || def.instanceOfArmor), 0, ItemSend);
1100
1101         // call this hook after everything else has been done
1102         if (MUTATOR_CALLHOOK(Item_Spawn, this))
1103         {
1104                 startitem_failed = true;
1105                 delete(this);
1106                 return;
1107         }
1108
1109         setItemGroup(this);
1110 }
1111
1112 void StartItem(entity this, GameItem def)
1113 {
1114         def = def.m_spawnfunc_hookreplace(def, this);
1115
1116         if (def.spawnflags & ITEM_FLAG_MUTATORBLOCKED)
1117         {
1118                 delete(this);
1119                 return;
1120         }
1121
1122         this.classname = def.m_canonical_spawnfunc;
1123
1124         _StartItem(
1125                 this,
1126                 this.itemdef = def,
1127                 def.m_respawntime(), // defaultrespawntime
1128                 def.m_respawntimejitter() // defaultrespawntimejitter
1129         );
1130 }
1131
1132 #define IS_SMALL(def) ((def.instanceOfHealth && def == ITEM_HealthSmall) || (def.instanceOfArmor && def == ITEM_ArmorSmall))
1133 int group_count = 1;
1134
1135 void setItemGroup(entity this)
1136 {
1137         if(!IS_SMALL(this.itemdef) || Item_IsLoot(this))
1138                 return;
1139
1140         FOREACH_ENTITY_RADIUS(this.origin, 120, (it != this) && IS_SMALL(it.itemdef),
1141         {
1142                 if(!this.item_group)
1143                 {
1144                         if(!it.item_group)
1145                         {
1146                                 it.item_group = group_count;
1147                                 group_count++;
1148                         }
1149                         this.item_group = it.item_group;
1150                 }
1151                 else // spawning item is already part of a item_group X
1152                 {
1153                         if(!it.item_group)
1154                                 it.item_group = this.item_group;
1155                         else if(it.item_group != this.item_group) // found an item near the spawning item that is part of a different item_group Y
1156                         {
1157                                 int grY = it.item_group;
1158                                 // move all items of item_group Y to item_group X
1159                                 IL_EACH(g_items, IS_SMALL(it.itemdef),
1160                                 {
1161                                         if(it.item_group == grY)
1162                                                 it.item_group = this.item_group;
1163                                 });
1164                         }
1165                 }
1166         });
1167 }
1168
1169 void setItemGroupCount()
1170 {
1171         for (int k = 1; k <= group_count; k++)
1172         {
1173                 int count = 0;
1174                 IL_EACH(g_items, IS_SMALL(it.itemdef) && it.item_group == k, { count++; });
1175                 if (count)
1176                         IL_EACH(g_items, IS_SMALL(it.itemdef) && it.item_group == k, { it.item_group_count = count; });
1177         }
1178 }
1179
1180 void target_items_use(entity this, entity actor, entity trigger)
1181 {
1182         if(Item_IsLoot(actor))
1183         {
1184                 EXACTTRIGGER_TOUCH(this, trigger);
1185                 delete(actor);
1186                 return;
1187         }
1188
1189         if (!IS_PLAYER(actor) || IS_DEAD(actor))
1190                 return;
1191
1192         if(trigger.solid == SOLID_TRIGGER)
1193         {
1194                 EXACTTRIGGER_TOUCH(this, trigger);
1195         }
1196
1197         IL_EACH(g_items, it.enemy == actor && Item_IsLoot(it),
1198         {
1199                 delete(it);
1200         });
1201
1202         if(GiveItems(actor, 0, tokenize_console(this.netname)))
1203                 centerprint(actor, this.message);
1204 }
1205
1206 spawnfunc(target_items)
1207 {
1208         this.use = target_items_use;
1209         if(!this.strength_finished)
1210                 this.strength_finished = autocvar_g_balance_powerup_strength_time;
1211         if(!this.invincible_finished)
1212                 this.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1213         if(!this.superweapons_finished)
1214                 this.superweapons_finished = autocvar_g_balance_superweapons_time;
1215
1216         string str;
1217         int n = tokenize_console(this.netname);
1218         if(argv(0) == "give")
1219         {
1220                 str = substring(this.netname, argv_start_index(1), argv_end_index(-1) - argv_start_index(1));
1221         }
1222         else
1223         {
1224                 for(int j = 0; j < n; ++j)
1225                 {
1226                         // this is from a time when unlimited superweapons were handled together with ammo in some parts of the code
1227                         if     (argv(j) == "unlimited_ammo")         this.items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
1228                         else if(argv(j) == "unlimited_weapon_ammo")  this.items |= IT_UNLIMITED_AMMO;
1229                         else if(argv(j) == "unlimited_superweapons") this.items |= IT_UNLIMITED_SUPERWEAPONS;
1230                         else if(argv(j) == "strength")               this.items |= ITEM_Strength.m_itemid;
1231                         else if(argv(j) == "invincible")             this.items |= ITEM_Shield.m_itemid;
1232                         else if(argv(j) == "superweapons")           this.items |= IT_SUPERWEAPON;
1233                         else if(argv(j) == "jetpack")                this.items |= ITEM_Jetpack.m_itemid;
1234                         else if(argv(j) == "fuel_regen")             this.items |= ITEM_JetpackRegen.m_itemid;
1235                         else
1236                         {
1237                                 FOREACH(Buffs, it != BUFF_Null,
1238                                 {
1239                                         string s = Buff_UndeprecateName(argv(j));
1240                                         if(s == it.netname)
1241                                         {
1242                                                 STAT(BUFFS, this) |= (it.m_itemid);
1243                                                 if(!STAT(BUFF_TIME, this))
1244                                                         STAT(BUFF_TIME, this) = it.m_time(it);
1245                                                 break;
1246                                         }
1247                                 });
1248                                 FOREACH(Weapons, it != WEP_Null, {
1249                                         string s = W_UndeprecateName(argv(j));
1250                                         if(s == it.netname)
1251                                         {
1252                                                 STAT(WEAPONS, this) |= (it.m_wepset);
1253                                                 if(this.spawnflags == 0 || this.spawnflags == 2)
1254                                                         it.wr_init(it);
1255                                                 break;
1256                                         }
1257                                 });
1258                         }
1259                 }
1260
1261                 string itemprefix, valueprefix;
1262                 if(this.spawnflags == 0)
1263                 {
1264                         itemprefix = "";
1265                         valueprefix = "";
1266                 }
1267                 else if(this.spawnflags == 1)
1268                 {
1269                         itemprefix = "max ";
1270                         valueprefix = "max ";
1271                 }
1272                 else if(this.spawnflags == 2)
1273                 {
1274                         itemprefix = "min ";
1275                         valueprefix = "min ";
1276                 }
1277                 else if(this.spawnflags == 4)
1278                 {
1279                         itemprefix = "minus ";
1280                         valueprefix = "max ";
1281                 }
1282                 else
1283                 {
1284                         error("invalid spawnflags");
1285                         itemprefix = valueprefix = string_null;
1286                 }
1287
1288                 str = "";
1289                 str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & IT_UNLIMITED_AMMO), "unlimited_weapon_ammo");
1290                 str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & IT_UNLIMITED_SUPERWEAPONS), "unlimited_superweapons");
1291                 str = sprintf("%s %s%d %s", str, valueprefix, this.strength_finished * boolean(this.items & ITEM_Strength.m_itemid), "strength");
1292                 str = sprintf("%s %s%d %s", str, valueprefix, this.invincible_finished * boolean(this.items & ITEM_Shield.m_itemid), "invincible");
1293                 str = sprintf("%s %s%d %s", str, valueprefix, this.superweapons_finished * boolean(this.items & IT_SUPERWEAPON), "superweapons");
1294                 str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & ITEM_Jetpack.m_itemid), "jetpack");
1295                 str = sprintf("%s %s%d %s", str, itemprefix, boolean(this.items & ITEM_JetpackRegen.m_itemid), "fuel_regen");
1296                 float res;
1297                 res = GetResource(this, RES_SHELLS);  if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "shells");
1298                 res = GetResource(this, RES_BULLETS); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "nails");
1299                 res = GetResource(this, RES_ROCKETS); if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "rockets");
1300                 res = GetResource(this, RES_CELLS);   if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "cells");
1301                 res = GetResource(this, RES_PLASMA);  if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "plasma");
1302                 res = GetResource(this, RES_FUEL);    if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "fuel");
1303                 res = GetResource(this, RES_HEALTH);  if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "health");
1304                 res = GetResource(this, RES_ARMOR);   if(res != 0) str = sprintf("%s %s%d %s", str, valueprefix, max(0, res), "armor");
1305                 // HACK: buffs share a single timer, so we need to include enabled buffs AFTER disabled ones to avoid loss
1306                 FOREACH(Buffs, it != BUFF_Null && !(STAT(BUFFS, this) & it.m_itemid), str = sprintf("%s %s%d %s", str, valueprefix, max(0, STAT(BUFF_TIME, this)), it.netname));
1307                 FOREACH(Buffs, it != BUFF_Null && (STAT(BUFFS, this) & it.m_itemid), str = sprintf("%s %s%d %s", str, valueprefix, max(0, STAT(BUFF_TIME, this)), it.netname));
1308                 FOREACH(Weapons, it != WEP_Null, str = sprintf("%s %s%d %s", str, itemprefix, !!(STAT(WEAPONS, this) & (it.m_wepset)), it.netname));
1309         }
1310         this.netname = strzone(str);
1311
1312         n = tokenize_console(this.netname);
1313         for(int j = 0; j < n; ++j)
1314         {
1315                 FOREACH(Weapons, it != WEP_Null && W_UndeprecateName(argv(j)) == it.netname, {
1316                         it.wr_init(it);
1317                         break;
1318                 });
1319         }
1320 }
1321
1322 float GiveWeapon(entity e, float wpn, float op, float val)
1323 {
1324         WepSet v0, v1;
1325         WepSet s = WepSet_FromWeapon(REGISTRY_GET(Weapons, wpn));
1326         v0 = (STAT(WEAPONS, e) & s);
1327         switch(op)
1328         {
1329                 case OP_SET:
1330                         if(val > 0)
1331                                 STAT(WEAPONS, e) |= s;
1332                         else
1333                                 STAT(WEAPONS, e) &= ~s;
1334                         break;
1335                 case OP_MIN:
1336                 case OP_PLUS:
1337                         if(val > 0)
1338                                 STAT(WEAPONS, e) |= s;
1339                         break;
1340                 case OP_MAX:
1341                         if(val <= 0)
1342                                 STAT(WEAPONS, e) &= ~s;
1343                         break;
1344                 case OP_MINUS:
1345                         if(val > 0)
1346                                 STAT(WEAPONS, e) &= ~s;
1347                         break;
1348         }
1349         v1 = (STAT(WEAPONS, e) & s);
1350         return (v0 != v1);
1351 }
1352
1353 bool GiveBuff(entity e, Buff thebuff, int op, int val)
1354 {
1355         bool had_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
1356         float new_buff_time = ((had_buff) ? STAT(BUFF_TIME, e) : 0);
1357         switch (op)
1358         {
1359                 case OP_SET:
1360                         new_buff_time = val;
1361                         break;
1362                 case OP_MIN:
1363                         new_buff_time = max(new_buff_time, val);
1364                         break;
1365                 case OP_MAX:
1366                         new_buff_time = min(new_buff_time, val);
1367                         break;
1368                 case OP_PLUS:
1369                         new_buff_time += val;
1370                         break;
1371                 case OP_MINUS:
1372                         new_buff_time -= val;
1373                         break;
1374         }
1375         if(new_buff_time <= 0)
1376         {
1377                 if(had_buff)
1378                         STAT(BUFF_TIME, e) = new_buff_time;
1379                 STAT(BUFFS, e) &= ~thebuff.m_itemid;
1380         }
1381         else
1382         {
1383                 STAT(BUFF_TIME, e) = new_buff_time;
1384                 STAT(BUFFS, e) = thebuff.m_itemid; // NOTE: replaces any existing buffs on the player!
1385         }
1386         bool have_buff = (STAT(BUFFS, e) & thebuff.m_itemid);
1387         return (had_buff != have_buff);
1388 }
1389
1390 void GiveSound(entity e, float v0, float v1, float t, Sound snd_incr, Sound snd_decr)
1391 {
1392         if(v1 == v0)
1393                 return;
1394         if(v1 <= v0 - t)
1395         {
1396                 if(snd_decr != NULL)
1397                         sound (e, CH_TRIGGER, snd_decr, VOL_BASE, ATTEN_NORM);
1398         }
1399         else if(v0 >= v0 + t)
1400         {
1401                 if(snd_incr != NULL)
1402                         sound (e, CH_TRIGGER, snd_incr, VOL_BASE, ATTEN_NORM);
1403         }
1404 }
1405
1406 void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime)
1407 {
1408         if(v0 < v1)
1409                 e.(rotfield) = max(e.(rotfield), time + rottime);
1410         else if(v0 > v1)
1411                 e.(regenfield) = max(e.(regenfield), time + regentime);
1412 }
1413 bool GiveResourceValue(entity e, int res_type, int op, int val)
1414 {
1415         int v0 = GetResource(e, res_type);
1416         float new_val = 0;
1417         switch (op)
1418         {
1419                 // min 100 cells = at least 100 cells
1420                 case OP_SET: new_val = val; break;
1421                 case OP_MIN: new_val = max(v0, val); break;
1422                 case OP_MAX: new_val = min(v0, val); break;
1423                 case OP_PLUS: new_val = v0 + val; break;
1424                 case OP_MINUS: new_val = v0 - val; break;
1425                 default: return false;
1426         }
1427
1428         return SetResourceExplicit(e, res_type, new_val);
1429 }
1430
1431 float GiveItems(entity e, float beginarg, float endarg)
1432 {
1433         float got, i, val, op;
1434         string cmd;
1435
1436         val = 999;
1437         op = OP_SET;
1438
1439         got = 0;
1440
1441         int _switchweapon = 0;
1442
1443         if(CS(e).autoswitch)
1444         {
1445                 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1446                 {
1447                         .entity weaponentity = weaponentities[slot];
1448                         if(e.(weaponentity).m_weapon != WEP_Null || slot == 0)
1449                         if(e.(weaponentity).m_switchweapon == w_getbestweapon(e, weaponentity))
1450                                 _switchweapon |= BIT(slot);
1451                 }
1452         }
1453
1454         STAT(STRENGTH_FINISHED, e) = max(0, STAT(STRENGTH_FINISHED, e) - time);
1455         STAT(INVINCIBLE_FINISHED, e) = max(0, STAT(INVINCIBLE_FINISHED, e) - time);
1456         STAT(SUPERWEAPONS_FINISHED, e) = max(0, STAT(SUPERWEAPONS_FINISHED, e) - time);
1457         STAT(BUFF_TIME, e) = max(0, STAT(BUFF_TIME, e) - time);
1458
1459         PREGIVE(e, items);
1460         PREGIVE_WEAPONS(e);
1461         PREGIVE(e, stat_STRENGTH_FINISHED);
1462         PREGIVE(e, stat_INVINCIBLE_FINISHED);
1463         PREGIVE(e, stat_SUPERWEAPONS_FINISHED);
1464         PREGIVE_RESOURCE(e, RES_BULLETS);
1465         PREGIVE_RESOURCE(e, RES_CELLS);
1466         PREGIVE_RESOURCE(e, RES_PLASMA);
1467         PREGIVE_RESOURCE(e, RES_SHELLS);
1468         PREGIVE_RESOURCE(e, RES_ROCKETS);
1469         PREGIVE_RESOURCE(e, RES_FUEL);
1470         PREGIVE_RESOURCE(e, RES_ARMOR);
1471         PREGIVE_RESOURCE(e, RES_HEALTH);
1472
1473         for(i = beginarg; i < endarg; ++i)
1474         {
1475                 cmd = argv(i);
1476
1477                 if(cmd == "0" || stof(cmd))
1478                 {
1479                         val = stof(cmd);
1480                         continue;
1481                 }
1482                 switch(cmd)
1483                 {
1484                         case "no":
1485                                 op = OP_MAX;
1486                                 val = 0;
1487                                 continue;
1488                         case "max":
1489                                 op = OP_MAX;
1490                                 continue;
1491                         case "min":
1492                                 op = OP_MIN;
1493                                 continue;
1494                         case "plus":
1495                                 op = OP_PLUS;
1496                                 continue;
1497                         case "minus":
1498                                 op = OP_MINUS;
1499                                 continue;
1500                         case "ALL":
1501                                 got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
1502                                 got += GiveValue(e, stat_STRENGTH_FINISHED, op, val);
1503                                 got += GiveValue(e, stat_INVINCIBLE_FINISHED, op, val);
1504                                 got += GiveValue(e, stat_SUPERWEAPONS_FINISHED, op, val);
1505                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS, op, val);
1506                         case "all":
1507                                 got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
1508                                 got += GiveResourceValue(e, RES_HEALTH, op, val);
1509                                 got += GiveResourceValue(e, RES_ARMOR, op, val);
1510                         case "allweapons":
1511                                 FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)), got += GiveWeapon(e, it.m_id, op, val));
1512                         //case "allbuffs": // all buffs makes a player god, do not want!
1513                                 //FOREACH(Buffs, it != BUFF_Null, got += GiveBuff(e, it.m_itemid, op, val));
1514                         case "allammo":
1515                                 got += GiveResourceValue(e, RES_CELLS, op, val);
1516                                 got += GiveResourceValue(e, RES_PLASMA, op, val);
1517                                 got += GiveResourceValue(e, RES_SHELLS, op, val);
1518                                 got += GiveResourceValue(e, RES_BULLETS, op, val);
1519                                 got += GiveResourceValue(e, RES_ROCKETS, op, val);
1520                                 got += GiveResourceValue(e, RES_FUEL, op, val);
1521                                 break;
1522                         case "unlimited_ammo":
1523                                 // this is from a time when unlimited superweapons were handled together with ammo in some parts of the code
1524                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS, op, val);
1525                                 break;
1526                         case "unlimited_weapon_ammo":
1527                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
1528                                 break;
1529                         case "unlimited_superweapons":
1530                                 got += GiveBit(e, items, IT_UNLIMITED_SUPERWEAPONS, op, val);
1531                                 break;
1532                         case "jetpack":
1533                                 got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
1534                                 break;
1535                         case "fuel_regen":
1536                                 got += GiveBit(e, items, ITEM_JetpackRegen.m_itemid, op, val);
1537                                 break;
1538                         case "strength":
1539                                 got += GiveValue(e, stat_STRENGTH_FINISHED, op, val);
1540                                 break;
1541                         case "invincible":
1542                                 got += GiveValue(e, stat_INVINCIBLE_FINISHED, op, val);
1543                                 break;
1544                         case "superweapons":
1545                                 got += GiveValue(e, stat_SUPERWEAPONS_FINISHED, op, val);
1546                                 break;
1547                         case "cells":
1548                                 got += GiveResourceValue(e, RES_CELLS, op, val);
1549                                 break;
1550                         case "plasma":
1551                                 got += GiveResourceValue(e, RES_PLASMA, op, val);
1552                                 break;
1553                         case "shells":
1554                                 got += GiveResourceValue(e, RES_SHELLS, op, val);
1555                                 break;
1556                         case "nails":
1557                         case "bullets":
1558                                 got += GiveResourceValue(e, RES_BULLETS, op, val);
1559                                 break;
1560                         case "rockets":
1561                                 got += GiveResourceValue(e, RES_ROCKETS, op, val);
1562                                 break;
1563                         case "health":
1564                                 got += GiveResourceValue(e, RES_HEALTH, op, val);
1565                                 break;
1566                         case "armor":
1567                                 got += GiveResourceValue(e, RES_ARMOR, op, val);
1568                                 break;
1569                         case "fuel":
1570                                 got += GiveResourceValue(e, RES_FUEL, op, val);
1571                                 break;
1572                         default:
1573                                 FOREACH(Buffs, it != BUFF_Null && buff_Available(it) && Buff_UndeprecateName(cmd) == it.netname,
1574                                 {
1575                                         got += GiveBuff(e, it, op, val);
1576                                         break;
1577                                 });
1578                                 FOREACH(Weapons, it != WEP_Null && W_UndeprecateName(cmd) == it.netname, {
1579                     got += GiveWeapon(e, it.m_id, op, val);
1580                     break;
1581                                 });
1582                                 break;
1583                 }
1584                 val = 999;
1585                 op = OP_SET;
1586         }
1587
1588         POSTGIVE_BIT(e, items, ITEM_JetpackRegen.m_itemid, SND_ITEMPICKUP, SND_Null);
1589         POSTGIVE_BIT(e, items, IT_UNLIMITED_SUPERWEAPONS, SND_POWERUP, SND_POWEROFF);
1590         POSTGIVE_BIT(e, items, IT_UNLIMITED_AMMO, SND_POWERUP, SND_POWEROFF);
1591         POSTGIVE_BIT(e, items, ITEM_Jetpack.m_itemid, SND_ITEMPICKUP, SND_Null);
1592         FOREACH(Weapons, it != WEP_Null, {
1593                 POSTGIVE_WEAPON(e, it, SND_WEAPONPICKUP, SND_Null);
1594                 if(!(save_weapons & (it.m_wepset)))
1595                         if(STAT(WEAPONS, e) & (it.m_wepset))
1596                                 it.wr_init(it);
1597         });
1598         POSTGIVE_VALUE(e, stat_STRENGTH_FINISHED, 1, SND_POWERUP, SND_POWEROFF);
1599         POSTGIVE_VALUE(e, stat_INVINCIBLE_FINISHED, 1, SND_Shield, SND_POWEROFF);
1600         //POSTGIVE_VALUE(e, stat_SUPERWEAPONS_FINISHED, 1, SND_Null, SND_Null);
1601         POSTGIVE_RESOURCE(e, RES_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
1602         POSTGIVE_RESOURCE(e, RES_CELLS, 0, SND_ITEMPICKUP, SND_Null);
1603         POSTGIVE_RESOURCE(e, RES_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
1604         POSTGIVE_RESOURCE(e, RES_SHELLS, 0, SND_ITEMPICKUP, SND_Null);
1605         POSTGIVE_RESOURCE(e, RES_ROCKETS, 0, SND_ITEMPICKUP, SND_Null);
1606         POSTGIVE_RES_ROT(e, RES_FUEL, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
1607         POSTGIVE_RES_ROT(e, RES_ARMOR, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
1608         POSTGIVE_RES_ROT(e, RES_HEALTH, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
1609
1610         if(STAT(SUPERWEAPONS_FINISHED, e) <= 0)
1611                 if(!g_weaponarena && (STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS))
1612                         STAT(SUPERWEAPONS_FINISHED, e) = autocvar_g_balance_superweapons_time;
1613
1614         if(STAT(STRENGTH_FINISHED, e) <= 0)
1615                 STAT(STRENGTH_FINISHED, e) = 0;
1616         else
1617                 STAT(STRENGTH_FINISHED, e) += time;
1618         if(STAT(INVINCIBLE_FINISHED, e) <= 0)
1619                 STAT(INVINCIBLE_FINISHED, e) = 0;
1620         else
1621                 STAT(INVINCIBLE_FINISHED, e) += time;
1622         if(STAT(SUPERWEAPONS_FINISHED, e) <= 0)
1623                 STAT(SUPERWEAPONS_FINISHED, e) = 0;
1624         else
1625                 STAT(SUPERWEAPONS_FINISHED, e) += time;
1626         if(STAT(BUFF_TIME, e) <= 0)
1627                 STAT(BUFF_TIME, e) = 0;
1628         else
1629                 STAT(BUFF_TIME, e) += time;
1630
1631         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1632         {
1633                 .entity weaponentity = weaponentities[slot];
1634                 if(e.(weaponentity).m_weapon != WEP_Null || slot == 0)
1635                 if(!(STAT(WEAPONS, e) & WepSet_FromWeapon(e.(weaponentity).m_switchweapon)))
1636                         _switchweapon |= BIT(slot);
1637         }
1638
1639         if(_switchweapon)
1640         {
1641                 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1642                 {
1643                         .entity weaponentity = weaponentities[slot];
1644                         if(_switchweapon & BIT(slot))
1645                         {
1646                                 Weapon wep = w_getbestweapon(e, weaponentity);
1647                                 if(wep != e.(weaponentity).m_switchweapon)
1648                                         W_SwitchWeapon_Force(e, wep, weaponentity);
1649                         }
1650                 }
1651         }
1652
1653         return got;
1654 }