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