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