]> 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         float t;
596         entity head;
597         Item_Show(self, 1);
598         if(!g_minstagib && self.items == IT_STRENGTH)
599                 sound (self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);     // play respawn sound
600         else if(!g_minstagib && self.items == IT_INVINCIBLE)
601                 sound (self, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);       // play respawn sound
602         else
603                 sound (self, CH_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTN_NORM);  // play respawn sound
604         setorigin (self, self.origin);
605
606         if(self.flags & FL_POWERUP || self.classname == "item_armor_large" || self.items == IT_HEALTH || WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
607         {
608                 Item_ItemsTime_UpdateTime(self, 0);
609                 Item_ItemsTime_GetForAll();
610         }
611
612         //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
613         pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
614 }
615
616 void Item_RespawnCountdown (void)
617 {
618         if(self.count >= ITEM_RESPAWN_TICKS)
619         {
620                 if(self.waypointsprite_attached)
621                         WaypointSprite_Kill(self.waypointsprite_attached);
622                 Item_Respawn();
623         }
624         else
625         {
626                 self.nextthink = time + 1;
627                 self.count += 1;
628                 if(self.count == 1)
629                 {
630                         string name;
631                         vector rgb = '1 0 1';
632                         name = string_null;
633                         if(g_minstagib)
634                         {
635                                 switch(self.items)
636                                 {
637                                         case IT_STRENGTH:   name = "item-invis"; rgb = '0 0 1'; break;
638                                         case IT_NAILS:      name = "item-extralife"; rgb = '1 0 0'; break;
639                                         case IT_INVINCIBLE: name = "item-speed"; rgb = '1 0 1'; break;
640                                 }
641                         }
642                         else
643                         {
644                                 switch(self.items)
645                                 {
646                                         case IT_STRENGTH:   name = "item-strength"; rgb = '0 0 1'; break;
647                                         case IT_INVINCIBLE: name = "item-shield"; rgb = '1 0 1'; break;
648                                         case IT_HEALTH:
649                                                 //if (self.classname == "item_health_mega")
650                                                         {name = "item_health_mega"; rgb = '1 0 0';}
651                                                 break;
652                                         case IT_ARMOR:
653                                                 if (self.classname == "item_armor_large")
654                                                         {name = "item_armor_large"; rgb = '0 1 0';}
655                                                 break;
656                                 }
657                         }
658                         switch(self.items)
659                         {
660                                 case IT_FUEL_REGEN:     name = "item-fuelregen"; rgb = '1 0.5 0'; break;
661                                 case IT_JETPACK:        name = "item-jetpack"; rgb = '0.5 0.5 0.5'; break;
662                         }
663                         if(self.flags & FL_WEAPON)
664                         {
665                                 entity wi = get_weaponinfo(self.weapon);
666                                 if(wi)
667                                 {
668                                         name = wi.model2;
669                                         rgb = '1 0 0';
670                                 }
671                         }
672                         if(name)
673                         {
674                                 WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb);
675                                 if(self.waypointsprite_attached)
676                                 {
677                                         if (self.items == IT_HEALTH || self.items == IT_ARMOR)
678                                                 WaypointSprite_UpdateRule(self.waypointsprite_attached, 0, SPRITERULE_SPECTATOR);
679                                         WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, time + ITEM_RESPAWN_TICKS);
680                                 }
681                         }
682                         else
683                         {
684                                 print("Unknown powerup-marked item is wanting to respawn\n");
685                                 localcmd(sprintf("prvm_edict server %d\n", num_for_edict(self)));
686                         }
687                 }
688                 sound (self, CH_TRIGGER, "misc/itemrespawncountdown.wav", VOL_BASE, ATTN_NORM); // play respawn sound
689                 if(self.waypointsprite_attached)
690                 {
691                         WaypointSprite_Ping(self.waypointsprite_attached);
692                         //WaypointSprite_UpdateHealth(self.waypointsprite_attached, self.count);
693                 }
694         }
695 }
696
697 void Item_ScheduleRespawnIn(entity e, float t)
698 {
699         if((e.flags & FL_POWERUP) || WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS) || e.classname == "item_armor_large" || e.items == IT_HEALTH)
700         {
701                 entity head;
702                 e.think = Item_RespawnCountdown;
703                 e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
704                 e.scheduledrespawntime = e.nextthink + ITEM_RESPAWN_TICKS;
705                 e.count = 0;
706                 if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
707                 {
708                         for(t = e.scheduledrespawntime, head = world; (head = nextent(head)); )
709                         {
710                                 if(e == head)
711                                         continue;
712                                 if(clienttype(head) == CLIENTTYPE_NOTACLIENT)
713                                 if(WEPSET_CONTAINS_ANY_EA(head, WEPBIT_SUPERWEAPONS))
714                                 if(head.classname != "weapon_info")
715                                 {
716                                         if(head.scheduledrespawntime <= time)
717                                         {
718                                                 t = 0;
719                                                 break;
720                                         }
721                                         if(head.scheduledrespawntime < t)
722                                                 t = head.scheduledrespawntime;
723                                 }
724                         }
725                 }
726                 else
727                 {
728                         for(t = e.scheduledrespawntime, head = world; (head = find(head, classname, e.classname)); )
729                         {
730                                 // in minstagib .classname is "minstagib" for every item
731                                 if(e == head || (g_minstagib && e.items != head.items))
732                                         continue;
733                                 if(head.scheduledrespawntime <= time)
734                                 {
735                                         t = 0;
736                                         break;
737                                 }
738                                 if(head.scheduledrespawntime < t)
739                                         t = head.scheduledrespawntime;
740                         }
741                 }
742                 Item_ItemsTime_UpdateTime(e, t);
743                 Item_ItemsTime_GetForAll();
744         }
745         else
746         {
747                 e.think = Item_Respawn;
748                 e.nextthink = time + t;
749                 e.scheduledrespawntime = e.nextthink;
750         }
751 }
752
753 void Item_ScheduleRespawn(entity e)
754 {
755         if(e.respawntime > 0)
756         {
757                 Item_Show(e, 0);
758                 Item_ScheduleRespawnIn(e, ITEM_RESPAWNTIME(e));
759         }
760         else // if respawntime is -1, this item does not respawn
761                 Item_Show(e, -1);
762 }
763
764 void Item_ScheduleInitialRespawn(entity e)
765 {
766         Item_Show(e, 0);
767         Item_ScheduleRespawnIn(e, game_starttime - time + ITEM_RESPAWNTIME_INITIAL(e));
768 }
769
770 float ITEM_MODE_NONE = 0;
771 float ITEM_MODE_HEALTH = 1;
772 float ITEM_MODE_ARMOR = 2;
773 float ITEM_MODE_FUEL = 3;
774 float Item_GiveAmmoTo(entity item, entity player, .float ammofield, float ammomax, float mode)
775 {
776         if (!item.ammofield)
777                 return FALSE;
778
779         if (item.spawnshieldtime)
780         {
781                 if ((player.ammofield < ammomax) || item.pickup_anyway)
782                 {
783                         player.ammofield = bound(player.ammofield, ammomax, player.ammofield + item.ammofield);
784                         goto YEAH;
785                 }
786         }
787         else if(g_weapon_stay == 2)
788         {
789                 float mi = min(item.ammofield, ammomax);
790                 if (player.ammofield < mi)
791                 {
792                         player.ammofield = mi;
793                         goto YEAH;
794                 }
795         }
796
797         return FALSE;
798
799 :YEAH
800         switch(mode)
801         {
802                 case ITEM_MODE_FUEL:
803                         player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + autocvar_g_balance_pause_fuel_rot);
804                         break;
805                 case ITEM_MODE_HEALTH:
806                         player.pauserothealth_finished = max(player.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
807                         break;
808                 case ITEM_MODE_ARMOR:
809                         player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + autocvar_g_balance_pause_armor_rot);
810                         break;
811                 default:
812                         break;
813         }
814         return TRUE;
815 }
816
817 float Item_GiveTo(entity item, entity player)
818 {
819         float _switchweapon;
820         float pickedup;
821         float it;
822         float i;
823
824         // if nothing happens to player, just return without taking the item
825         pickedup = FALSE;
826         _switchweapon = FALSE;
827
828         if (g_minstagib)
829         {
830                 float prevcells = player.ammo_cells;
831
832                 pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
833                 pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, 999, ITEM_MODE_NONE);
834
835                 if(player.ammo_cells > prevcells)
836                 {
837                         _switchweapon = TRUE;
838
839                         // play some cool sounds ;)
840                         if (clienttype(player) == CLIENTTYPE_REAL)
841                         {
842                                 if(player.health <= 5)
843                                         AnnounceTo(player, "lastsecond");
844                                 else if(player.health < 50)
845                                         AnnounceTo(player, "narrowly");
846                         }
847                         // sound not available
848                         // else if(item.items == IT_CELLS)
849                         //      AnnounceTo(player, "ammo");
850
851                         if (WEPSET_CONTAINS_EW(item, WEP_MINSTANEX))
852                                 W_GiveWeapon (player, WEP_MINSTANEX);
853                         player.health = 100;
854                 }
855
856                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
857                 {
858                         pickedup = TRUE;
859                         player.items |= it;
860                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
861                 }
862
863                 // extralife powerup
864                 if (item.max_health)
865                 {
866                         pickedup = TRUE;
867                         // sound not available
868                         // AnnounceTo(player, "_lives");
869                         player.armorvalue = bound(player.armorvalue, 999, player.armorvalue + autocvar_g_minstagib_extralives);
870                         sprint(player, "^3You picked up some extra lives\n");
871                 }
872
873                 // invis powerup
874                 if (item.strength_finished)
875                 {
876                         pickedup = TRUE;
877                         // sound not available
878                         // AnnounceTo(player, "invisible");
879                         player.strength_finished = max(player.strength_finished, time) + autocvar_g_balance_powerup_strength_time;
880                 }
881
882                 // speed powerup
883                 if (item.invincible_finished)
884                 {
885                         pickedup = TRUE;
886                         // sound not available
887                         // AnnounceTo(player, "speed");
888                         player.invincible_finished = max(player.invincible_finished, time) + autocvar_g_balance_powerup_invincible_time;
889                 }
890         }
891         else
892         {
893                 // in case the player has autoswitch enabled do the following:
894                 // if the player is using their best weapon before items are given, they
895                 // probably want to switch to an even better weapon after items are given
896                 if (player.autoswitch)
897                 if (player.switchweapon == w_getbestweapon(player))
898                         _switchweapon = TRUE;
899
900                 if not(WEPSET_CONTAINS_EW(player, player.switchweapon))
901                         _switchweapon = TRUE;
902
903                 pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
904                 pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max, ITEM_MODE_NONE);
905                 pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max, ITEM_MODE_NONE);
906                 pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max, ITEM_MODE_NONE);
907                 pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max, ITEM_MODE_NONE);
908                 pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health, ITEM_MODE_HEALTH);
909                 pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue, ITEM_MODE_ARMOR);
910
911                 if (item.flags & FL_WEAPON)
912                 {
913                         WEPSET_DECLARE_A(it);
914                         WEPSET_COPY_AE(it, item);
915                         WEPSET_ANDNOT_AE(it, player);
916
917                         if (!WEPSET_EMPTY_A(it) || (item.spawnshieldtime && self.pickup_anyway))
918                         {
919                                 pickedup = TRUE;
920                                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
921                                         if(WEPSET_CONTAINS_AW(it, i))
922                                                 W_GiveWeapon(player, i);
923                         }
924                 }
925
926                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
927                 {
928                         pickedup = TRUE;
929                         player.items |= it;
930                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
931                 }
932
933                 if (item.strength_finished)
934                 {
935                         pickedup = TRUE;
936                         player.strength_finished = max(player.strength_finished, time) + item.strength_finished;
937                 }
938                 if (item.invincible_finished)
939                 {
940                         pickedup = TRUE;
941                         player.invincible_finished = max(player.invincible_finished, time) + item.invincible_finished;
942                 }
943                 if (item.superweapons_finished)
944                 {
945                         pickedup = TRUE;
946                         player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished;
947                 }
948         }
949
950 :skip
951         // always eat teamed entities
952         if(item.team)
953                 pickedup = TRUE;
954
955         if (!pickedup)
956                 return 0;
957
958         if (_switchweapon)
959                 if (player.switchweapon != w_getbestweapon(player))
960                         W_SwitchWeapon_Force(player, w_getbestweapon(player));
961
962         return 1;
963 }
964
965 void Item_Touch (void)
966 {
967         entity e, head;
968         
969         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
970         if(self.classname == "droppedweapon")
971         {
972                 if (ITEM_TOUCH_NEEDKILL())
973                 {
974                         remove(self);
975                         return;
976                 }
977         }
978
979         if (other.classname != "player")
980                 return;
981         if (other.deadflag)
982                 return;
983         if (self.solid != SOLID_TRIGGER)
984                 return;
985         if (self.owner == other)
986                 return;
987         if(MUTATOR_CALLHOOK(ItemTouch))
988                 return;
989
990         if (self.classname == "droppedweapon")
991         {
992                 self.strength_finished = max(0, self.strength_finished - time);
993                 self.invincible_finished = max(0, self.invincible_finished - time);
994                 self.superweapons_finished = max(0, self.superweapons_finished - time);
995         }
996
997         if(!Item_GiveTo(self, other))
998         {
999                 if (self.classname == "droppedweapon")
1000                 {
1001                         // undo what we did above
1002                         self.strength_finished += time;
1003                         self.invincible_finished += time;
1004                         self.superweapons_finished += time;
1005                 }
1006                 return;
1007         }
1008
1009         other.last_pickup = time;
1010
1011         pointparticles(particleeffectnum("item_pickup"), self.origin, '0 0 0', 1);
1012         sound (other, CH_TRIGGER, self.item_pickupsound, VOL_BASE, ATTN_NORM);
1013
1014         if (self.classname == "droppedweapon")
1015                 remove (self);
1016         else if not(self.spawnshieldtime)
1017                 return;
1018         else
1019         {
1020                 if(self.team)
1021                 {
1022                         RandomSelection_Init();
1023                         for(head = world; (head = findfloat(head, team, self.team)); )
1024                         {
1025                                 if(head.flags & FL_ITEM)
1026                                 {
1027                                         Item_Show(head, -1);
1028                                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
1029                                 }
1030                         }
1031                         e = RandomSelection_chosen_ent;
1032
1033                 }
1034                 else
1035                         e = self;
1036                 Item_ScheduleRespawn(e);
1037         }
1038 }
1039
1040 void Item_Reset()
1041 {
1042         Item_Show(self, !self.state);
1043         setorigin (self, self.origin);
1044
1045         if(self.classname != "droppedweapon")
1046         {
1047                 self.think = func_null;
1048                 self.nextthink = 0;
1049
1050                 if(self.waypointsprite_attached)
1051                         WaypointSprite_Kill(self.waypointsprite_attached);
1052
1053                 if((self.flags & FL_POWERUP) | WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS)) // do not spawn powerups initially!
1054                         Item_ScheduleInitialRespawn(self);
1055         }
1056 }
1057
1058 void Item_FindTeam()
1059 {
1060         entity head, e;
1061
1062         if(self.effects & EF_NODRAW)
1063         {
1064                 // marker for item team search
1065                 dprint("Initializing item team ", ftos(self.team), "\n");
1066                 RandomSelection_Init();
1067                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
1068                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
1069                 e = RandomSelection_chosen_ent;
1070                 e.state = 0;
1071                 Item_Show(e, 1);
1072
1073                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
1074                 {
1075                         if(head != e)
1076                         {
1077                                 // make it a non-spawned item
1078                                 Item_Show(head, -1);
1079                                 head.state = 1; // state 1 = initially hidden item
1080                         }
1081                         head.effects &~= EF_NODRAW;
1082                 }
1083
1084                 Item_Reset();
1085         }
1086 }
1087
1088 // Savage: used for item garbage-collection
1089 // TODO: perhaps nice special effect?
1090 void RemoveItem(void)
1091 {
1092         remove(self);
1093 }
1094
1095 // pickup evaluation functions
1096 // these functions decide how desirable an item is to the bots
1097
1098 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;}
1099
1100 float weapon_pickupevalfunc(entity player, entity item)
1101 {
1102         float c, j, position;
1103
1104         // See if I have it already
1105         if(!WEPSET_CONTAINS_ALL_EE(player, item))
1106         {
1107                 // If I can pick it up
1108                 if(!item.spawnshieldtime)
1109                         c = 0;
1110                 else if(player.ammo_cells || player.ammo_shells || player.ammo_nails || player.ammo_rockets)
1111                 {
1112                         // Skilled bots will grab more
1113                         c = bound(0, skill / 10, 1) * 0.5;
1114                 }
1115                 else
1116                         c = 0;
1117         }
1118         else
1119                 c = 1;
1120
1121         // If custom weapon priorities for bots is enabled rate most wanted weapons higher
1122         if( bot_custom_weapon && c )
1123         {
1124                 // Find the highest position on any range
1125                 position = -1;
1126                 for(j = 0; j < WEP_LAST ; ++j){
1127                         if(
1128                                         bot_weapons_far[j] == item.weapon ||
1129                                         bot_weapons_mid[j] == item.weapon ||
1130                                         bot_weapons_close[j] == item.weapon
1131                           )
1132                         {
1133                                 position = j;
1134                                 break;
1135                         }
1136                 }
1137
1138                 // Rate it
1139                 if (position >= 0 )
1140                 {
1141                         position = WEP_LAST - position;
1142                         // item.bot_pickupbasevalue is overwritten here
1143                         return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
1144                 }
1145         }
1146
1147         return item.bot_pickupbasevalue * c;
1148 }
1149
1150 float commodity_pickupevalfunc(entity player, entity item)
1151 {
1152         float c, i;
1153         float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_fuel = FALSE;
1154         entity wi;
1155         c = 0;
1156
1157         // Detect needed ammo
1158         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
1159         {
1160                 wi = get_weaponinfo(i);
1161
1162                 if not(WEPSET_CONTAINS_EW(player, i))
1163                         continue;
1164
1165                 if(wi.items & IT_SHELLS)
1166                         need_shells = TRUE;
1167                 else if(wi.items & IT_NAILS)
1168                         need_nails = TRUE;
1169                 else if(wi.items & IT_ROCKETS)
1170                         need_rockets = TRUE;
1171                 else if(wi.items & IT_CELLS)
1172                         need_cells = TRUE;
1173                 else if(wi.items & IT_FUEL)
1174                         need_cells = TRUE;
1175         }
1176
1177         // TODO: figure out if the player even has the weapon this ammo is for?
1178         // may not affect strategy much though...
1179         // find out how much more ammo/armor/health the player can hold
1180         if (need_shells)
1181         if (item.ammo_shells)
1182         if (player.ammo_shells < g_pickup_shells_max)
1183                 c = c + max(0, 1 - player.ammo_shells / g_pickup_shells_max);
1184         if (need_nails)
1185         if (item.ammo_nails)
1186         if (player.ammo_nails < g_pickup_nails_max)
1187                 c = c + max(0, 1 - player.ammo_nails / g_pickup_nails_max);
1188         if (need_rockets)
1189         if (item.ammo_rockets)
1190         if (player.ammo_rockets < g_pickup_rockets_max)
1191                 c = c + max(0, 1 - player.ammo_rockets / g_pickup_rockets_max);
1192         if (need_cells)
1193         if (item.ammo_cells)
1194         if (player.ammo_cells < g_pickup_cells_max)
1195                 c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
1196         if (need_fuel)
1197         if (item.ammo_fuel)
1198         if (player.ammo_fuel < g_pickup_fuel_max)
1199                 c = c + max(0, 1 - player.ammo_fuel / g_pickup_fuel_max);
1200         if (item.armorvalue)
1201         if (player.armorvalue < item.max_armorvalue)
1202                 c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
1203         if (item.health)
1204         if (player.health < item.max_health)
1205                 c = c + max(0, 1 - player.health / item.max_health);
1206
1207         return item.bot_pickupbasevalue * c;
1208 }
1209
1210 void Item_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1211 {
1212         if(ITEM_DAMAGE_NEEDKILL(deathtype))
1213                 RemoveItem();
1214 }
1215
1216 .float is_item;
1217 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)
1218 {
1219         startitem_failed = FALSE;
1220
1221         if(self.model == "")
1222                 self.model = itemmodel;
1223         
1224         if(self.model == "")
1225     {
1226         error(strcat("^1Tried to spawn ", itemname, " with no model!\n"));
1227         return;
1228     }
1229         
1230         if(self.item_pickupsound == "")
1231                 self.item_pickupsound = pickupsound;
1232         
1233         if(!self.respawntime) // both need to be set
1234         {
1235                 self.respawntime = defaultrespawntime;
1236                 self.respawntimejitter = defaultrespawntimejitter;
1237         }
1238
1239         self.items = itemid;
1240         self.weapon = weaponid;
1241
1242         if(weaponid)
1243                 WEPSET_COPY_EW(self, weaponid);
1244         
1245         self.flags = FL_ITEM | itemflags;
1246
1247         if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
1248         {
1249                 startitem_failed = TRUE;
1250                 remove(self);
1251                 return;
1252         }
1253
1254         // is it a dropped weapon?
1255         if (self.classname == "droppedweapon")
1256         {
1257                 self.reset = SUB_Remove;
1258                 // it's a dropped weapon
1259                 self.movetype = MOVETYPE_TOSS;
1260
1261                 // Savage: remove thrown items after a certain period of time ("garbage collection")
1262                 self.think = RemoveItem;
1263                 self.nextthink = time + 20;
1264
1265                 self.takedamage = DAMAGE_YES;
1266                 self.event_damage = Item_Damage;
1267
1268                 if(self.strength_finished || self.invincible_finished || self.superweapons_finished)
1269                 /*
1270                 if(self.items == 0)
1271                 if(WEPSET_CONTAINS_ALL_AE(WEPBIT_SUPERWEAPONS, self)) // only superweapons
1272                 if(self.ammo_nails == 0)
1273                 if(self.ammo_cells == 0)
1274                 if(self.ammo_rockets == 0)
1275                 if(self.ammo_shells == 0)
1276                 if(self.ammo_fuel == 0)
1277                 if(self.health == 0)
1278                 if(self.armorvalue == 0)
1279                 */
1280                 {
1281                         // if item is worthless after a timer, have it expire then
1282                         self.nextthink = max(self.strength_finished, self.invincible_finished, self.superweapons_finished);
1283                 }
1284
1285                 // don't drop if in a NODROP zone (such as lava)
1286                 traceline(self.origin, self.origin, MOVE_NORMAL, self);
1287                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
1288                 {
1289                         startitem_failed = TRUE;
1290                         remove(self);
1291                         return;
1292                 }
1293         }
1294         else
1295         {
1296                 if(!have_pickup_item())
1297                 {
1298                         startitem_failed = TRUE;
1299                         remove (self);
1300                         return;
1301                 }
1302                 
1303                 if(self.angles != '0 0 0')
1304             self.SendFlags |= ISF_ANGLES;
1305
1306                 self.reset = Item_Reset;
1307                 // it's a level item
1308                 if(self.spawnflags & 1)
1309                         self.noalign = 1;
1310                 if (self.noalign)
1311                         self.movetype = MOVETYPE_NONE;
1312                 else
1313                         self.movetype = MOVETYPE_TOSS;
1314                 // do item filtering according to game mode and other things
1315                 if (!self.noalign)
1316                 {
1317                         // first nudge it off the floor a little bit to avoid math errors
1318                         setorigin(self, self.origin + '0 0 1');
1319                         // set item size before we spawn a spawnfunc_waypoint
1320                         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
1321                                 setsize (self, '-16 -16 0', '16 16 48');
1322                         else
1323                                 setsize (self, '-16 -16 0', '16 16 32');
1324                         // note droptofloor returns FALSE if stuck/or would fall too far
1325                         droptofloor();
1326                         waypoint_spawnforitem(self);
1327                 }
1328
1329                 /*
1330                  * can't do it that way, as it would break maps
1331                  * TODO make a target_give like entity another way, that perhaps has
1332                  * the weapon name in a key
1333                 if(self.targetname)
1334                 {
1335                         // target_give not yet supported; maybe later
1336                         print("removed targeted ", self.classname, "\n");
1337                         startitem_failed = TRUE;
1338                         remove (self);
1339                         return;
1340                 }
1341                 */
1342
1343                 if(autocvar_spawn_debug >= 2)
1344                 {
1345                         entity otheritem;
1346                         for(otheritem = findradius(self.origin, 3); otheritem; otheritem = otheritem.chain)
1347                         {
1348                             // why not flags & fl_item?
1349                                 if(otheritem.is_item)
1350                                 {
1351                                         dprint("XXX Found duplicated item: ", itemname, vtos(self.origin));
1352                                         dprint(" vs ", otheritem.netname, vtos(otheritem.origin), "\n");
1353                                         error("Mapper sucks.");
1354                                 }
1355                         }
1356                         self.is_item = TRUE;
1357                 }
1358
1359                 WEPSET_OR_AW(weaponsInMap, weaponid);
1360
1361                 precache_model (self.model);
1362                 precache_sound (self.item_pickupsound);
1363
1364                 precache_sound ("misc/itemrespawncountdown.wav");
1365                 if(!g_minstagib && itemid == IT_STRENGTH)
1366                         precache_sound ("misc/strength_respawn.wav");
1367                 else if(!g_minstagib && itemid == IT_INVINCIBLE)
1368                         precache_sound ("misc/shield_respawn.wav");
1369                 else
1370                         precache_sound ("misc/itemrespawn.wav");
1371
1372                 if((itemflags & (FL_POWERUP | FL_WEAPON)) || (itemid & (IT_HEALTH | IT_ARMOR | IT_KEY1 | IT_KEY2)))
1373                         self.target = "###item###"; // for finding the nearest item using find()
1374
1375                 Item_ItemsTime_UpdateTime(self, 0);
1376         }
1377
1378         self.bot_pickup = TRUE;
1379         self.bot_pickupevalfunc = pickupevalfunc;
1380         self.bot_pickupbasevalue = pickupbasevalue;
1381         self.mdl = self.model;
1382         self.netname = itemname;
1383         self.touch = Item_Touch;
1384         setmodel(self, "null"); // precision set below
1385         //self.effects |= EF_LOWPRECISION; 
1386         
1387         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
1388     {
1389         self.pos1 = '-16 -16 0';
1390         self.pos2 = '16 16 48';
1391     }
1392         else
1393     {
1394         self.pos1 = '-16 -16 0';
1395         self.pos2 = '16 16 32';
1396     }
1397     setsize (self, self.pos1, self.pos2);
1398     
1399     if(itemflags & FL_POWERUP) 
1400         self.ItemStatus |= ITS_ANIMATE1;
1401         
1402         if(self.armorvalue || self.health)
1403         self.ItemStatus |= ITS_ANIMATE2;
1404         
1405         if(itemflags & FL_WEAPON)
1406         {
1407                 if (self.classname != "droppedweapon") // if dropped, colormap is already set up nicely
1408             self.colormap = 1024; // color shirt=0 pants=0 grey
1409         else
1410             self.gravity = 1;
1411             
1412                 self.ItemStatus |= ITS_ANIMATE1;
1413                 self.ItemStatus |= ISF_COLORMAP;
1414         }
1415
1416         self.state = 0;
1417         if(self.team) // broken, no idea why.
1418         {
1419                 if(!self.cnt)
1420                         self.cnt = 1; // item probability weight
1421                         
1422                 self.effects |= EF_NODRAW; // marker for item team search
1423                 InitializeEntity(self, Item_FindTeam, INITPRIO_FINDTARGET);
1424         }
1425         else
1426                 Item_Reset();
1427         
1428     Net_LinkEntity(self, FALSE, 0, ItemSend);
1429
1430         // call this hook after everything else has been done
1431         if(MUTATOR_CALLHOOK(Item_Spawn))
1432         {
1433                 startitem_failed = TRUE;
1434                 remove(self);
1435                 return;
1436         }
1437 }
1438
1439 /* replace items in minstagib
1440  * IT_STRENGTH   = invisibility
1441  * IT_NAILS      = extra lives
1442  * IT_INVINCIBLE = speed
1443  */
1444 void minstagib_items (float itemid) // will be deleted soon.
1445 {
1446         float rnd;
1447         self.classname = "minstagib"; // ...?
1448
1449         // replace rocket launchers and nex guns with ammo cells
1450         if (itemid == IT_CELLS)
1451         {
1452                 self.ammo_cells = autocvar_g_minstagib_ammo_drop;
1453                 StartItem ("models/items/a_cells.md3",
1454                         "misc/itempickup.wav", 45, 0,
1455                         "MinstaNex Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100);
1456                 return;
1457         }
1458
1459         // randomize
1460         rnd = random() * 3;
1461         if (rnd <= 1)
1462                 itemid = IT_STRENGTH;
1463         else if (rnd <= 2)
1464                 itemid = IT_NAILS;
1465         else
1466                 itemid = IT_INVINCIBLE;
1467
1468         // replace with invis
1469         if (itemid == IT_STRENGTH)
1470         {
1471                 if(!self.strength_finished)
1472                         self.strength_finished = autocvar_g_balance_powerup_strength_time;
1473                 StartItem ("models/items/g_strength.md3",
1474                         "misc/powerup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
1475                         "Invisibility", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_MID);
1476         }
1477         // replace with extra lives
1478         if (itemid == IT_NAILS)
1479         {
1480                 self.max_health = 1;
1481                 StartItem ("models/items/g_h100.md3",
1482                         "misc/megahealth.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
1483                         "Extralife", IT_NAILS, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1484         }
1485         // replace with speed
1486         if (itemid == IT_INVINCIBLE)
1487         {
1488                 if(!self.invincible_finished)
1489                         self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1490                 StartItem ("models/items/g_invincible.md3",
1491                         "misc/powerup_shield.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
1492                         "Speed", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_MID);
1493         }
1494 }
1495
1496 float minst_no_auto_cells;
1497 void minst_remove_item (void) {
1498         if(minst_no_auto_cells)
1499                 remove(self);
1500 }
1501
1502 float weaponswapping;
1503 float internalteam;
1504
1505 void weapon_defaultspawnfunc(float wpn)
1506 {
1507         entity e;
1508         float t;
1509         var .float ammofield;
1510         string s;
1511         entity oldself;
1512         float i, j;
1513         float f;
1514
1515         if(self.classname != "droppedweapon" && self.classname != "replacedweapon")
1516         {
1517                 e = get_weaponinfo(wpn);
1518
1519                 if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED)
1520                 {
1521                         print("Attempted to spawn a mutator-blocked weapon; these guns will in the future require a mutator\n");
1522                         /*
1523                         objerror("Attempted to spawn a mutator-blocked weapon rejected");
1524                         startitem_failed = TRUE;
1525                         return;
1526                         */
1527                 }
1528
1529                 s = W_Apply_Weaponreplace(e.netname);
1530                 ret_string = s;
1531                 other = e;
1532                 MUTATOR_CALLHOOK(SetWeaponreplace);
1533                 s = ret_string;
1534                 if(s == "")
1535                 {
1536                         remove(self);
1537                         startitem_failed = TRUE;
1538                         return;
1539                 }
1540                 t = tokenize_console(s);
1541                 if(t >= 2)
1542                 {
1543                         self.team = --internalteam;
1544                         oldself = self;
1545                         for(i = 1; i < t; ++i)
1546                         {
1547                                 s = argv(i);
1548                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1549                                 {
1550                                         e = get_weaponinfo(j);
1551                                         if(e.netname == s)
1552                                         {
1553                                                 self = spawn();
1554                                                 copyentity(oldself, self);
1555                                                 self.classname = "replacedweapon";
1556                                                 weapon_defaultspawnfunc(j);
1557                                                 break;
1558                                         }
1559                                 }
1560                                 if(j > WEP_LAST)
1561                                 {
1562                                         print("The weapon replace list for ", oldself.classname, " contains an unknown weapon ", s, ". Skipped.\n");
1563                                 }
1564                         }
1565                         self = oldself;
1566                 }
1567                 if(t >= 1) // always the case!
1568                 {
1569                         s = argv(0);
1570                         wpn = 0;
1571                         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1572                         {
1573                                 e = get_weaponinfo(j);
1574                                 if(e.netname == s)
1575                                 {
1576                                         wpn = j;
1577                                         break;
1578                                 }
1579                         }
1580                         if(j > WEP_LAST)
1581                         {
1582                                 print("The weapon replace list for ", self.classname, " contains an unknown weapon ", s, ". Skipped.\n");
1583                         }
1584                 }
1585                 if(wpn == 0)
1586                 {
1587                         remove(self);
1588                         startitem_failed = TRUE;
1589                         return;
1590                 }
1591         }
1592
1593         e = get_weaponinfo(wpn);
1594
1595         if(!self.respawntime)
1596         {
1597                 if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
1598                 {
1599                         self.respawntime = g_pickup_respawntime_superweapon;
1600                         self.respawntimejitter = g_pickup_respawntimejitter_superweapon;
1601                 }
1602                 else
1603                 {
1604                         self.respawntime = g_pickup_respawntime_weapon;
1605                         self.respawntimejitter = g_pickup_respawntimejitter_weapon;
1606                 }
1607         }
1608
1609         if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
1610                 if(!self.superweapons_finished)
1611                         self.superweapons_finished = autocvar_g_balance_superweapons_time;
1612
1613         if(e.items)
1614         {
1615                 for(i = 0, j = 1; i < 24; ++i, j *= 2)
1616                 {
1617                         if(e.items & j)
1618                         {
1619                                 ammofield = Item_CounterField(j);
1620                                 if(!self.ammofield)
1621                                         self.ammofield = cvar(strcat("g_pickup_", Item_CounterFieldName(j), "_weapon"));
1622                         }
1623                 }
1624         }
1625
1626         // pickup anyway
1627         if(g_pickup_weapons_anyway)
1628                 self.pickup_anyway = TRUE;
1629
1630         f = FL_WEAPON;
1631
1632         // no weapon-stay on superweapons
1633         if(WEPSET_CONTAINS_ANY_EA(e, WEPBIT_SUPERWEAPONS))
1634                 f |= FL_NO_WEAPON_STAY;
1635
1636         // weapon stay isn't supported for teamed weapons
1637         if(self.team)
1638                 f |= FL_NO_WEAPON_STAY;
1639
1640         // stupid minstagib hack, don't ask
1641         if(g_minstagib)
1642                 if(self.ammo_cells)
1643                         self.ammo_cells = autocvar_g_minstagib_ammo_drop;
1644
1645         StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapon, f, weapon_pickupevalfunc, e.bot_pickupbasevalue);
1646         if (self.modelindex) // don't precache if self was removed
1647                 weapon_action(e.weapon, WR_PRECACHE);
1648 }
1649
1650 void spawnfunc_weapon_shotgun (void);
1651 void spawnfunc_weapon_uzi (void) {
1652         if(autocvar_sv_q3acompat_machineshotgunswap)
1653         if(self.classname != "droppedweapon")
1654         {
1655                 weapon_defaultspawnfunc(WEP_SHOTGUN);
1656                 return;
1657         }
1658         weapon_defaultspawnfunc(WEP_UZI);
1659 }
1660
1661 void spawnfunc_weapon_shotgun (void) {
1662         if(autocvar_sv_q3acompat_machineshotgunswap)
1663         if(self.classname != "droppedweapon")
1664         {
1665                 weapon_defaultspawnfunc(WEP_UZI);
1666                 return;
1667         }
1668         weapon_defaultspawnfunc(WEP_SHOTGUN);
1669 }
1670
1671 void spawnfunc_weapon_nex (void)
1672 {
1673         if (g_minstagib)
1674         {
1675                 minstagib_items(IT_CELLS);
1676                 self.think = minst_remove_item;
1677                 self.nextthink = time;
1678                 return;
1679         }
1680         weapon_defaultspawnfunc(WEP_NEX);
1681 }
1682
1683 void spawnfunc_weapon_minstanex (void)
1684 {
1685         if (g_minstagib)
1686         {
1687                 minstagib_items(IT_CELLS);
1688                 self.think = minst_remove_item;
1689                 self.nextthink = time;
1690                 return;
1691         }
1692         weapon_defaultspawnfunc(WEP_MINSTANEX);
1693 }
1694
1695 void spawnfunc_weapon_rocketlauncher (void)
1696 {
1697         if (g_minstagib)
1698         {
1699                 minstagib_items(IT_CELLS); // replace rocketlauncher with cells
1700                 self.think = minst_remove_item;
1701                 self.nextthink = time;
1702                 return;
1703         }
1704         weapon_defaultspawnfunc(WEP_ROCKET_LAUNCHER);
1705 }
1706
1707 void spawnfunc_item_rockets (void) {
1708         if(!self.ammo_rockets)
1709                 self.ammo_rockets = g_pickup_rockets;
1710         if(!self.pickup_anyway)
1711                 self.pickup_anyway = g_pickup_ammo_anyway;
1712         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);
1713 }
1714
1715 void spawnfunc_item_shells (void);
1716 void spawnfunc_item_bullets (void) {
1717         if(!weaponswapping)
1718         if(autocvar_sv_q3acompat_machineshotgunswap)
1719         if(self.classname != "droppedweapon")
1720         {
1721                 weaponswapping = TRUE;
1722                 spawnfunc_item_shells();
1723                 weaponswapping = FALSE;
1724                 return;
1725         }
1726
1727         if(!self.ammo_nails)
1728                 self.ammo_nails = g_pickup_nails;
1729         if(!self.pickup_anyway)
1730                 self.pickup_anyway = g_pickup_ammo_anyway;
1731         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);
1732 }
1733
1734 void spawnfunc_item_cells (void) {
1735         if(!self.ammo_cells)
1736                 self.ammo_cells = g_pickup_cells;
1737         if(!self.pickup_anyway)
1738                 self.pickup_anyway = g_pickup_ammo_anyway;
1739         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);
1740 }
1741
1742 void spawnfunc_item_shells (void) {
1743         if(!weaponswapping)
1744         if(autocvar_sv_q3acompat_machineshotgunswap)
1745         if(self.classname != "droppedweapon")
1746         {
1747                 weaponswapping = TRUE;
1748                 spawnfunc_item_bullets();
1749                 weaponswapping = FALSE;
1750                 return;
1751         }
1752
1753         if(!self.ammo_shells)
1754                 self.ammo_shells = g_pickup_shells;
1755         if(!self.pickup_anyway)
1756                 self.pickup_anyway = g_pickup_ammo_anyway;
1757         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);
1758 }
1759
1760 void spawnfunc_item_armor_small (void) {
1761         if(!self.armorvalue)
1762                 self.armorvalue = g_pickup_armorsmall;
1763         if(!self.max_armorvalue)
1764                 self.max_armorvalue = g_pickup_armorsmall_max;
1765         if(!self.pickup_anyway)
1766                 self.pickup_anyway = g_pickup_armorsmall_anyway;
1767         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);
1768 }
1769
1770 void spawnfunc_item_armor_medium (void) {
1771         if(!self.armorvalue)
1772                 self.armorvalue = g_pickup_armormedium;
1773         if(!self.max_armorvalue)
1774                 self.max_armorvalue = g_pickup_armormedium_max;
1775         if(!self.pickup_anyway)
1776                 self.pickup_anyway = g_pickup_armormedium_anyway;
1777         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);
1778 }
1779
1780 void spawnfunc_item_armor_big (void) {
1781         if(!self.armorvalue)
1782                 self.armorvalue = g_pickup_armorbig;
1783         if(!self.max_armorvalue)
1784                 self.max_armorvalue = g_pickup_armorbig_max;
1785         if(!self.pickup_anyway)
1786                 self.pickup_anyway = g_pickup_armorbig_anyway;
1787         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);
1788 }
1789
1790 void spawnfunc_item_armor_large (void) {
1791         if(!self.armorvalue)
1792                 self.armorvalue = g_pickup_armorlarge;
1793         if(!self.max_armorvalue)
1794                 self.max_armorvalue = g_pickup_armorlarge_max;
1795         if(!self.pickup_anyway)
1796                 self.pickup_anyway = g_pickup_armorlarge_anyway;
1797         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);
1798 }
1799
1800 void spawnfunc_item_health_small (void) {
1801         if(!self.max_health)
1802                 self.max_health = g_pickup_healthsmall_max;
1803         if(!self.health)
1804                 self.health = g_pickup_healthsmall;
1805         if(!self.pickup_anyway)
1806                 self.pickup_anyway = g_pickup_healthsmall_anyway;
1807         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);
1808 }
1809
1810 void spawnfunc_item_health_medium (void) {
1811         if(!self.max_health)
1812                 self.max_health = g_pickup_healthmedium_max;
1813         if(!self.health)
1814                 self.health = g_pickup_healthmedium;
1815         if(!self.pickup_anyway)
1816                 self.pickup_anyway = g_pickup_healthmedium_anyway;
1817         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);
1818 }
1819
1820 void spawnfunc_item_health_large (void) {
1821         if(!self.max_health)
1822                 self.max_health = g_pickup_healthlarge_max;
1823         if(!self.health)
1824                 self.health = g_pickup_healthlarge;
1825         if(!self.pickup_anyway)
1826                 self.pickup_anyway = g_pickup_healthlarge_anyway;
1827         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);
1828 }
1829
1830 void spawnfunc_item_health_mega (void) {
1831         if(g_minstagib) {
1832                 minstagib_items(IT_NAILS);
1833         } else {
1834                 if(!self.max_health)
1835                         self.max_health = g_pickup_healthmega_max;
1836                 if(!self.health)
1837                         self.health = g_pickup_healthmega;
1838                 if(!self.pickup_anyway)
1839                         self.pickup_anyway = g_pickup_healthmega_anyway;
1840                 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);
1841         }
1842 }
1843
1844 // support old misnamed entities
1845 void spawnfunc_item_armor1() { spawnfunc_item_armor_small(); }  // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
1846 void spawnfunc_item_armor25() { spawnfunc_item_armor_large(); }
1847 void spawnfunc_item_health1() { spawnfunc_item_health_small(); }
1848 void spawnfunc_item_health25() { spawnfunc_item_health_medium(); }
1849 void spawnfunc_item_health100() { spawnfunc_item_health_mega(); }
1850
1851 void spawnfunc_item_strength (void) {
1852         if(g_minstagib) {
1853                 minstagib_items(IT_STRENGTH);
1854         } else {
1855                 precache_sound("weapons/strength_fire.wav");
1856                 if(!self.strength_finished)
1857                         self.strength_finished = autocvar_g_balance_powerup_strength_time;
1858                 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);
1859         }
1860 }
1861
1862 void spawnfunc_item_invincible (void) {
1863         if(g_minstagib) {
1864                 minstagib_items(IT_INVINCIBLE);
1865         } else {
1866                 if(!self.invincible_finished)
1867                         self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1868                 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);
1869         }
1870 }
1871
1872 void spawnfunc_item_minst_cells (void) {
1873         if (g_minstagib)
1874         {
1875                 minst_no_auto_cells = TRUE;
1876                 minstagib_items(IT_CELLS);
1877         }
1878         else
1879                 remove(self);
1880 }
1881
1882 // compatibility:
1883 void spawnfunc_item_quad (void) {self.classname = "item_strength";spawnfunc_item_strength();}
1884
1885 float GiveItems(entity e, float beginarg, float endarg);
1886 void target_items_use (void)
1887 {
1888         if(activator.classname == "droppedweapon")
1889         {
1890                 EXACTTRIGGER_TOUCH;
1891                 remove(activator);
1892                 return;
1893         }
1894
1895         if(activator.classname != "player")
1896                 return;
1897         if(activator.deadflag != DEAD_NO)
1898                 return;
1899         EXACTTRIGGER_TOUCH;
1900
1901         entity e;
1902         for(e = world; (e = find(e, classname, "droppedweapon")); )
1903                 if(e.enemy == activator)
1904                         remove(e);
1905
1906         if(GiveItems(activator, 0, tokenize_console(self.netname)))
1907                 centerprint(activator, self.message);
1908 }
1909
1910 void spawnfunc_target_items (void)
1911 {
1912         float n, i, j;
1913         entity e;
1914
1915         self.use = target_items_use;
1916         if(!self.strength_finished)
1917                 self.strength_finished = autocvar_g_balance_powerup_strength_time;
1918         if(!self.invincible_finished)
1919                 self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1920         if(!self.superweapons_finished)
1921                 self.superweapons_finished = autocvar_g_balance_superweapons_time;
1922
1923         precache_sound("misc/itempickup.wav");
1924         precache_sound("misc/megahealth.wav");
1925         precache_sound("misc/armor25.wav");
1926         precache_sound("misc/powerup.wav");
1927         precache_sound("misc/poweroff.wav");
1928         precache_sound("weapons/weaponpickup.wav");
1929
1930         n = tokenize_console(self.netname);
1931         if(argv(0) == "give")
1932         {
1933                 self.netname = substring(self.netname, argv_start_index(1), argv_end_index(-1) - argv_start_index(1));
1934         }
1935         else
1936         {
1937                 for(i = 0; i < n; ++i)
1938                 {
1939                         if     (argv(i) == "unlimited_ammo")         self.items |= IT_UNLIMITED_AMMO;
1940                         else if(argv(i) == "unlimited_weapon_ammo")  self.items |= IT_UNLIMITED_WEAPON_AMMO;
1941                         else if(argv(i) == "unlimited_superweapons") self.items |= IT_UNLIMITED_SUPERWEAPONS;
1942                         else if(argv(i) == "strength")               self.items |= IT_STRENGTH;
1943                         else if(argv(i) == "invincible")             self.items |= IT_INVINCIBLE;
1944                         else if(argv(i) == "superweapons")           self.items |= IT_SUPERWEAPON;
1945                         else if(argv(i) == "jetpack")                self.items |= IT_JETPACK;
1946                         else if(argv(i) == "fuel_regen")             self.items |= IT_FUEL_REGEN;
1947                         else
1948                         {
1949                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1950                                 {
1951                                         e = get_weaponinfo(j);
1952                                         if(argv(i) == e.netname)
1953                                         {
1954                                                 WEPSET_OR_EW(self, j);
1955                                                 if(self.spawnflags == 0 || self.spawnflags == 2)
1956                                                         weapon_action(e.weapon, WR_PRECACHE);
1957                                                 break;
1958                                         }
1959                                 }
1960                                 if(j > WEP_LAST)
1961                                         print("target_items: invalid item ", argv(i), "\n");
1962                         }
1963                 }
1964
1965                 string itemprefix, valueprefix;
1966                 if(self.spawnflags == 0)
1967                 {
1968                         itemprefix = "";
1969                         valueprefix = "";
1970                 }
1971                 else if(self.spawnflags == 1)
1972                 {
1973                         itemprefix = "max ";
1974                         valueprefix = "max ";
1975                 }
1976                 else if(self.spawnflags == 2)
1977                 {
1978                         itemprefix = "min ";
1979                         valueprefix = "min ";
1980                 }
1981                 else if(self.spawnflags == 4)
1982                 {
1983                         itemprefix = "minus ";
1984                         valueprefix = "max ";
1985                 }
1986                 else
1987                 {
1988                         error("invalid spawnflags");
1989 #ifdef GMQCC
1990                         itemprefix = string_null;
1991                         valueprefix = string_null;
1992 #endif
1993                 }
1994
1995                 self.netname = "";
1996                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_WEAPON_AMMO), "unlimited_weapon_ammo");
1997                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_SUPERWEAPONS), "unlimited_superweapons");
1998                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.strength_finished * !!(self.items & IT_STRENGTH), "strength");
1999                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.invincible_finished * !!(self.items & IT_INVINCIBLE), "invincible");
2000                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.superweapons_finished * !!(self.items & IT_SUPERWEAPON), "superweapons");
2001                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_JETPACK), "jetpack");
2002                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_FUEL_REGEN), "fuel_regen");
2003                 if(self.ammo_shells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_shells), "shells");
2004                 if(self.ammo_nails != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_nails), "nails");
2005                 if(self.ammo_rockets != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_rockets), "rockets");
2006                 if(self.ammo_cells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_cells), "cells");
2007                 if(self.ammo_fuel != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_fuel), "fuel");
2008                 if(self.health != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "health");
2009                 if(self.armorvalue != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "armor");
2010                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2011                 {
2012                         e = get_weaponinfo(j);
2013                         if(e.weapon)
2014                                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, WEPSET_CONTAINS_EW(self, j), e.netname);
2015                 }
2016         }
2017         self.netname = strzone(self.netname);
2018         //print(self.netname, "\n");
2019
2020         n = tokenize_console(self.netname);
2021         for(i = 0; i < n; ++i)
2022         {
2023                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2024                 {
2025                         e = get_weaponinfo(j);
2026                         if(argv(i) == e.netname)
2027                         {
2028                                 weapon_action(e.weapon, WR_PRECACHE);
2029                                 break;
2030                         }
2031                 }
2032         }
2033 }
2034
2035 void spawnfunc_item_fuel(void)
2036 {
2037         if(!self.ammo_fuel)
2038                 self.ammo_fuel = g_pickup_fuel;
2039         if(!self.pickup_anyway)
2040                 self.pickup_anyway = g_pickup_ammo_anyway;
2041         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);
2042 }
2043
2044 void spawnfunc_item_fuel_regen(void)
2045 {
2046         if(start_items & IT_FUEL_REGEN)
2047         {
2048                 spawnfunc_item_fuel();
2049                 return;
2050         }
2051         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);
2052 }
2053
2054 void spawnfunc_item_jetpack(void)
2055 {
2056         if(g_grappling_hook)
2057                 return; // sorry, but these two can't coexist (same button); spawn fuel instead
2058         if(!self.ammo_fuel)
2059                 self.ammo_fuel = g_pickup_fuel_jetpack;
2060         if(start_items & IT_JETPACK)
2061         {
2062                 spawnfunc_item_fuel();
2063                 return;
2064         }
2065         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);
2066 }
2067
2068
2069 #define OP_SET 0
2070 #define OP_MIN 1
2071 #define OP_MAX 2
2072 #define OP_PLUS 3
2073 #define OP_MINUS 4
2074
2075 float GiveWeapon(entity e, float wpn, float op, float val)
2076 {
2077         float v0, v1;
2078         v0 = WEPSET_CONTAINS_EW(e, wpn);
2079         switch(op)
2080         {
2081                 case OP_SET:
2082                         if(val > 0)
2083                                 WEPSET_OR_EW(e, wpn);
2084                         else
2085                                 WEPSET_ANDNOT_EW(e, wpn);
2086                         break;
2087                 case OP_MIN:
2088                 case OP_PLUS:
2089                         if(val > 0)
2090                                 WEPSET_OR_EW(e, wpn);
2091                         break;
2092                 case OP_MAX:
2093                         if(val <= 0)
2094                                 WEPSET_ANDNOT_EW(e, wpn);
2095                         break;
2096                 case OP_MINUS:
2097                         if(val > 0)
2098                                 WEPSET_ANDNOT_EW(e, wpn);
2099                         break;
2100         }
2101         v1 = WEPSET_CONTAINS_EW(e, wpn);
2102         return (v0 != v1);
2103 }
2104
2105 float GiveBit(entity e, .float fld, float bit, float op, float val)
2106 {
2107         float v0, v1;
2108         v0 = (e.fld & bit);
2109         switch(op)
2110         {
2111                 case OP_SET:
2112                         if(val > 0)
2113                                 e.fld |= bit;
2114                         else
2115                                 e.fld &~= bit;
2116                         break;
2117                 case OP_MIN:
2118                 case OP_PLUS:
2119                         if(val > 0)
2120                                 e.fld |= bit;
2121                         break;
2122                 case OP_MAX:
2123                         if(val <= 0)
2124                                 e.fld &~= bit;
2125                         break;
2126                 case OP_MINUS:
2127                         if(val > 0)
2128                                 e.fld &~= bit;
2129                         break;
2130         }
2131         v1 = (e.fld & bit);
2132         return (v0 != v1);
2133 }
2134
2135 float GiveValue(entity e, .float fld, float op, float val)
2136 {
2137         float v0, v1;
2138         v0 = e.fld;
2139         switch(op)
2140         {
2141                 case OP_SET:
2142                         e.fld = val;
2143                         break;
2144                 case OP_MIN:
2145                         e.fld = max(e.fld, val); // min 100 cells = at least 100 cells
2146                         break;
2147                 case OP_MAX:
2148                         e.fld = min(e.fld, val);
2149                         break;
2150                 case OP_PLUS:
2151                         e.fld += val;
2152                         break;
2153                 case OP_MINUS:
2154                         e.fld -= val;
2155                         break;
2156         }
2157         v1 = e.fld;
2158         return (v0 != v1);
2159 }
2160
2161 void GiveSound(entity e, float v0, float v1, float t, string snd_incr, string snd_decr)
2162 {
2163         if(v1 == v0)
2164                 return;
2165         if(v1 <= v0 - t)
2166         {
2167                 if(snd_decr != "")
2168                         sound (e, CH_TRIGGER, snd_decr, VOL_BASE, ATTN_NORM);
2169         }
2170         else if(v0 >= v0 + t)
2171         {
2172                 if(snd_incr != "")
2173                         sound (e, CH_TRIGGER, snd_incr, VOL_BASE, ATTN_NORM);
2174         }
2175 }
2176
2177 void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime)
2178 {
2179         if(v0 < v1)
2180                 e.rotfield = max(e.rotfield, time + rottime);
2181         else if(v0 > v1)
2182                 e.regenfield = max(e.regenfield, time + regentime);
2183 }
2184
2185 #define PREGIVE_WEAPONS(e) WEPSET_DECLARE_A(save_weapons); WEPSET_COPY_AE(save_weapons, e)
2186 #define PREGIVE(e,f) float save_##f; save_##f = (e).f
2187 #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)
2188 #define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
2189 #define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
2190 #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)
2191
2192 float GiveItems(entity e, float beginarg, float endarg)
2193 {
2194         float got, i, j, val, op;
2195         float _switchweapon;
2196         entity wi;
2197         string cmd;
2198
2199         val = 999;
2200         op = OP_SET;
2201
2202         got = 0;
2203
2204         _switchweapon = FALSE;
2205         if (e.autoswitch)
2206                 if (e.switchweapon == w_getbestweapon(e))
2207                         _switchweapon = TRUE;
2208
2209         e.strength_finished = max(0, e.strength_finished - time);
2210         e.invincible_finished = max(0, e.invincible_finished - time);
2211         e.superweapons_finished = max(0, e.superweapons_finished - time);
2212         
2213         PREGIVE(e, items);
2214         PREGIVE_WEAPONS(e);
2215         PREGIVE(e, strength_finished);
2216         PREGIVE(e, invincible_finished);
2217         PREGIVE(e, superweapons_finished);
2218         PREGIVE(e, ammo_nails);
2219         PREGIVE(e, ammo_cells);
2220         PREGIVE(e, ammo_shells);
2221         PREGIVE(e, ammo_rockets);
2222         PREGIVE(e, ammo_fuel);
2223         PREGIVE(e, armorvalue);
2224         PREGIVE(e, health);
2225
2226         for(i = beginarg; i < endarg; ++i)
2227         {
2228                 cmd = argv(i);
2229
2230                 if(cmd == "0" || stof(cmd))
2231                 {
2232                         val = stof(cmd);
2233                         continue;
2234                 }
2235                 switch(cmd)
2236                 {
2237                         case "no":
2238                                 op = OP_MAX;
2239                                 val = 0;
2240                                 continue;
2241                         case "max":
2242                                 op = OP_MAX;
2243                                 continue;
2244                         case "min":
2245                                 op = OP_MIN;
2246                                 continue;
2247                         case "plus":
2248                                 op = OP_PLUS;
2249                                 continue;
2250                         case "minus":
2251                                 op = OP_MINUS;
2252                                 continue;
2253                         case "ALL":
2254                                 got += GiveBit(e, items, IT_FUEL_REGEN, op, val);
2255                                 got += GiveValue(e, strength_finished, op, val);
2256                                 got += GiveValue(e, invincible_finished, op, val);
2257                                 got += GiveValue(e, superweapons_finished, op, val);
2258                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
2259                         case "all":
2260                                 got += GiveBit(e, items, IT_JETPACK, op, val);
2261                                 got += GiveValue(e, health, op, val);
2262                                 got += GiveValue(e, armorvalue, op, val);
2263                         case "allweapons":
2264                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2265                                 {
2266                                         wi = get_weaponinfo(j);
2267                                         if(wi.weapon)
2268                                                 if not(wi.spawnflags & WEP_FLAG_MUTATORBLOCKED)
2269                                                         got += GiveWeapon(e, j, op, val);
2270                                 }
2271                         case "allammo":
2272                                 got += GiveValue(e, ammo_cells, op, val);
2273                                 got += GiveValue(e, ammo_shells, op, val);
2274                                 got += GiveValue(e, ammo_nails, op, val);
2275                                 got += GiveValue(e, ammo_rockets, op, val);
2276                                 got += GiveValue(e, ammo_fuel, op, val);
2277                                 break;
2278                         case "unlimited_ammo":
2279                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
2280                                 break;
2281                         case "unlimited_weapon_ammo":
2282                                 got += GiveBit(e, items, IT_UNLIMITED_WEAPON_AMMO, op, val);
2283                                 break;
2284                         case "unlimited_superweapons":
2285                                 got += GiveBit(e, items, IT_UNLIMITED_SUPERWEAPONS, op, val);
2286                                 break;
2287                         case "jetpack":
2288                                 got += GiveBit(e, items, IT_JETPACK, op, val);
2289                                 break;
2290                         case "fuel_regen":
2291                                 got += GiveBit(e, items, IT_FUEL_REGEN, op, val);
2292                                 break;
2293                         case "strength":
2294                                 got += GiveValue(e, strength_finished, op, val);
2295                                 break;
2296                         case "invincible":
2297                                 got += GiveValue(e, invincible_finished, op, val);
2298                                 break;
2299                         case "superweapons":
2300                                 got += GiveValue(e, superweapons_finished, op, val);
2301                                 break;
2302                         case "cells":
2303                                 got += GiveValue(e, ammo_cells, op, val);
2304                                 break;
2305                         case "shells":
2306                                 got += GiveValue(e, ammo_shells, op, val);
2307                                 break;
2308                         case "nails":
2309                         case "bullets":
2310                                 got += GiveValue(e, ammo_nails, op, val);
2311                                 break;
2312                         case "rockets":
2313                                 got += GiveValue(e, ammo_rockets, op, val);
2314                                 break;
2315                         case "health":
2316                                 got += GiveValue(e, health, op, val);
2317                                 break;
2318                         case "armor":
2319                                 got += GiveValue(e, armorvalue, op, val);
2320                                 break;
2321                         case "fuel":
2322                                 got += GiveValue(e, ammo_fuel, op, val);
2323                                 break;
2324                         default:
2325                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2326                                 {
2327                                         wi = get_weaponinfo(j);
2328                                         if(cmd == wi.netname)
2329                                         {
2330                                                 got += GiveWeapon(e, j, op, val);
2331                                                 break;
2332                                         }
2333                                 }
2334                                 if(j > WEP_LAST)
2335                                         print("give: invalid item ", cmd, "\n");
2336                                 break;
2337                 }
2338                 val = 999;
2339                 op = OP_SET;
2340         }
2341
2342         POSTGIVE_BIT(e, items, IT_FUEL_REGEN, "misc/itempickup.wav", string_null);
2343         POSTGIVE_BIT(e, items, IT_UNLIMITED_SUPERWEAPONS, "misc/powerup.wav", "misc/poweroff.wav");
2344         POSTGIVE_BIT(e, items, IT_UNLIMITED_WEAPON_AMMO, "misc/powerup.wav", "misc/poweroff.wav");
2345         POSTGIVE_BIT(e, items, IT_JETPACK, "misc/itempickup.wav", string_null);
2346         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
2347         {
2348                 wi = get_weaponinfo(j);
2349                 if(wi.weapon)
2350                 {
2351                         POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null);
2352                         if not(WEPSET_CONTAINS_AW(save_weapons, j))
2353                                 if(WEPSET_CONTAINS_EW(e, j))
2354                                         weapon_action(wi.weapon, WR_PRECACHE);
2355                 }
2356         }
2357         POSTGIVE_VALUE(e, strength_finished, 1, "misc/powerup.wav", "misc/poweroff.wav");
2358         POSTGIVE_VALUE(e, invincible_finished, 1, "misc/powerup_shield.wav", "misc/poweroff.wav");
2359         POSTGIVE_VALUE(e, ammo_nails, 0, "misc/itempickup.wav", string_null);
2360         POSTGIVE_VALUE(e, ammo_cells, 0, "misc/itempickup.wav", string_null);
2361         POSTGIVE_VALUE(e, ammo_shells, 0, "misc/itempickup.wav", string_null);
2362         POSTGIVE_VALUE(e, ammo_rockets, 0, "misc/itempickup.wav", string_null);
2363         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);
2364         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);
2365         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);
2366
2367         if(e.superweapons_finished <= 0)
2368                 if(WEPSET_CONTAINS_ANY_EA(self, WEPBIT_SUPERWEAPONS))
2369                         e.superweapons_finished = autocvar_g_balance_superweapons_time;
2370
2371         if (g_minstagib)
2372         {
2373                 e.health = bound(0, e.health, 100);
2374                 e.armorvalue = bound(0, e.armorvalue, 999);
2375         }
2376
2377         if(e.strength_finished <= 0)
2378                 e.strength_finished = 0;
2379         else
2380                 e.strength_finished += time;
2381         if(e.invincible_finished <= 0)
2382                 e.invincible_finished = 0;
2383         else
2384                 e.invincible_finished += time;
2385         if(e.superweapons_finished <= 0)
2386                 e.superweapons_finished = 0;
2387         else
2388                 e.superweapons_finished += time;
2389
2390         if not(WEPSET_CONTAINS_EW(e, e.switchweapon))
2391                 _switchweapon = TRUE;
2392         if(_switchweapon)
2393                 W_SwitchWeapon_Force(e, w_getbestweapon(e));
2394
2395         return got;
2396 }
2397 #endif