]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_items.qc
18b2e3c9efe4d14445101fcffab7dd9c2d889e74
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_items.qc
1 #define ISF_LOCATION 2
2 #define ISF_MODEL    4
3 #define ISF_STATUS   8
4     #define ITS_STAYWEP   1
5     #define ITS_ANIMATE1  2
6     #define ITS_ANIMATE2  4
7     #define ITS_AVAILABLE 8
8     #define ITS_ALLOWFB   16
9     #define ITS_ALLOWSI   32
10     #define ITS_POWERUP   64
11 #define ISF_COLORMAP 16
12 #define ISF_DROP 32
13
14 .float ItemStatus;
15
16 #ifdef CSQC
17
18 var float  autocvar_cl_ghost_items = 1;
19 var vector autocvar_cl_ghost_items_color = '-1 -1 -1';
20 float  autocvar_cl_fullbright_items;
21 vector autocvar_cl_staywep_color;
22 float  autocvar_cl_staywep_alpha;
23 float  autocvar_cl_simple_items;
24 float  cl_simple_items;
25 float  cl_ghost_items_alpha;
26
27 .float  spawntime;
28 .float  gravity;
29 .vector colormod;
30 void ItemDraw()
31 {    
32     if(self.ItemStatus & ITS_ANIMATE1)
33     {
34         self.angles += '0 180 0' * frametime;
35         setorigin(self, '0 0 10' + self.oldorigin + '0 0 8' * sin(time * 2));        
36     }    
37     
38     if(self.ItemStatus & ITS_ANIMATE2)
39     {
40         self.angles += '0 -90 0' * frametime;
41         setorigin(self, '0 0 8' + self.oldorigin + '0 0 4' * sin(time * 3));        
42     }
43     
44     if(self.gravity)
45         Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
46 }
47
48 void ItemDrawSimple()
49 {
50     if(self.gravity)
51         Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);    
52 }
53
54 float csqcitems_started; // remove this after a release or two
55 void csqcitems_start()
56 {
57     if(autocvar_cl_ghost_items == 1)
58         cl_ghost_items_alpha = 0.55;
59     else
60         cl_ghost_items_alpha = bound(0, autocvar_cl_ghost_items, 1);
61     
62     csqcitems_started = TRUE;
63 }
64
65 void ItemRead(float _IsNew)
66 {
67     if(!csqcitems_started)
68         csqcitems_start();
69     
70     float sf = ReadByte();
71
72     if(sf & ISF_LOCATION)
73     {
74         self.origin_x = ReadCoord();
75         self.origin_y = ReadCoord();
76         self.origin_z = ReadCoord();
77         setorigin(self, self.origin);
78         self.oldorigin = self.origin;
79     }
80     
81     if(sf & ISF_STATUS) // need to read/write status frist so model can handle simple, fb etc.
82     {
83         self.ItemStatus = ReadByte();    
84         
85         if(self.ItemStatus & ITS_AVAILABLE)
86         {
87             self.alpha = 1;
88             self.colormod = self.glowmod = '1 1 1';
89         }
90         else
91         {
92             if (cl_ghost_items_alpha)
93             {
94                 self.alpha = autocvar_cl_ghost_items;
95                 self.colormod = self.glowmod = autocvar_cl_ghost_items_color;
96             }
97             else
98                 self.alpha = -1;
99         }    
100         
101         if(autocvar_cl_fullbright_items)
102             if(self.ItemStatus & ITS_ALLOWFB)
103                 self.effects |= EF_FULLBRIGHT;
104             
105         if(self.ItemStatus & ITS_STAYWEP)
106         {
107             self.colormod = self.glowmod = autocvar_cl_staywep_color;
108             self.alpha = autocvar_cl_staywep_alpha;
109             
110         }
111         
112         if(self.ItemStatus & ITS_POWERUP)
113         {
114             if(self.ItemStatus & ITS_AVAILABLE)
115                 self.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
116             else
117                  self.effects &~= (EF_ADDITIVE | EF_FULLBRIGHT);
118         }
119     }
120     
121     if(sf & ISF_MODEL)
122     {
123         self.drawmask  = MASK_NORMAL;
124         self.movetype  = MOVETYPE_NOCLIP;
125         
126         if(self.mdl)
127             strunzone(self.mdl);
128         
129         self.mdl = "";
130         string _fn = ReadString();
131         
132         if(cl_simple_items && (self.ItemStatus & ITS_ALLOWSI))
133         {
134             string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
135                         
136             if(fexists(strcat(_fn2, "_simple.md3")))
137                 self.mdl = strzone(strcat(_fn2, "_simple.md3"));
138             else if(fexists(strcat(_fn2, "_simple.dpm")))
139                 self.mdl = strzone(strcat(_fn2, "_simple.dpm"));
140             else if(fexists(strcat(_fn2, "_simple.iqm")))
141                 self.mdl = strzone(strcat(_fn2, "_simple.iqm"));
142             else if(fexists(strcat(_fn2, "_simple.obj")))
143                 self.mdl = strzone(strcat(_fn2, "_simple.obj"));
144             else
145             {
146                 self.mdl = "";
147                 dprint("Simple item requested for ", _fn, " but no model exsist for it\n");
148             }
149         }
150         
151         if(self.mdl == "")
152         {
153             self.mdl       = strzone(_fn);
154             self.draw      = ItemDraw;
155         }
156         else
157             self.draw      = ItemDrawSimple;
158         
159         precache_model(self.mdl);
160         setmodel(self, self.mdl);
161     }
162     
163     if(sf & ISF_COLORMAP)
164         self.colormap = ReadShort();
165     
166     if(sf & ISF_DROP)
167     {
168         self.gravity = 1;
169         self.move_movetype = MOVETYPE_TOSS;
170         self.move_velocity_x = ReadCoord();
171         self.move_velocity_y = ReadCoord();
172         self.move_velocity_z = ReadCoord();
173         self.velocity = self.move_velocity;
174         self.move_origin = self.oldorigin;
175         
176         if(!self.move_time)
177         {
178             self.move_time = time;
179             self.spawntime = time;
180         }
181         else
182             self.move_time = max(self.move_time, time);
183     }    
184 }
185 #endif
186
187 #ifdef SVQC
188 float autocvar_sv_simple_items;
189 float ItemSend(entity to, float sf)
190 {
191     if(self.gravity)
192         sf |= ISF_DROP;
193     else
194         sf &~= ISF_DROP;
195         
196         WriteByte(MSG_ENTITY, ENT_CLIENT_ITEM); 
197         WriteByte(MSG_ENTITY, sf);
198
199         
200         //WriteByte(MSG_ENTITY, self.cnt);
201     if(sf & ISF_LOCATION)
202     {
203         WriteCoord(MSG_ENTITY, self.origin_x);
204         WriteCoord(MSG_ENTITY, self.origin_y);
205         WriteCoord(MSG_ENTITY, self.origin_z);
206     }
207
208     if(sf & ISF_STATUS)
209         WriteByte(MSG_ENTITY, self.ItemStatus);
210
211     if(sf & ISF_MODEL)
212         WriteString(MSG_ENTITY, self.mdl);
213         
214     if(sf & ISF_COLORMAP)
215         WriteShort(MSG_ENTITY, self.colormap);
216
217     if(sf & ISF_DROP)
218     {
219         WriteCoord(MSG_ENTITY, self.velocity_x);
220         WriteCoord(MSG_ENTITY, self.velocity_y);
221         WriteCoord(MSG_ENTITY, self.velocity_z);
222     }
223         
224     return TRUE;
225 }
226
227
228 float have_pickup_item(void)
229 {
230         // minstagib: only allow filtered items
231         if(g_minstagib)
232                 if(self.classname != "minstagib")
233                         return FALSE;
234
235         if(self.flags & FL_POWERUP)
236         {
237                 if(autocvar_g_powerups > 0)
238                         return TRUE;
239                 if(autocvar_g_powerups == 0)
240                         return FALSE;
241                 if(g_lms)
242                         return FALSE;
243                 if(g_ca)
244                         return FALSE;
245                 if(g_arena)
246                         return FALSE;
247         }
248         else
249         {
250                 if(autocvar_g_pickup_items > 0)
251                         return TRUE;
252                 if(autocvar_g_pickup_items == 0)
253                         return FALSE;
254                 if(g_lms)
255                         return FALSE;
256                 if(g_ca)
257                         return FALSE;
258                 if(g_weaponarena)
259                         if(!WEPSET_EMPTY_E(self) || (self.items & IT_AMMO))
260                                 return FALSE;
261         }
262         return TRUE;
263 }
264
265 #define ITEM_RESPAWN_TICKS 10
266
267 #define ITEM_RESPAWNTIME(i)         ((i).respawntime + crandom() * (i).respawntimejitter)
268         // range: respawntime - respawntimejitter .. respawntime + respawntimejitter
269 #define ITEM_RESPAWNTIME_INITIAL(i) (ITEM_RESPAWN_TICKS + random() * ((i).respawntime + (i).respawntimejitter - ITEM_RESPAWN_TICKS))
270         // range: 10 .. respawntime + respawntimejitter
271
272 floatfield Item_CounterField(float it)
273 {
274         switch(it)
275         {
276                 case IT_SHELLS:      return ammo_shells;
277                 case IT_NAILS:       return ammo_nails;
278                 case IT_ROCKETS:     return ammo_rockets;
279                 case IT_CELLS:       return ammo_cells;
280                 case IT_FUEL:        return ammo_fuel;
281                 case IT_5HP:         return health;
282                 case IT_25HP:        return health;
283                 case IT_HEALTH:      return health;
284                 case IT_ARMOR_SHARD: return armorvalue;
285                 case IT_ARMOR:       return armorvalue;
286                 // add more things here (health, armor)
287                 default:             error("requested item has no counter field");
288         }
289 }
290
291 string Item_CounterFieldName(float it)
292 {
293         switch(it)
294         {
295                 case IT_SHELLS:      return "shells";
296                 case IT_NAILS:       return "nails";
297                 case IT_ROCKETS:     return "rockets";
298                 case IT_CELLS:       return "cells";
299                 case IT_FUEL:        return "fuel";
300
301                 // add more things here (health, armor)
302                 default:             error("requested item has no counter field name");
303         }
304 }
305
306 .float max_armorvalue;
307 .float pickup_anyway;
308 /*
309 float Item_Customize()
310 {
311         if(self.spawnshieldtime)
312                 return TRUE;
313         if(!WEPSET_CONTAINS_ALL_EE(other, self))
314         {
315                 self.colormod = '0 0 0';
316                 self.glowmod = self.colormod;
317                 self.alpha = 0.5 + 0.5 * g_ghost_items; // halfway more alpha
318                 return TRUE;
319         }
320         else
321         {
322                 if(g_ghost_items)
323                 {
324                         self.colormod = stov(autocvar_g_ghost_items_color);
325                         self.glowmod = self.colormod;
326                         self.alpha = g_ghost_items;
327                         return TRUE;
328                 }
329                 else
330                         return FALSE;
331         }
332 }
333 */
334
335 void Item_Show (entity e, float mode)
336 {    
337         e.effects &~= EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST;
338         e.ItemStatus &~= ITS_STAYWEP;
339         if (mode > 0)
340         {
341                 // make the item look normal, and be touchable
342                 e.model = e.mdl;
343                 e.solid = SOLID_TRIGGER;
344                 e.spawnshieldtime = 1;
345                 e.ItemStatus |= ITS_AVAILABLE;
346         }
347         else if (mode < 0)
348         {
349                 // hide the item completely
350                 e.model = string_null;
351                 e.solid = SOLID_NOT;
352                 e.spawnshieldtime = 1;
353                 e.ItemStatus &~= ITS_AVAILABLE;
354         }
355         else if((e.flags & FL_WEAPON) && !(e.flags & FL_NO_WEAPON_STAY) && g_weapon_stay)
356         {
357                 // make the item translucent and not touchable
358                 e.model = e.mdl;
359                 e.solid = SOLID_TRIGGER; // can STILL be picked up!
360                 e.effects |= EF_STARDUST;
361                 e.spawnshieldtime = 0; // field indicates whether picking it up may give you anything other than the weapon
362                 e.ItemStatus |= (ITS_AVAILABLE | ITS_STAYWEP);
363         }
364         else
365         {
366                 //setmodel(e, "null");
367                 e.solid = SOLID_NOT;
368                 e.colormod = '0 0 0';
369                 e.glowmod = e.colormod;
370                 e.spawnshieldtime = 1;
371                 e.ItemStatus &~= ITS_AVAILABLE;
372         }
373         
374         if (e.items & IT_STRENGTH || e.items & IT_INVINCIBLE)
375             e.ItemStatus |= ITS_POWERUP;                
376         
377         if (autocvar_g_nodepthtestitems)
378                 e.effects |= EF_NODEPTHTEST;
379                 
380     
381     if (autocvar_g_fullbrightitems)
382                 e.ItemStatus |= ITS_ALLOWFB;
383         
384         if (autocvar_sv_simple_items)
385         e.ItemStatus |= ITS_ALLOWSI;
386
387         // relink entity (because solid may have changed)
388         setorigin(e, e.origin);
389     e.SendFlags |= ISF_STATUS;
390 }
391
392 void Item_Respawn (void)
393 {
394         Item_Show(self, 1);
395         if(!g_minstagib && self.items == IT_STRENGTH)
396                 sound (self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);     // play respawn sound
397         else if(!g_minstagib && self.items == IT_INVINCIBLE)
398                 sound (self, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);       // play respawn sound
399         else
400                 sound (self, CH_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTN_NORM);  // play respawn sound
401         setorigin (self, self.origin);
402
403         //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
404         pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
405 }
406
407 void Item_RespawnCountdown (void)
408 {
409         if(self.count >= ITEM_RESPAWN_TICKS)
410         {
411                 if(self.waypointsprite_attached)
412                         WaypointSprite_Kill(self.waypointsprite_attached);
413                 Item_Respawn();
414         }
415         else
416         {
417                 self.nextthink = time + 1;
418                 self.count += 1;
419                 if(self.count == 1)
420                 {
421                         string name;
422                         vector rgb = '1 0 1';
423                         name = string_null;
424                         if(g_minstagib)
425                         {
426                                 switch(self.items)
427                                 {
428                                         case IT_STRENGTH:   name = "item-invis"; rgb = '0 0 1'; break;
429                                         case IT_NAILS:      name = "item-extralife"; rgb = '1 0 0'; break;
430                                         case IT_INVINCIBLE: name = "item-speed"; rgb = '1 0 1'; break;
431                                 }
432                         }
433                         else
434                         {
435                                 switch(self.items)
436                                 {
437                                         case IT_STRENGTH:   name = "item-strength"; rgb = '0 0 1'; break;
438                                         case IT_INVINCIBLE: name = "item-shield"; rgb = '1 0 1'; break;
439                                 }
440                         }
441                         switch(self.items)
442                         {
443                                 case IT_FUEL_REGEN:     name = "item-fuelregen"; rgb = '1 0.5 0'; break;
444                                 case IT_JETPACK:        name = "item-jetpack"; rgb = '0.5 0.5 0.5'; break;
445                         }
446                         if(self.flags & FL_WEAPON)
447                         {
448                                 entity wi = get_weaponinfo(self.weapon);
449                                 if(wi)
450                                 {
451                                         name = wi.model2;
452                                         rgb = '1 0 0';
453                                 }
454                         }
455                         if(!name)
456                         {
457                                 print("Unknown powerup-marked item is wanting to respawn\n");
458                                 localcmd(sprintf("prvm_edict server %d\n", num_for_edict(self)));
459                         }
460                         if(name)
461                         {
462                                 WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb);
463                                 if(self.waypointsprite_attached)
464                                         WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, time + ITEM_RESPAWN_TICKS);
465                         }
466                 }
467                 sound (self, CH_TRIGGER, "misc/itemrespawncountdown.wav", VOL_BASE, ATTN_NORM); // play respawn sound
468                 if(self.waypointsprite_attached)
469                 {
470                         WaypointSprite_Ping(self.waypointsprite_attached);
471                         //WaypointSprite_UpdateHealth(self.waypointsprite_attached, self.count);
472                 }
473         }
474 }
475
476 void Item_ScheduleRespawnIn(entity e, float t)
477 {
478         if((e.flags & FL_POWERUP) || WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
479         {
480                 e.think = Item_RespawnCountdown;
481                 e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
482                 e.count = 0;
483         }
484         else
485         {
486                 e.think = Item_Respawn;
487                 e.nextthink = time + t;
488         }
489 }
490
491 void Item_ScheduleRespawn(entity e)
492 {
493         if(e.respawntime > 0)
494         {
495                 Item_Show(e, 0);
496                 Item_ScheduleRespawnIn(e, ITEM_RESPAWNTIME(e));
497         }
498         else // if respawntime is -1, this item does not respawn
499                 Item_Show(e, -1);
500 }
501
502 void Item_ScheduleInitialRespawn(entity e)
503 {
504         Item_Show(e, 0);
505         Item_ScheduleRespawnIn(e, game_starttime - time + ITEM_RESPAWNTIME_INITIAL(e));
506 }
507
508 float ITEM_MODE_NONE = 0;
509 float ITEM_MODE_HEALTH = 1;
510 float ITEM_MODE_ARMOR = 2;
511 float ITEM_MODE_FUEL = 3;
512 float Item_GiveAmmoTo(entity item, entity player, .float ammofield, float ammomax, float mode)
513 {
514         if (!item.ammofield)
515                 return FALSE;
516
517         if (item.spawnshieldtime)
518         {
519                 if ((player.ammofield < ammomax) || item.pickup_anyway)
520                 {
521                         player.ammofield = bound(player.ammofield, ammomax, player.ammofield + item.ammofield);
522                         goto YEAH;
523                 }
524         }
525         else if(g_weapon_stay == 2)
526         {
527                 float mi = min(item.ammofield, ammomax);
528                 if (player.ammofield < mi)
529                 {
530                         player.ammofield = mi;
531                         goto YEAH;
532                 }
533         }
534
535         return FALSE;
536
537 :YEAH
538         switch(mode)
539         {
540                 case ITEM_MODE_FUEL:
541                         player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + autocvar_g_balance_pause_fuel_rot);
542                         break;
543                 case ITEM_MODE_HEALTH:
544                         player.pauserothealth_finished = max(player.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
545                         break;
546                 case ITEM_MODE_ARMOR:
547                         player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + autocvar_g_balance_pause_armor_rot);
548                         break;
549                 default:
550                         break;
551         }
552         return TRUE;
553 }
554
555 float Item_GiveTo(entity item, entity player)
556 {
557         float _switchweapon;
558         float pickedup;
559         float it;
560         float i;
561
562         // if nothing happens to player, just return without taking the item
563         pickedup = FALSE;
564         _switchweapon = FALSE;
565
566         if (g_minstagib)
567         {
568                 float prevcells = player.ammo_cells;
569
570                 pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
571                 pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, 999, ITEM_MODE_NONE);
572
573                 if(player.ammo_cells > prevcells)
574                 {
575                         _switchweapon = TRUE;
576
577                         // play some cool sounds ;)
578                         if (clienttype(player) == CLIENTTYPE_REAL)
579                         {
580                                 if(player.health <= 5)
581                                         AnnounceTo(player, "lastsecond");
582                                 else if(player.health < 50)
583                                         AnnounceTo(player, "narrowly");
584                         }
585                         // sound not available
586                         // else if(item.items == IT_CELLS)
587                         //      AnnounceTo(player, "ammo");
588
589                         if (WEPSET_CONTAINS_EW(item, WEP_MINSTANEX))
590                                 W_GiveWeapon (player, WEP_MINSTANEX, item.netname);
591                         player.health = 100;
592                 }
593
594                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
595                 {
596                         pickedup = TRUE;
597                         player.items |= it;
598                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
599                 }
600
601                 // extralife powerup
602                 if (item.max_health)
603                 {
604                         pickedup = TRUE;
605                         // sound not available
606                         // AnnounceTo(player, "_lives");
607                         player.armorvalue = bound(player.armorvalue, 999, player.armorvalue + autocvar_g_minstagib_extralives);
608                         sprint(player, "^3You picked up some extra lives\n");
609                 }
610
611                 // invis powerup
612                 if (item.strength_finished)
613                 {
614                         pickedup = TRUE;
615                         // sound not available
616                         // AnnounceTo(player, "invisible");
617                         player.strength_finished = max(player.strength_finished, time) + autocvar_g_balance_powerup_strength_time;
618                 }
619
620                 // speed powerup
621                 if (item.invincible_finished)
622                 {
623                         pickedup = TRUE;
624                         // sound not available
625                         // AnnounceTo(player, "speed");
626                         player.invincible_finished = max(player.invincible_finished, time) + autocvar_g_balance_powerup_strength_time;
627                 }
628         }
629         else
630         {
631                 // in case the player has autoswitch enabled do the following:
632                 // if the player is using their best weapon before items are given, they
633                 // probably want to switch to an even better weapon after items are given
634                 if (player.autoswitch)
635                 if (player.switchweapon == w_getbestweapon(player))
636                         _switchweapon = TRUE;
637
638                 if not(WEPSET_CONTAINS_EW(player, player.switchweapon))
639                         _switchweapon = TRUE;
640
641                 pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
642                 pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max, ITEM_MODE_NONE);
643                 pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max, ITEM_MODE_NONE);
644                 pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max, ITEM_MODE_NONE);
645                 pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max, ITEM_MODE_NONE);
646                 pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health, ITEM_MODE_HEALTH);
647                 pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue, ITEM_MODE_ARMOR);
648
649                 if (item.flags & FL_WEAPON)
650                 {
651                         WEPSET_DECLARE_A(it);
652                         WEPSET_COPY_AE(it, item);
653                         WEPSET_ANDNOT_AE(it, player);
654
655                         if (!WEPSET_EMPTY_A(it) || (item.spawnshieldtime && self.pickup_anyway))
656                         {
657                                 pickedup = TRUE;
658                                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
659                                         if(WEPSET_CONTAINS_AW(it, i))
660                                                 W_GiveWeapon (player, i, item.netname);
661                         }
662                 }
663
664                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
665                 {
666                         pickedup = TRUE;
667                         player.items |= it;
668                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
669                 }
670
671                 if (item.strength_finished)
672                 {
673                         pickedup = TRUE;
674                         player.strength_finished = max(player.strength_finished, time) + item.strength_finished;
675                 }
676                 if (item.invincible_finished)
677                 {
678                         pickedup = TRUE;
679                         player.invincible_finished = max(player.invincible_finished, time) + item.invincible_finished;
680                 }
681                 if (item.superweapons_finished)
682                 {
683                         pickedup = TRUE;
684                         player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished;
685                 }
686         }
687
688 :skip
689         // always eat teamed entities
690         if(item.team)
691                 pickedup = TRUE;
692
693         if (!pickedup)
694                 return 0;
695
696         if (_switchweapon)
697                 if (player.switchweapon != w_getbestweapon(player))
698                         W_SwitchWeapon_Force(player, w_getbestweapon(player));
699
700         return 1;
701 }
702
703 void Item_Touch (void)
704 {
705         entity e, head;
706
707         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
708         if(self.classname == "droppedweapon")
709         {
710                 if (ITEM_TOUCH_NEEDKILL())
711                 {
712                         remove(self);
713                         return;
714                 }
715         }
716
717         if (other.classname != "player")
718                 return;
719         if (other.deadflag)
720                 return;
721         if (self.solid != SOLID_TRIGGER)
722                 return;
723         if (self.owner == other)
724                 return;
725
726         if (self.classname == "droppedweapon")
727         {
728                 self.strength_finished = max(0, self.strength_finished - time);
729                 self.invincible_finished = max(0, self.invincible_finished - time);
730                 self.superweapons_finished = max(0, self.superweapons_finished - time);
731         }
732
733         if(!Item_GiveTo(self, other))
734         {
735                 if (self.classname == "droppedweapon")
736                 {
737                         // undo what we did above
738                         self.strength_finished += time;
739                         self.invincible_finished += time;
740                         self.superweapons_finished += time;
741                 }
742                 return;
743         }
744
745         other.last_pickup = time;
746
747         pointparticles(particleeffectnum("item_pickup"), self.origin, '0 0 0', 1);
748         sound (other, CH_TRIGGER, self.item_pickupsound, VOL_BASE, ATTN_NORM);
749
750         if (self.classname == "droppedweapon")
751                 remove (self);
752         else if not(self.spawnshieldtime)
753                 return;
754         else
755         {
756                 if(self.team)
757                 {
758                         RandomSelection_Init();
759                         for(head = world; (head = findfloat(head, team, self.team)); )
760                         {
761                                 if(head.flags & FL_ITEM)
762                                 {
763                                         Item_Show(head, -1);
764                                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
765                                 }
766                         }
767                         e = RandomSelection_chosen_ent;
768
769                 }
770                 else
771                         e = self;
772                 Item_ScheduleRespawn(e);
773         }
774 }
775
776 void Item_Reset()
777 {
778         Item_Show(self, !self.state);
779         setorigin (self, self.origin);
780
781         if(self.classname != "droppedweapon")
782         {
783                 self.think = SUB_Null;
784                 self.nextthink = 0;
785
786                 if(self.waypointsprite_attached)
787                         WaypointSprite_Kill(self.waypointsprite_attached);
788
789                 if((self.flags & FL_POWERUP) | WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS)) // do not spawn powerups initially!
790                         Item_ScheduleInitialRespawn(self);
791         }
792 }
793
794 void Item_FindTeam()
795 {
796         entity head, e;
797
798         if(self.effects & EF_NODRAW)
799         {
800                 // marker for item team search
801                 dprint("Initializing item team ", ftos(self.team), "\n");
802                 RandomSelection_Init();
803                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
804                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
805                 e = RandomSelection_chosen_ent;
806                 e.state = 0;
807                 Item_Show(e, 1);
808
809                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
810                 {
811                         if(head != e)
812                         {
813                                 // make it a non-spawned item
814                                 Item_Show(head, -1);
815                                 head.state = 1; // state 1 = initially hidden item
816                         }
817                         head.effects &~= EF_NODRAW;
818                 }
819
820                 Item_Reset();
821         }
822 }
823
824 // Savage: used for item garbage-collection
825 // TODO: perhaps nice special effect?
826 void RemoveItem(void)
827 {
828         remove(self);
829 }
830
831 // pickup evaluation functions
832 // these functions decide how desirable an item is to the bots
833
834 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;}
835
836 float weapon_pickupevalfunc(entity player, entity item)
837 {
838         float c, j, position;
839
840         // See if I have it already
841         if(!WEPSET_CONTAINS_ALL_EE(player, item))
842         {
843                 // If I can pick it up
844                 if(!item.spawnshieldtime)
845                         c = 0;
846                 else if(player.ammo_cells || player.ammo_shells || player.ammo_nails || player.ammo_rockets)
847                 {
848                         // Skilled bots will grab more
849                         c = bound(0, skill / 10, 1) * 0.5;
850                 }
851                 else
852                         c = 0;
853         }
854         else
855                 c = 1;
856
857         // If custom weapon priorities for bots is enabled rate most wanted weapons higher
858         if( bot_custom_weapon && c )
859         {
860                 // Find the highest position on any range
861                 position = -1;
862                 for(j = 0; j < WEP_LAST ; ++j){
863                         if(
864                                         bot_weapons_far[j] == item.weapon ||
865                                         bot_weapons_mid[j] == item.weapon ||
866                                         bot_weapons_close[j] == item.weapon
867                           )
868                         {
869                                 position = j;
870                                 break;
871                         }
872                 }
873
874                 // Rate it
875                 if (position >= 0 )
876                 {
877                         position = WEP_LAST - position;
878                         // item.bot_pickupbasevalue is overwritten here
879                         return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
880                 }
881         }
882
883         return item.bot_pickupbasevalue * c;
884 }
885
886 float commodity_pickupevalfunc(entity player, entity item)
887 {
888         float c, i;
889         float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_fuel = FALSE;
890         entity wi;
891         c = 0;
892
893         // Detect needed ammo
894         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
895         {
896                 wi = get_weaponinfo(i);
897
898                 if not(WEPSET_CONTAINS_EW(player, i))
899                         continue;
900
901                 if(wi.items & IT_SHELLS)
902                         need_shells = TRUE;
903                 else if(wi.items & IT_NAILS)
904                         need_nails = TRUE;
905                 else if(wi.items & IT_ROCKETS)
906                         need_rockets = TRUE;
907                 else if(wi.items & IT_CELLS)
908                         need_cells = TRUE;
909                 else if(wi.items & IT_FUEL)
910                         need_cells = TRUE;
911         }
912
913         // TODO: figure out if the player even has the weapon this ammo is for?
914         // may not affect strategy much though...
915         // find out how much more ammo/armor/health the player can hold
916         if (need_shells)
917         if (item.ammo_shells)
918         if (player.ammo_shells < g_pickup_shells_max)
919                 c = c + max(0, 1 - player.ammo_shells / g_pickup_shells_max);
920         if (need_nails)
921         if (item.ammo_nails)
922         if (player.ammo_nails < g_pickup_nails_max)
923                 c = c + max(0, 1 - player.ammo_nails / g_pickup_nails_max);
924         if (need_rockets)
925         if (item.ammo_rockets)
926         if (player.ammo_rockets < g_pickup_rockets_max)
927                 c = c + max(0, 1 - player.ammo_rockets / g_pickup_rockets_max);
928         if (need_cells)
929         if (item.ammo_cells)
930         if (player.ammo_cells < g_pickup_cells_max)
931                 c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
932         if (need_fuel)
933         if (item.ammo_fuel)
934         if (player.ammo_fuel < g_pickup_fuel_max)
935                 c = c + max(0, 1 - player.ammo_fuel / g_pickup_fuel_max);
936         if (item.armorvalue)
937         if (player.armorvalue < item.max_armorvalue)
938                 c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
939         if (item.health)
940         if (player.health < item.max_health)
941                 c = c + max(0, 1 - player.health / item.max_health);
942
943         return item.bot_pickupbasevalue * c;
944 }
945
946 void Item_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
947 {
948         if(ITEM_DAMAGE_NEEDKILL(deathtype))
949                 RemoveItem();
950 }
951
952 .float is_item;
953 void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, float defaultrespawntimejitter, string itemname, float itemid, float weaponid, float itemflags, float(entity player, entity item) pickupevalfunc, float pickupbasevalue)
954 {
955         startitem_failed = FALSE;
956
957         if(self.model == "")
958                 self.model = itemmodel;
959         
960         if(self.item_pickupsound == "")
961                 self.item_pickupsound = pickupsound;
962         
963         if(!self.respawntime) // both need to be set
964         {
965                 self.respawntime = defaultrespawntime;
966                 self.respawntimejitter = defaultrespawntimejitter;
967         }
968
969         self.items = itemid;
970         self.weapon = weaponid;
971
972         if(weaponid)
973                 WEPSET_COPY_EW(self, weaponid);
974         
975         self.flags = FL_ITEM | itemflags;
976
977         if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
978         {
979                 startitem_failed = TRUE;
980                 remove(self);
981                 return;
982         }
983
984         // is it a dropped weapon?
985         if (self.classname == "droppedweapon")
986         {
987                 self.reset = SUB_Remove;
988                 // it's a dropped weapon
989                 self.movetype = MOVETYPE_TOSS;
990
991                 // Savage: remove thrown items after a certain period of time ("garbage collection")
992                 self.think = RemoveItem;
993                 self.nextthink = time + 20;
994
995                 self.takedamage = DAMAGE_YES;
996                 self.event_damage = Item_Damage;
997
998                 if(self.strength_finished || self.invincible_finished || self.superweapons_finished)
999                 /*
1000                 if(self.items == 0)
1001                 if(WEPSET_CONTAINS_ALL_AE(WEPBIT_SUPERWEAPONS, self)) // only superweapons
1002                 if(self.ammo_nails == 0)
1003                 if(self.ammo_cells == 0)
1004                 if(self.ammo_rockets == 0)
1005                 if(self.ammo_shells == 0)
1006                 if(self.ammo_fuel == 0)
1007                 if(self.health == 0)
1008                 if(self.armorvalue == 0)
1009                 */
1010                 {
1011                         // if item is worthless after a timer, have it expire then
1012                         self.nextthink = max(self.strength_finished, self.invincible_finished, self.superweapons_finished);
1013                 }
1014
1015                 // don't drop if in a NODROP zone (such as lava)
1016                 traceline(self.origin, self.origin, MOVE_NORMAL, self);
1017                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
1018                 {
1019                         startitem_failed = TRUE;
1020                         remove(self);
1021                         return;
1022                 }
1023         }
1024         else
1025         {
1026                 if(!have_pickup_item())
1027                 {
1028                         startitem_failed = TRUE;
1029                         remove (self);
1030                         return;
1031                 }
1032
1033                 self.reset = Item_Reset;
1034                 // it's a level item
1035                 if(self.spawnflags & 1)
1036                         self.noalign = 1;
1037                 if (self.noalign)
1038                         self.movetype = MOVETYPE_NONE;
1039                 else
1040                         self.movetype = MOVETYPE_TOSS;
1041                 // do item filtering according to game mode and other things
1042                 if (!self.noalign)
1043                 {
1044                         // first nudge it off the floor a little bit to avoid math errors
1045                         setorigin(self, self.origin + '0 0 1');
1046                         // set item size before we spawn a spawnfunc_waypoint
1047                         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
1048                                 setsize (self, '-16 -16 0', '16 16 48');
1049                         else
1050                                 setsize (self, '-16 -16 0', '16 16 32');
1051                         // note droptofloor returns FALSE if stuck/or would fall too far
1052                         droptofloor();
1053                         waypoint_spawnforitem(self);
1054                 }
1055
1056                 /*
1057                  * can't do it that way, as it would break maps
1058                  * TODO make a target_give like entity another way, that perhaps has
1059                  * the weapon name in a key
1060                 if(self.targetname)
1061                 {
1062                         // target_give not yet supported; maybe later
1063                         print("removed targeted ", self.classname, "\n");
1064                         startitem_failed = TRUE;
1065                         remove (self);
1066                         return;
1067                 }
1068                 */
1069
1070                 if(autocvar_spawn_debug >= 2)
1071                 {
1072                         entity otheritem;
1073                         for(otheritem = findradius(self.origin, 3); otheritem; otheritem = otheritem.chain)
1074                         {
1075                             // why not flags & fl_item?
1076                                 if(otheritem.is_item)
1077                                 {
1078                                         dprint("XXX Found duplicated item: ", itemname, vtos(self.origin));
1079                                         dprint(" vs ", otheritem.netname, vtos(otheritem.origin), "\n");
1080                                         error("Mapper sucks.");
1081                                 }
1082                         }
1083                         self.is_item = TRUE;
1084                 }
1085
1086                 WEPSET_OR_AW(weaponsInMap, weaponid);
1087
1088                 precache_model (self.model);
1089                 precache_sound (self.item_pickupsound);
1090
1091                 precache_sound ("misc/itemrespawncountdown.wav");
1092                 if(!g_minstagib && itemid == IT_STRENGTH)
1093                         precache_sound ("misc/strength_respawn.wav");
1094                 else if(!g_minstagib && itemid == IT_INVINCIBLE)
1095                         precache_sound ("misc/shield_respawn.wav");
1096                 else
1097                         precache_sound ("misc/itemrespawn.wav");
1098
1099                 if((itemflags & (FL_POWERUP | FL_WEAPON)) || (itemid & (IT_HEALTH | IT_ARMOR | IT_KEY1 | IT_KEY2)))
1100                         self.target = "###item###"; // for finding the nearest item using find()
1101         }
1102
1103         self.bot_pickup = TRUE;
1104         self.bot_pickupevalfunc = pickupevalfunc;
1105         self.bot_pickupbasevalue = pickupbasevalue;
1106         self.mdl = self.model;
1107         self.netname = itemname;
1108         self.touch = Item_Touch;
1109         setmodel(self, "null"); // precision set below
1110         //self.effects |= EF_LOWPRECISION; 
1111         
1112         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
1113     {
1114         self.pos1 = '-16 -16 0';
1115         self.pos2 = '16 16 48';
1116     }
1117         else
1118     {
1119         self.pos1 = '-16 -16 0';
1120         self.pos2 = '16 16 32';
1121     }
1122     setsize (self, self.pos1, self.pos2);
1123     
1124     if(itemflags & FL_POWERUP) 
1125         self.ItemStatus |= ITS_ANIMATE1;
1126         
1127         if(self.armorvalue || self.health)
1128         self.ItemStatus |= ITS_ANIMATE2;
1129         
1130         if(itemflags & FL_WEAPON)
1131         {
1132                 if (self.classname != "droppedweapon") // if dropped, colormap is already set up nicely
1133             self.colormap = 1024; // color shirt=0 pants=0 grey
1134         else
1135             self.gravity = 1;
1136             
1137                 self.ItemStatus |= ITS_ANIMATE1;
1138                 self.ItemStatus |= ISF_COLORMAP;
1139         }
1140
1141         self.state = 0;
1142         if(self.team) // broken, no idea why.
1143         {
1144                 if(!self.cnt)
1145                         self.cnt = 1; // item probability weight
1146                         
1147                 self.effects |= EF_NODRAW; // marker for item team search
1148                 InitializeEntity(self, Item_FindTeam, INITPRIO_FINDTARGET);
1149         }
1150         else
1151                 Item_Reset();
1152         
1153     Net_LinkEntity(self, FALSE, 0, ItemSend);
1154 }
1155
1156 /* replace items in minstagib
1157  * IT_STRENGTH   = invisibility
1158  * IT_NAILS      = extra lives
1159  * IT_INVINCIBLE = speed
1160  */
1161 void minstagib_items (float itemid) // will be deleted soon.
1162 {
1163         float rnd;
1164         self.classname = "minstagib"; // ...?
1165
1166         // replace rocket launchers and nex guns with ammo cells
1167         if (itemid == IT_CELLS)
1168         {
1169                 self.ammo_cells = autocvar_g_minstagib_ammo_drop;
1170                 StartItem ("models/items/a_cells.md3",
1171                         "misc/itempickup.wav", 45, 0,
1172                         "MinstaNex Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100);
1173                 return;
1174         }
1175
1176         // randomize
1177         rnd = random() * 3;
1178         if (rnd <= 1)
1179                 itemid = IT_STRENGTH;
1180         else if (rnd <= 2)
1181                 itemid = IT_NAILS;
1182         else
1183                 itemid = IT_INVINCIBLE;
1184
1185         // replace with invis
1186         if (itemid == IT_STRENGTH)
1187         {
1188                 if(!self.strength_finished)
1189                         self.strength_finished = autocvar_g_balance_powerup_strength_time;
1190                 StartItem ("models/items/g_strength.md3",
1191                         "misc/powerup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
1192                         "Invisibility", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_MID);
1193         }
1194         // replace with extra lives
1195         if (itemid == IT_NAILS)
1196         {
1197                 self.max_health = 1;
1198                 StartItem ("models/items/g_h100.md3",
1199                         "misc/megahealth.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
1200                         "Extralife", IT_NAILS, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1201         }
1202         // replace with speed
1203         if (itemid == IT_INVINCIBLE)
1204         {
1205                 if(!self.invincible_finished)
1206                         self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1207                 StartItem ("models/items/g_invincible.md3",
1208                         "misc/powerup_shield.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
1209                         "Speed", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_MID);
1210         }
1211 }
1212
1213 float minst_no_auto_cells;
1214 void minst_remove_item (void) {
1215         if(minst_no_auto_cells)
1216                 remove(self);
1217 }
1218
1219 float weaponswapping;
1220 float internalteam;
1221
1222 void weapon_defaultspawnfunc(float wpn)
1223 {
1224         entity e;
1225         float t;
1226         var .float ammofield;
1227         string s;
1228         entity oldself;
1229         float i, j;
1230         float f;
1231
1232         if(self.classname != "droppedweapon" && self.classname != "replacedweapon")
1233         {
1234                 e = get_weaponinfo(wpn);
1235
1236                 if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
1237                 {
1238                         print("Attempted to spawn a mutator-blocked weapon; these guns will in the future require a mutator\n");
1239                         /*
1240                         objerror("Attempted to spawn a mutator-blocked weapon rejected");
1241                         startitem_failed = TRUE;
1242                         return;
1243                         */
1244                 }
1245
1246                 s = W_Apply_Weaponreplace(e.netname);
1247                 ret_string = s;
1248                 other = e;
1249                 MUTATOR_CALLHOOK(SetWeaponreplace);
1250                 s = ret_string;
1251                 if(s == "")
1252                 {
1253                         remove(self);
1254                         startitem_failed = TRUE;
1255                         return;
1256                 }
1257                 t = tokenize_console(s);
1258                 if(t >= 2)
1259                 {
1260                         self.team = --internalteam;
1261                         oldself = self;
1262                         for(i = 1; i < t; ++i)
1263                         {
1264                                 s = argv(i);
1265                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1266                                 {
1267                                         e = get_weaponinfo(j);
1268                                         if(e.netname == s)
1269                                         {
1270                                                 self = spawn();
1271                                                 copyentity(oldself, self);
1272                                                 self.classname = "replacedweapon";
1273                                                 weapon_defaultspawnfunc(j);
1274                                                 break;
1275                                         }
1276                                 }
1277                                 if(j > WEP_LAST)
1278                                 {
1279                                         print("The weapon replace list for ", oldself.classname, " contains an unknown weapon ", s, ". Skipped.\n");
1280                                 }
1281                         }
1282                         self = oldself;
1283                 }
1284                 if(t >= 1) // always the case!
1285                 {
1286                         s = argv(0);
1287                         wpn = 0;
1288                         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1289                         {
1290                                 e = get_weaponinfo(j);
1291                                 if(e.netname == s)
1292                                 {
1293                                         wpn = j;
1294                                         break;
1295                                 }
1296                         }
1297                         if(j > WEP_LAST)
1298                         {
1299                                 print("The weapon replace list for ", self.classname, " contains an unknown weapon ", s, ". Skipped.\n");
1300                         }
1301                 }
1302                 if(wpn == 0)
1303                 {
1304                         remove(self);
1305                         startitem_failed = TRUE;
1306                         return;
1307                 }
1308         }
1309
1310         e = get_weaponinfo(wpn);
1311
1312         if(!self.respawntime)
1313         {
1314                 if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
1315                 {
1316                         self.respawntime = g_pickup_respawntime_superweapon;
1317                         self.respawntimejitter = g_pickup_respawntimejitter_superweapon;
1318                 }
1319                 else
1320                 {
1321                         self.respawntime = g_pickup_respawntime_weapon;
1322                         self.respawntimejitter = g_pickup_respawntimejitter_weapon;
1323                 }
1324         }
1325
1326         if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
1327                 if(!self.superweapons_finished)
1328                         self.superweapons_finished = autocvar_g_balance_superweapons_time;
1329
1330         if(e.items)
1331         {
1332                 for(i = 0, j = 1; i < 24; ++i, j *= 2)
1333                 {
1334                         if(e.items & j)
1335                         {
1336                                 ammofield = Item_CounterField(j);
1337                                 if(!self.ammofield)
1338                                         self.ammofield = cvar(strcat("g_pickup_", Item_CounterFieldName(j), "_weapon"));
1339                         }
1340                 }
1341         }
1342
1343         // pickup anyway
1344         if(g_pickup_weapons_anyway)
1345                 self.pickup_anyway = TRUE;
1346
1347         f = FL_WEAPON;
1348
1349         // no weapon-stay on superweapons
1350         if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
1351                 f |= FL_NO_WEAPON_STAY;
1352
1353         // weapon stay isn't supported for teamed weapons
1354         if(self.team)
1355                 f |= FL_NO_WEAPON_STAY;
1356
1357         // stupid minstagib hack, don't ask
1358         if(g_minstagib)
1359                 if(self.ammo_cells)
1360                         self.ammo_cells = autocvar_g_minstagib_ammo_drop;
1361
1362         StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapon, f, weapon_pickupevalfunc, e.bot_pickupbasevalue);
1363         if (self.modelindex) // don't precache if self was removed
1364                 weapon_action(e.weapon, WR_PRECACHE);
1365 }
1366
1367 void spawnfunc_weapon_shotgun (void);
1368 void spawnfunc_weapon_uzi (void) {
1369         if(autocvar_sv_q3acompat_machineshotgunswap)
1370         if(self.classname != "droppedweapon")
1371         {
1372                 weapon_defaultspawnfunc(WEP_SHOTGUN);
1373                 return;
1374         }
1375         weapon_defaultspawnfunc(WEP_UZI);
1376 }
1377
1378 void spawnfunc_weapon_shotgun (void) {
1379         if(autocvar_sv_q3acompat_machineshotgunswap)
1380         if(self.classname != "droppedweapon")
1381         {
1382                 weapon_defaultspawnfunc(WEP_UZI);
1383                 return;
1384         }
1385         weapon_defaultspawnfunc(WEP_SHOTGUN);
1386 }
1387
1388 void spawnfunc_weapon_nex (void)
1389 {
1390         if (g_minstagib)
1391         {
1392                 minstagib_items(IT_CELLS);
1393                 self.think = minst_remove_item;
1394                 self.nextthink = time;
1395                 return;
1396         }
1397         weapon_defaultspawnfunc(WEP_NEX);
1398 }
1399
1400 void spawnfunc_weapon_minstanex (void)
1401 {
1402         if (g_minstagib)
1403         {
1404                 minstagib_items(IT_CELLS);
1405                 self.think = minst_remove_item;
1406                 self.nextthink = time;
1407                 return;
1408         }
1409         weapon_defaultspawnfunc(WEP_MINSTANEX);
1410 }
1411
1412 void spawnfunc_weapon_rocketlauncher (void)
1413 {
1414         if (g_minstagib)
1415         {
1416                 minstagib_items(IT_CELLS); // replace rocketlauncher with cells
1417                 self.think = minst_remove_item;
1418                 self.nextthink = time;
1419                 return;
1420         }
1421         weapon_defaultspawnfunc(WEP_ROCKET_LAUNCHER);
1422 }
1423
1424 void spawnfunc_item_rockets (void) {
1425         if(!self.ammo_rockets)
1426                 self.ammo_rockets = g_pickup_rockets;
1427         if(!self.pickup_anyway)
1428                 self.pickup_anyway = g_pickup_ammo_anyway;
1429         StartItem ("models/items/a_rockets.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "rockets", IT_ROCKETS, 0, 0, commodity_pickupevalfunc, 3000);
1430 }
1431
1432 void spawnfunc_item_shells (void);
1433 void spawnfunc_item_bullets (void) {
1434         if(!weaponswapping)
1435         if(autocvar_sv_q3acompat_machineshotgunswap)
1436         if(self.classname != "droppedweapon")
1437         {
1438                 weaponswapping = TRUE;
1439                 spawnfunc_item_shells();
1440                 weaponswapping = FALSE;
1441                 return;
1442         }
1443
1444         if(!self.ammo_nails)
1445                 self.ammo_nails = g_pickup_nails;
1446         if(!self.pickup_anyway)
1447                 self.pickup_anyway = g_pickup_ammo_anyway;
1448         StartItem ("models/items/a_bullets.mdl", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "bullets", IT_NAILS, 0, 0, commodity_pickupevalfunc, 2000);
1449 }
1450
1451 void spawnfunc_item_cells (void) {
1452         if(!self.ammo_cells)
1453                 self.ammo_cells = g_pickup_cells;
1454         if(!self.pickup_anyway)
1455                 self.pickup_anyway = g_pickup_ammo_anyway;
1456         StartItem ("models/items/a_cells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "cells", IT_CELLS, 0, 0, commodity_pickupevalfunc, 2000);
1457 }
1458
1459 void spawnfunc_item_shells (void) {
1460         if(!weaponswapping)
1461         if(autocvar_sv_q3acompat_machineshotgunswap)
1462         if(self.classname != "droppedweapon")
1463         {
1464                 weaponswapping = TRUE;
1465                 spawnfunc_item_bullets();
1466                 weaponswapping = FALSE;
1467                 return;
1468         }
1469
1470         if(!self.ammo_shells)
1471                 self.ammo_shells = g_pickup_shells;
1472         if(!self.pickup_anyway)
1473                 self.pickup_anyway = g_pickup_ammo_anyway;
1474         StartItem ("models/items/a_shells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "shells", IT_SHELLS, 0, 0, commodity_pickupevalfunc, 500);
1475 }
1476
1477 void spawnfunc_item_armor_small (void) {
1478         if(!self.armorvalue)
1479                 self.armorvalue = g_pickup_armorsmall;
1480         if(!self.max_armorvalue)
1481                 self.max_armorvalue = g_pickup_armorsmall_max;
1482         if(!self.pickup_anyway)
1483                 self.pickup_anyway = g_pickup_armorsmall_anyway;
1484         StartItem ("models/items/item_armor_small.md3", "misc/armor1.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Armor", IT_ARMOR_SHARD, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1485 }
1486
1487 void spawnfunc_item_armor_medium (void) {
1488         if(!self.armorvalue)
1489                 self.armorvalue = g_pickup_armormedium;
1490         if(!self.max_armorvalue)
1491                 self.max_armorvalue = g_pickup_armormedium_max;
1492         if(!self.pickup_anyway)
1493                 self.pickup_anyway = g_pickup_armormedium_anyway;
1494         StartItem ("models/items/item_armor_medium.md3", "misc/armor10.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "25 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1495 }
1496
1497 void spawnfunc_item_armor_big (void) {
1498         if(!self.armorvalue)
1499                 self.armorvalue = g_pickup_armorbig;
1500         if(!self.max_armorvalue)
1501                 self.max_armorvalue = g_pickup_armorbig_max;
1502         if(!self.pickup_anyway)
1503                 self.pickup_anyway = g_pickup_armorbig_anyway;
1504         StartItem ("models/items/item_armor_big.md3", "misc/armor17_5.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "50 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000);
1505 }
1506
1507 void spawnfunc_item_armor_large (void) {
1508         if(!self.armorvalue)
1509                 self.armorvalue = g_pickup_armorlarge;
1510         if(!self.max_armorvalue)
1511                 self.max_armorvalue = g_pickup_armorlarge_max;
1512         if(!self.pickup_anyway)
1513                 self.pickup_anyway = g_pickup_armorlarge_anyway;
1514         StartItem ("models/items/item_armor_large.md3", "misc/armor25.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1515 }
1516
1517 void spawnfunc_item_health_small (void) {
1518         if(!self.max_health)
1519                 self.max_health = g_pickup_healthsmall_max;
1520         if(!self.health)
1521                 self.health = g_pickup_healthsmall;
1522         if(!self.pickup_anyway)
1523                 self.pickup_anyway = g_pickup_healthsmall_anyway;
1524         StartItem ("models/items/g_h1.md3", "misc/minihealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Health", IT_5HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1525 }
1526
1527 void spawnfunc_item_health_medium (void) {
1528         if(!self.max_health)
1529                 self.max_health = g_pickup_healthmedium_max;
1530         if(!self.health)
1531                 self.health = g_pickup_healthmedium;
1532         if(!self.pickup_anyway)
1533                 self.pickup_anyway = g_pickup_healthmedium_anyway;
1534         StartItem ("models/items/g_h25.md3", "misc/mediumhealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "25 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1535 }
1536
1537 void spawnfunc_item_health_large (void) {
1538         if(!self.max_health)
1539                 self.max_health = g_pickup_healthlarge_max;
1540         if(!self.health)
1541                 self.health = g_pickup_healthlarge;
1542         if(!self.pickup_anyway)
1543                 self.pickup_anyway = g_pickup_healthlarge_anyway;
1544         StartItem ("models/items/g_h50.md3", "misc/mediumhealth.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "50 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1545 }
1546
1547 void spawnfunc_item_health_mega (void) {
1548         if(g_minstagib) {
1549                 minstagib_items(IT_NAILS);
1550         } else {
1551                 if(!self.max_health)
1552                         self.max_health = g_pickup_healthmega_max;
1553                 if(!self.health)
1554                         self.health = g_pickup_healthmega;
1555                 if(!self.pickup_anyway)
1556                         self.pickup_anyway = g_pickup_healthmega_anyway;
1557                 StartItem ("models/items/g_h100.md3", "misc/megahealth.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Health", IT_HEALTH, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1558         }
1559 }
1560
1561 // support old misnamed entities
1562 void spawnfunc_item_armor1() { spawnfunc_item_armor_small(); }  // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
1563 void spawnfunc_item_armor25() { spawnfunc_item_armor_large(); }
1564 void spawnfunc_item_health1() { spawnfunc_item_health_small(); }
1565 void spawnfunc_item_health25() { spawnfunc_item_health_medium(); }
1566 void spawnfunc_item_health100() { spawnfunc_item_health_mega(); }
1567
1568 void spawnfunc_item_strength (void) {
1569         if(g_minstagib) {
1570                 minstagib_items(IT_STRENGTH);
1571         } else {
1572                 precache_sound("weapons/strength_fire.wav");
1573                 if(!self.strength_finished)
1574                         self.strength_finished = autocvar_g_balance_powerup_strength_time;
1575                 StartItem ("models/items/g_strength.md3", "misc/powerup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Strength Powerup", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, 100000);
1576         }
1577 }
1578
1579 void spawnfunc_item_invincible (void) {
1580         if(g_minstagib) {
1581                 minstagib_items(IT_INVINCIBLE);
1582         } else {
1583                 if(!self.invincible_finished)
1584                         self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1585                 StartItem ("models/items/g_invincible.md3", "misc/powerup_shield.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Shield", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, 100000);
1586         }
1587 }
1588
1589 void spawnfunc_item_minst_cells (void) {
1590         if (g_minstagib)
1591         {
1592                 minst_no_auto_cells = TRUE;
1593                 minstagib_items(IT_CELLS);
1594         }
1595         else
1596                 remove(self);
1597 }
1598
1599 // compatibility:
1600 void spawnfunc_item_quad (void) {self.classname = "item_strength";spawnfunc_item_strength();}
1601
1602 float GiveItems(entity e, float beginarg, float endarg);
1603 void target_items_use (void)
1604 {
1605         if(activator.classname == "droppedweapon")
1606         {
1607                 EXACTTRIGGER_TOUCH;
1608                 remove(activator);
1609                 return;
1610         }
1611
1612         if(activator.classname != "player")
1613                 return;
1614         if(activator.deadflag != DEAD_NO)
1615                 return;
1616         EXACTTRIGGER_TOUCH;
1617
1618         entity e;
1619         for(e = world; (e = find(e, classname, "droppedweapon")); )
1620                 if(e.enemy == activator)
1621                         remove(e);
1622
1623         if(GiveItems(activator, 0, tokenize_console(self.netname)))
1624                 centerprint(activator, self.message);
1625 }
1626
1627 void spawnfunc_target_items (void)
1628 {
1629         float n, i, j;
1630         entity e;
1631
1632         self.use = target_items_use;
1633         if(!self.strength_finished)
1634                 self.strength_finished = autocvar_g_balance_powerup_strength_time;
1635         if(!self.invincible_finished)
1636                 self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1637         if(!self.superweapons_finished)
1638                 self.superweapons_finished = autocvar_g_balance_superweapons_time;
1639
1640         precache_sound("misc/itempickup.wav");
1641         precache_sound("misc/megahealth.wav");
1642         precache_sound("misc/armor25.wav");
1643         precache_sound("misc/powerup.wav");
1644         precache_sound("misc/poweroff.wav");
1645         precache_sound("weapons/weaponpickup.wav");
1646
1647         n = tokenize_console(self.netname);
1648         if(argv(0) == "give")
1649         {
1650                 self.netname = substring(self.netname, argv_start_index(1), argv_end_index(-1) - argv_start_index(1));
1651         }
1652         else
1653         {
1654                 for(i = 0; i < n; ++i)
1655                 {
1656                         if     (argv(i) == "unlimited_ammo")         self.items |= IT_UNLIMITED_AMMO;
1657                         else if(argv(i) == "unlimited_weapon_ammo")  self.items |= IT_UNLIMITED_WEAPON_AMMO;
1658                         else if(argv(i) == "unlimited_superweapons") self.items |= IT_UNLIMITED_SUPERWEAPONS;
1659                         else if(argv(i) == "strength")               self.items |= IT_STRENGTH;
1660                         else if(argv(i) == "invincible")             self.items |= IT_INVINCIBLE;
1661                         else if(argv(i) == "superweapons")           self.items |= IT_SUPERWEAPON;
1662                         else if(argv(i) == "jetpack")                self.items |= IT_JETPACK;
1663                         else if(argv(i) == "fuel_regen")             self.items |= IT_FUEL_REGEN;
1664                         else
1665                         {
1666                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1667                                 {
1668                                         e = get_weaponinfo(j);
1669                                         if(argv(i) == e.netname)
1670                                         {
1671                                                 WEPSET_OR_EW(self, j);
1672                                                 if(self.spawnflags == 0 || self.spawnflags == 2)
1673                                                         weapon_action(e.weapon, WR_PRECACHE);
1674                                                 break;
1675                                         }
1676                                 }
1677                                 if(j > WEP_LAST)
1678                                         print("target_items: invalid item ", argv(i), "\n");
1679                         }
1680                 }
1681
1682                 string itemprefix, valueprefix;
1683                 if(self.spawnflags == 0)
1684                 {
1685                         itemprefix = "";
1686                         valueprefix = "";
1687                 }
1688                 else if(self.spawnflags == 1)
1689                 {
1690                         itemprefix = "max ";
1691                         valueprefix = "max ";
1692                 }
1693                 else if(self.spawnflags == 2)
1694                 {
1695                         itemprefix = "min ";
1696                         valueprefix = "min ";
1697                 }
1698                 else if(self.spawnflags == 4)
1699                 {
1700                         itemprefix = "minus ";
1701                         valueprefix = "max ";
1702                 }
1703                 else
1704                         error("invalid spawnflags");
1705
1706                 self.netname = "";
1707                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_WEAPON_AMMO), "unlimited_weapon_ammo");
1708                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_SUPERWEAPONS), "unlimited_superweapons");
1709                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.strength_finished * !!(self.items & IT_STRENGTH), "strength");
1710                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.invincible_finished * !!(self.items & IT_INVINCIBLE), "invincible");
1711                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.superweapons_finished * !!(self.items & IT_SUPERWEAPON), "superweapons");
1712                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_JETPACK), "jetpack");
1713                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_FUEL_REGEN), "fuel_regen");
1714                 if(self.ammo_shells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_shells), "shells");
1715                 if(self.ammo_nails != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_nails), "nails");
1716                 if(self.ammo_rockets != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_rockets), "rockets");
1717                 if(self.ammo_cells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_cells), "cells");
1718                 if(self.ammo_fuel != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_fuel), "fuel");
1719                 if(self.health != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "health");
1720                 if(self.armorvalue != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "armor");
1721                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1722                 {
1723                         e = get_weaponinfo(j);
1724                         if(e.weapon)
1725                                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, WEPSET_CONTAINS_EW(self, j), e.netname);
1726                 }
1727         }
1728         self.netname = strzone(self.netname);
1729         //print(self.netname, "\n");
1730
1731         n = tokenize_console(self.netname);
1732         for(i = 0; i < n; ++i)
1733         {
1734                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1735                 {
1736                         e = get_weaponinfo(j);
1737                         if(argv(i) == e.netname)
1738                         {
1739                                 weapon_action(e.weapon, WR_PRECACHE);
1740                                 break;
1741                         }
1742                 }
1743         }
1744 }
1745
1746 void spawnfunc_item_fuel(void)
1747 {
1748         if(!self.ammo_fuel)
1749                 self.ammo_fuel = g_pickup_fuel;
1750         if(!self.pickup_anyway)
1751                 self.pickup_anyway = g_pickup_ammo_anyway;
1752         StartItem ("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1753 }
1754
1755 void spawnfunc_item_fuel_regen(void)
1756 {
1757         if(start_items & IT_FUEL_REGEN)
1758         {
1759                 spawnfunc_item_fuel();
1760                 return;
1761         }
1762         StartItem ("models/items/g_fuelregen.md3", "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Fuel regenerator", IT_FUEL_REGEN, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1763 }
1764
1765 void spawnfunc_item_jetpack(void)
1766 {
1767         if(g_grappling_hook)
1768                 return; // sorry, but these two can't coexist (same button); spawn fuel instead
1769         if(!self.ammo_fuel)
1770                 self.ammo_fuel = g_pickup_fuel_jetpack;
1771         if(start_items & IT_JETPACK)
1772         {
1773                 spawnfunc_item_fuel();
1774                 return;
1775         }
1776         StartItem ("models/items/g_jetpack.md3", "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Jet pack", IT_JETPACK, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1777 }
1778
1779
1780 #define OP_SET 0
1781 #define OP_MIN 1
1782 #define OP_MAX 2
1783 #define OP_PLUS 3
1784 #define OP_MINUS 4
1785
1786 float GiveWeapon(entity e, float wpn, float op, float val)
1787 {
1788         float v0, v1;
1789         v0 = WEPSET_CONTAINS_EW(e, wpn);
1790         switch(op)
1791         {
1792                 case OP_SET:
1793                         if(val > 0)
1794                                 WEPSET_OR_EW(e, wpn);
1795                         else
1796                                 WEPSET_ANDNOT_EW(e, wpn);
1797                         break;
1798                 case OP_MIN:
1799                 case OP_PLUS:
1800                         if(val > 0)
1801                                 WEPSET_OR_EW(e, wpn);
1802                         break;
1803                 case OP_MAX:
1804                         if(val <= 0)
1805                                 WEPSET_ANDNOT_EW(e, wpn);
1806                         break;
1807                 case OP_MINUS:
1808                         if(val > 0)
1809                                 WEPSET_ANDNOT_EW(e, wpn);
1810                         break;
1811         }
1812         v1 = WEPSET_CONTAINS_EW(e, wpn);
1813         return (v0 != v1);
1814 }
1815
1816 float GiveBit(entity e, .float fld, float bit, float op, float val)
1817 {
1818         float v0, v1;
1819         v0 = (e.fld & bit);
1820         switch(op)
1821         {
1822                 case OP_SET:
1823                         if(val > 0)
1824                                 e.fld |= bit;
1825                         else
1826                                 e.fld &~= bit;
1827                         break;
1828                 case OP_MIN:
1829                 case OP_PLUS:
1830                         if(val > 0)
1831                                 e.fld |= bit;
1832                         break;
1833                 case OP_MAX:
1834                         if(val <= 0)
1835                                 e.fld &~= bit;
1836                         break;
1837                 case OP_MINUS:
1838                         if(val > 0)
1839                                 e.fld &~= bit;
1840                         break;
1841         }
1842         v1 = (e.fld & bit);
1843         return (v0 != v1);
1844 }
1845
1846 float GiveValue(entity e, .float fld, float op, float val)
1847 {
1848         float v0, v1;
1849         v0 = e.fld;
1850         switch(op)
1851         {
1852                 case OP_SET:
1853                         e.fld = val;
1854                         break;
1855                 case OP_MIN:
1856                         e.fld = max(e.fld, val); // min 100 cells = at least 100 cells
1857                         break;
1858                 case OP_MAX:
1859                         e.fld = min(e.fld, val);
1860                         break;
1861                 case OP_PLUS:
1862                         e.fld += val;
1863                         break;
1864                 case OP_MINUS:
1865                         e.fld -= val;
1866                         break;
1867         }
1868         v1 = e.fld;
1869         return (v0 != v1);
1870 }
1871
1872 void GiveSound(entity e, float v0, float v1, float t, string snd_incr, string snd_decr)
1873 {
1874         if(v1 == v0)
1875                 return;
1876         if(v1 <= v0 - t)
1877         {
1878                 if(snd_decr != "")
1879                         sound (e, CH_TRIGGER, snd_decr, VOL_BASE, ATTN_NORM);
1880         }
1881         else if(v0 >= v0 + t)
1882         {
1883                 if(snd_incr != "")
1884                         sound (e, CH_TRIGGER, snd_incr, VOL_BASE, ATTN_NORM);
1885         }
1886 }
1887
1888 void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime)
1889 {
1890         if(v0 < v1)
1891                 e.rotfield = max(e.rotfield, time + rottime);
1892         else if(v0 > v1)
1893                 e.regenfield = max(e.regenfield, time + regentime);
1894 }
1895
1896 #define PREGIVE_WEAPONS(e) WEPSET_DECLARE_A(save_weapons); WEPSET_COPY_AE(save_weapons, e)
1897 #define PREGIVE(e,f) float save_##f; save_##f = (e).f
1898 #define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), WEPSET_CONTAINS_AW(save_weapons, b), WEPSET_CONTAINS_EW(e, b), 0, snd_incr, snd_decr)
1899 #define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
1900 #define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
1901 #define POSTGIVE_VALUE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e), save_##f, (e).f, rotfield, rottime, regenfield, regentime); GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
1902
1903 float GiveItems(entity e, float beginarg, float endarg)
1904 {
1905         float got, i, j, val, op;
1906         float _switchweapon;
1907         entity wi;
1908         string cmd;
1909
1910         val = 999;
1911         op = OP_SET;
1912
1913         got = 0;
1914
1915         _switchweapon = FALSE;
1916         if (e.autoswitch)
1917                 if (e.switchweapon == w_getbestweapon(e))
1918                         _switchweapon = TRUE;
1919
1920         e.strength_finished = max(0, e.strength_finished - time);
1921         e.invincible_finished = max(0, e.invincible_finished - time);
1922         e.superweapons_finished = max(0, e.superweapons_finished - time);
1923         
1924         PREGIVE(e, items);
1925         PREGIVE_WEAPONS(e);
1926         PREGIVE(e, strength_finished);
1927         PREGIVE(e, invincible_finished);
1928         PREGIVE(e, superweapons_finished);
1929         PREGIVE(e, ammo_nails);
1930         PREGIVE(e, ammo_cells);
1931         PREGIVE(e, ammo_shells);
1932         PREGIVE(e, ammo_rockets);
1933         PREGIVE(e, ammo_fuel);
1934         PREGIVE(e, armorvalue);
1935         PREGIVE(e, health);
1936
1937         for(i = beginarg; i < endarg; ++i)
1938         {
1939                 cmd = argv(i);
1940
1941                 if(cmd == "0" || stof(cmd))
1942                 {
1943                         val = stof(cmd);
1944                         continue;
1945                 }
1946                 switch(cmd)
1947                 {
1948                         case "no":
1949                                 op = OP_MAX;
1950                                 val = 0;
1951                                 continue;
1952                         case "max":
1953                                 op = OP_MAX;
1954                                 continue;
1955                         case "min":
1956                                 op = OP_MIN;
1957                                 continue;
1958                         case "plus":
1959                                 op = OP_PLUS;
1960                                 continue;
1961                         case "minus":
1962                                 op = OP_MINUS;
1963                                 continue;
1964                         case "ALL":
1965                                 got += GiveBit(e, items, IT_FUEL_REGEN, op, val);
1966                                 got += GiveValue(e, strength_finished, op, val);
1967                                 got += GiveValue(e, invincible_finished, op, val);
1968                                 got += GiveValue(e, superweapons_finished, op, val);
1969                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
1970                         case "all":
1971                                 got += GiveBit(e, items, IT_JETPACK, op, val);
1972                                 got += GiveValue(e, health, op, val);
1973                                 got += GiveValue(e, armorvalue, op, val);
1974                         case "allweapons":
1975                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1976                                 {
1977                                         wi = get_weaponinfo(j);
1978                                         if(wi.weapon)
1979                                                 if not(wi.spawnflags & WEP_FLAG_MUTATORBLOCKED)
1980                                                         got += GiveWeapon(e, j, op, val);
1981                                 }
1982                         case "allammo":
1983                                 got += GiveValue(e, ammo_cells, op, val);
1984                                 got += GiveValue(e, ammo_shells, op, val);
1985                                 got += GiveValue(e, ammo_nails, op, val);
1986                                 got += GiveValue(e, ammo_rockets, op, val);
1987                                 got += GiveValue(e, ammo_fuel, op, val);
1988                                 break;
1989                         case "unlimited_ammo":
1990                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
1991                                 break;
1992                         case "unlimited_weapon_ammo":
1993                                 got += GiveBit(e, items, IT_UNLIMITED_WEAPON_AMMO, op, val);
1994                                 break;
1995                         case "unlimited_superweapons":
1996                                 got += GiveBit(e, items, IT_UNLIMITED_SUPERWEAPONS, op, val);
1997                                 break;
1998                         case "jetpack":
1999                                 got += GiveBit(e, items, IT_JETPACK, op, val);
2000                                 break;
2001                         case "fuel_regen":
2002                                 got += GiveBit(e, items, IT_FUEL_REGEN, op, val);
2003                                 break;
2004                         case "strength":
2005                                 got += GiveValue(e, strength_finished, op, val);
2006                                 break;
2007                         case "invincible":
2008                                 got += GiveValue(e, invincible_finished, op, val);
2009                                 break;
2010                         case "superweapons":
2011                                 got += GiveValue(e, superweapons_finished, op, val);
2012                                 break;
2013                         case "cells":
2014                                 got += GiveValue(e, ammo_cells, op, val);
2015                                 break;
2016                         case "shells":
2017                                 got += GiveValue(e, ammo_shells, op, val);
2018                                 break;
2019                         case "nails":
2020                         case "bullets":
2021                                 got += GiveValue(e, ammo_nails, op, val);
2022                                 break;
2023                         case "rockets":
2024                                 got += GiveValue(e, ammo_rockets, op, val);
2025                                 break;
2026                         case "health":
2027                                 got += GiveValue(e, health, op, val);
2028                                 break;
2029                         case "armor":
2030                                 got += GiveValue(e, armorvalue, op, val);
2031                                 break;
2032                         case "fuel":
2033                                 got += GiveValue(e, ammo_fuel, op, val);
2034                                 break;
2035                         default:
2036                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2037                                 {
2038                                         wi = get_weaponinfo(j);
2039                                         if(cmd == wi.netname)
2040                                         {
2041                                                 got += GiveWeapon(e, j, op, val);
2042                                                 break;
2043                                         }
2044                                 }
2045                                 if(j > WEP_LAST)
2046                                         print("give: invalid item ", cmd, "\n");
2047                                 break;
2048                 }
2049                 val = 999;
2050                 op = OP_SET;
2051         }
2052
2053         POSTGIVE_BIT(e, items, IT_FUEL_REGEN, "misc/itempickup.wav", string_null);
2054         POSTGIVE_BIT(e, items, IT_UNLIMITED_SUPERWEAPONS, "misc/powerup.wav", "misc/poweroff.wav");
2055         POSTGIVE_BIT(e, items, IT_UNLIMITED_WEAPON_AMMO, "misc/powerup.wav", "misc/poweroff.wav");
2056         POSTGIVE_BIT(e, items, IT_JETPACK, "misc/itempickup.wav", string_null);
2057         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2058         {
2059                 wi = get_weaponinfo(j);
2060                 if(wi.weapon)
2061                 {
2062                         POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null);
2063                         if not(WEPSET_CONTAINS_AW(save_weapons, j))
2064                                 if(WEPSET_CONTAINS_EW(e, j))
2065                                         weapon_action(wi.weapon, WR_PRECACHE);
2066                 }
2067         }
2068         POSTGIVE_VALUE(e, strength_finished, 1, "misc/powerup.wav", "misc/poweroff.wav");
2069         POSTGIVE_VALUE(e, invincible_finished, 1, "misc/powerup_shield.wav", "misc/poweroff.wav");
2070         POSTGIVE_VALUE(e, ammo_nails, 0, "misc/itempickup.wav", string_null);
2071         POSTGIVE_VALUE(e, ammo_cells, 0, "misc/itempickup.wav", string_null);
2072         POSTGIVE_VALUE(e, ammo_shells, 0, "misc/itempickup.wav", string_null);
2073         POSTGIVE_VALUE(e, ammo_rockets, 0, "misc/itempickup.wav", string_null);
2074         POSTGIVE_VALUE_ROT(e, ammo_fuel, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, "misc/itempickup.wav", string_null);
2075         POSTGIVE_VALUE_ROT(e, armorvalue, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, "misc/armor25.wav", string_null);
2076         POSTGIVE_VALUE_ROT(e, health, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, "misc/megahealth.wav", string_null);
2077
2078         if(e.superweapons_finished <= 0)
2079                 if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
2080                         e.superweapons_finished = autocvar_g_balance_superweapons_time;
2081
2082         if (g_minstagib)
2083         {
2084                 e.health = bound(0, e.health, 100);
2085                 e.armorvalue = bound(0, e.armorvalue, 999);
2086         }
2087
2088         if(e.strength_finished <= 0)
2089                 e.strength_finished = 0;
2090         else
2091                 e.strength_finished += time;
2092         if(e.invincible_finished <= 0)
2093                 e.invincible_finished = 0;
2094         else
2095                 e.invincible_finished += time;
2096         if(e.superweapons_finished <= 0)
2097                 e.superweapons_finished = 0;
2098         else
2099                 e.superweapons_finished += time;
2100
2101         if not(WEPSET_CONTAINS_EW(e, e.switchweapon))
2102                 _switchweapon = TRUE;
2103         if(_switchweapon)
2104                 W_SwitchWeapon_Force(e, w_getbestweapon(e));
2105
2106         return got;
2107 }
2108 #endif