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