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