]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/t_items.qc
use a healthbar for powerup respawns
[divverent/nexuiz.git] / data / qcsrc / server / t_items.qc
1 #define ITEM_RESPAWN_TICKS 10
2
3 #define ITEM_RESPAWNTIME(i)         ((i).respawntime + crandom() * (i).respawntimejitter)
4         // range: respawntime - respawntimejitter .. respawntime + respawntimejitter
5 #define ITEM_RESPAWNTIME_INITIAL(i) (ITEM_RESPAWN_TICKS + random() * ((i).respawntime + (i).respawntimejitter - ITEM_RESPAWN_TICKS))
6         // range: 10 .. respawntime + respawntimejitter
7
8 floatfield Item_CounterField(float it)
9 {
10         switch(it)
11         {
12                 case IT_SHELLS:      return ammo_shells;
13                 case IT_NAILS:       return ammo_nails;
14                 case IT_ROCKETS:     return ammo_rockets;
15                 case IT_CELLS:       return ammo_cells;
16                 case IT_FUEL:        return ammo_fuel;
17                 case IT_5HP:         return health;
18                 case IT_25HP:        return health;
19                 case IT_HEALTH:      return health;
20                 case IT_ARMOR_SHARD: return armorvalue;
21                 case IT_ARMOR:       return armorvalue;
22                 // add more things here (health, armor)
23                 default:             error("requested item has no counter field");
24         }
25 }
26
27 string Item_CounterFieldName(float it)
28 {
29         switch(it)
30         {
31                 case IT_SHELLS:      return "shells";
32                 case IT_NAILS:       return "nails";
33                 case IT_ROCKETS:     return "rockets";
34                 case IT_CELLS:       return "cells";
35                 case IT_FUEL:        return "fuel";
36
37                 // add more things here (health, armor)
38                 default:             error("requested item has no counter field name");
39         }
40 }
41
42 .float max_armorvalue;
43
44 void Item_Respawn (void)
45 {
46         self.model = self.mdl;          // restore original model
47         self.solid = SOLID_TRIGGER;     // allow it to be touched again
48         if(!g_minstagib && self.items == IT_STRENGTH)
49                 sound (self, CHAN_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);   // play respawn sound
50         else if(!g_minstagib && self.items == IT_INVINCIBLE)
51                 sound (self, CHAN_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);     // play respawn sound
52         else
53                 sound (self, CHAN_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTN_NORM);        // play respawn sound
54         setorigin (self, self.origin);
55
56         //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
57         pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
58 }
59
60 void Item_RespawnCountdown (void)
61 {
62         if(self.count >= ITEM_RESPAWN_TICKS)
63         {
64                 if(self.waypointsprite_attached)
65                         WaypointSprite_Kill(self.waypointsprite_attached);
66                 Item_Respawn();
67         }
68         else
69         {
70                 self.nextthink = time + 1;
71                 self.count += 1;
72                 if(self.count == 1)
73                 {
74                         string name;
75                         vector rgb;
76                         name = string_null;
77                         if(g_minstagib)
78                         {
79                                 switch(self.items)
80                                 {
81                                         case IT_STRENGTH:   name = "item-invis"; rgb = '0 0 1'; break;
82                                         case IT_NAILS:      name = "item-extralife"; rgb = '1 0 0'; break;
83                                         case IT_INVINCIBLE: name = "item-speed"; rgb = '1 0 1'; break;
84                                 }
85                         }
86                         else
87                         {
88                                 switch(self.items)
89                                 {
90                                         case IT_STRENGTH:   name = "item-strength"; rgb = '0 0 1'; break;
91                                         case IT_INVINCIBLE: name = "item-shield"; rgb = '1 0 1'; break;
92                                 }
93                         }
94                         switch(self.items)
95                         {
96                                 case IT_FUEL_REGEN:     name = "item-fuelregen"; rgb = '1 0.5 0'; break;
97                                 case IT_JETPACK:        name = "item-jetpack"; rgb = '0.5 0.5 0.5'; break;
98                         }
99                         if(name)
100                         {
101                                 WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, FALSE);
102                                 if(self.waypointsprite_attached)
103                                 {
104                                         WaypointSprite_UpdateTeamRadar(self.waypointsprite_attached, RADARICON_POWERUP, rgb);
105                                         WaypointSprite_UpdateMaxHealth(self.waypointsprite_attached, ITEM_RESPAWN_TICKS + 1);
106                                 }
107                         }
108                 }
109                 sound (self, CHAN_TRIGGER, "misc/itemrespawncountdown.wav", VOL_BASE, ATTN_NORM);       // play respawn sound
110                 if(self.waypointsprite_attached)
111                 {
112                         WaypointSprite_Ping(self.waypointsprite_attached);
113                         WaypointSprite_UpdateHealth(self.waypointsprite_attached, self.count);
114                 }
115         }
116 }
117
118 void Item_ScheduleRespawnIn(entity e, float t)
119 {
120         if(e.flags & FL_POWERUP)
121         {
122                 e.think = Item_RespawnCountdown;
123                 e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
124                 e.count = 0;
125         }
126         else
127         {
128                 e.think = Item_Respawn;
129                 e.nextthink = time + t;
130         }
131 }
132
133 void Item_ScheduleRespawn(entity e)
134 {
135         Item_ScheduleRespawnIn(e, ITEM_RESPAWNTIME(e));
136 }
137
138 void Item_ScheduleInitialRespawn(entity e)
139 {
140         Item_ScheduleRespawnIn(e, game_starttime - time + ITEM_RESPAWNTIME_INITIAL(e));
141 }
142
143 float Item_GiveTo(entity item, entity player)
144 {
145         float _switchweapon;
146         float pickedup;
147         float it;
148         float i;
149         entity e;
150
151         // if nothing happens to player, just return without taking the item
152         pickedup = FALSE;
153         _switchweapon = FALSE;
154
155         if (g_minstagib)
156         {
157                 if (item.ammo_fuel)
158                 if (player.ammo_fuel < g_pickup_fuel_max)
159                 {
160                         pickedup = TRUE;
161                         player.ammo_fuel = min(player.ammo_fuel + item.ammo_fuel, g_pickup_fuel_max);
162                         player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
163                 }
164                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
165                 {
166                         pickedup = TRUE;
167                         player.items |= it;
168                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
169                 }
170
171                 _switchweapon = TRUE;
172                 if (item.ammo_cells)
173                 {
174                         pickedup = TRUE;
175                         // play some cool sounds ;)
176                         centerprint(player, "\n");
177                         if(player.health <= 5)
178                                 announce(player, "announcer/robotic/lastsecond.wav");
179                         else if(player.health < 50)
180                                 announce(player, "announcer/robotic/narrowly.wav");
181                         // sound not available
182                         // else if(item.items == IT_CELLS)
183                         //      play2(player, "announce/robotic/ammo.wav");
184
185                         if (item.weapons & WEPBIT_MINSTANEX)
186                                 W_GiveWeapon (player, WEP_MINSTANEX, "Nex");
187                         if (item.ammo_cells)
188                                 player.ammo_cells = min (player.ammo_cells + cvar("g_minstagib_ammo_drop"), 999);
189                         player.health = 100;
190                 }
191
192                 // extralife powerup
193                 if (item.max_health)
194                 {
195                         pickedup = TRUE;
196                         // sound not available
197                         // play2(player, "announce/robotic/extra.ogg\nplay2 announce/robotic/_lives.wav");
198                         player.armorvalue = player.armorvalue + cvar("g_minstagib_extralives");
199                         sprint(player, "^3You picked up some extra lives\n");
200                 }
201
202                 // invis powerup
203                 if (item.strength_finished)
204                 {
205                         pickedup = TRUE;
206                         // sound not available
207                         // play2(player, "announce/robotic/invisible.wav");
208                         player.strength_finished = max(player.strength_finished, time) + cvar("g_balance_powerup_strength_time");
209                 }
210
211                 // speed powerup
212                 if (item.invincible_finished)
213                 {
214                         pickedup = TRUE;
215                         // sound not available
216                         // play2(player, "announce/robotic/speed.wav");
217                         player.invincible_finished = max(player.invincible_finished, time) + cvar("g_balance_powerup_strength_time");
218                 }
219
220                 if (item.ammo_fuel)
221                 if (player.ammo_fuel < g_pickup_fuel_max)
222                 {
223                         pickedup = TRUE;
224                         player.ammo_fuel = min(player.ammo_fuel + item.ammo_fuel, g_pickup_fuel_max);
225                         player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
226                 }
227
228         }
229         else
230         {
231                 if (g_weapon_stay == 1)
232                 if not(item.flags & FL_NO_WEAPON_STAY)
233                 if (item.flags & FL_WEAPON)
234                 {
235                         if(item.classname == "droppedweapon")
236                         {
237                                 if (player.weapons & item.weapons)      // don't let players stack ammo by tossing weapons
238                                         goto skip;
239                         }
240                         else
241                         {
242                                 if (player.weapons & item.weapons)
243                                         goto skip;
244                         }
245                 }
246
247                 // in case the player has autoswitch enabled do the following:
248                 // if the player is using their best weapon before items are given, they
249                 // probably want to switch to an even better weapon after items are given
250                 if (player.autoswitch)
251                 if (player.switchweapon == w_getbestweapon(player))
252                         _switchweapon = TRUE;
253
254                 if not(player.weapons & W_WeaponBit(player.switchweapon))
255                         _switchweapon = TRUE;
256
257                 if (item.ammo_shells)
258                 if (player.ammo_shells < g_pickup_shells_max)
259                 {
260                         pickedup = TRUE;
261                         player.ammo_shells = min (player.ammo_shells + item.ammo_shells, g_pickup_shells_max);
262                 }
263                 if (item.ammo_nails)
264                 if (player.ammo_nails < g_pickup_nails_max)
265                 {
266                         pickedup = TRUE;
267                         player.ammo_nails = min (player.ammo_nails + item.ammo_nails, g_pickup_nails_max);
268                 }
269                 if (item.ammo_rockets)
270                 if (player.ammo_rockets < g_pickup_rockets_max)
271                 {
272                         pickedup = TRUE;
273                         player.ammo_rockets = min (player.ammo_rockets + item.ammo_rockets, g_pickup_rockets_max);
274                 }
275                 if (item.ammo_cells)
276                 if (player.ammo_cells < g_pickup_cells_max)
277                 {
278                         pickedup = TRUE;
279                         player.ammo_cells = min (player.ammo_cells + item.ammo_cells, g_pickup_cells_max);
280                 }
281                 if (item.ammo_fuel)
282                 if (player.ammo_fuel < g_pickup_fuel_max)
283                 {
284                         pickedup = TRUE;
285                         player.ammo_fuel = min(player.ammo_fuel + item.ammo_fuel, g_pickup_fuel_max);
286                         player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
287                 }
288
289                 if (item.flags & FL_WEAPON)
290                 if ((it = item.weapons - (item.weapons & player.weapons)))
291                 {
292                         pickedup = TRUE;
293                         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
294                         {
295                                 e = get_weaponinfo(i);
296                                 if(it & e.weapons)
297                                         W_GiveWeapon (player, e.weapon, item.netname);
298                         }
299                 }
300
301                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
302                 {
303                         pickedup = TRUE;
304                         player.items |= it;
305                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
306                 }
307
308                 if (item.strength_finished)
309                 {
310                         pickedup = TRUE;
311                         player.strength_finished = max(player.strength_finished, time) + cvar("g_balance_powerup_strength_time");
312                 }
313                 if (item.invincible_finished)
314                 {
315                         pickedup = TRUE;
316                         player.invincible_finished = max(player.invincible_finished, time) + cvar("g_balance_powerup_invincible_time");
317                 }
318                 //if (item.speed_finished)
319                 //{
320                 //      pickedup = TRUE;
321                 //      player.speed_finished = max(player.speed_finished, time) + cvar("g_balance_powerup_speed_time");
322                 //}
323                 //if (item.slowmo_finished)
324                 //{
325                 //      pickedup = TRUE;
326                 //      player.slowmo_finished = max(player.slowmo_finished, time) + (cvar("g_balance_powerup_slowmo_time") * cvar("g_balance_powerup_slowmo_speed"));
327                 //}
328
329                 if (item.health)
330                 if (player.health < item.max_health)
331                 {
332                         pickedup = TRUE;
333                         player.health = min(player.health + item.health, item.max_health);
334                         player.pauserothealth_finished = max(player.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
335                 }
336                 if (item.armorvalue)
337                 if (player.armorvalue < item.max_armorvalue)
338                 {
339                         pickedup = TRUE;
340                         player.armorvalue = min(player.armorvalue + item.armorvalue, item.max_armorvalue);
341                         player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + cvar("g_balance_pause_armor_rot"));
342                 }
343         }
344
345 :skip
346         // always eat teamed entities
347         if(item.team)
348                 pickedup = TRUE;
349
350         if (!pickedup)
351                 return 0;
352
353         sound (player, CHAN_AUTO, item.item_pickupsound, VOL_BASE, ATTN_NORM);
354         if (_switchweapon)
355                 if (player.switchweapon != w_getbestweapon(player))
356                         W_SwitchWeapon_Force(player, w_getbestweapon(player));
357
358         return 1;
359 }
360
361 void Item_Touch (void)
362 {
363         entity e, head;
364
365         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
366         if (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
367         {
368                 remove(self);
369                 return;
370         }
371         if (other.classname != "player")
372                 return;
373         if (other.deadflag)
374                 return;
375         if (self.solid != SOLID_TRIGGER)
376                 return;
377         if (self.owner == other)
378                 return;
379
380         if(!Item_GiveTo(self, other))
381                 return;
382
383         pointparticles(particleeffectnum("item_pickup"), self.origin, '0 0 0', 1);
384
385         if (self.classname == "droppedweapon")
386                 remove (self);
387         else if((self.flags & FL_WEAPON) && !(self.flags & FL_NO_WEAPON_STAY) && g_weapon_stay)
388                 return;
389         else
390         {
391                 self.solid = SOLID_NOT;
392                 self.model = string_null;
393                 if(self.team)
394                 {
395                         RandomSelection_Init();
396                         for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
397                                 RandomSelection_Add(head, 0, string_null, head.cnt, 0);
398                         e = RandomSelection_chosen_ent;
399                 }
400                 else
401                         e = self;
402                 Item_ScheduleRespawn(e);
403         }
404 }
405
406 void Item_FindTeam()
407 {
408         entity head, e;
409
410         if(self.effects & EF_NODRAW)
411         {
412                 // marker for item team search
413                 dprint("Initializing item team ", ftos(self.team), "\n");
414                 RandomSelection_Init();
415                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
416                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
417                 e = RandomSelection_chosen_ent;
418                 e.state = 0;
419
420                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
421                 {
422                         if(head != e)
423                         {
424                                 // make it a non-spawned item
425                                 head.solid = SOLID_NOT;
426                                 head.model = string_null;
427                                 head.state = 1; // state 1 = initially hidden item
428                         }
429                         head.effects &~= EF_NODRAW;
430                 }
431
432                 if(e.flags & FL_POWERUP) // do not spawn powerups initially!
433                 {
434                         e.solid = SOLID_NOT;
435                         e.model = string_null;
436                         Item_ScheduleInitialRespawn(e);
437                 }
438         }
439 }
440
441 void Item_Reset()
442 {
443         if(self.state == 1)
444         {
445                 self.model = string_null;
446                 self.solid = SOLID_NOT;
447         }
448         else
449         {
450                 self.model = self.mdl;
451                 self.solid = SOLID_TRIGGER;
452         }
453         setorigin (self, self.origin);
454         self.think = SUB_Null;
455         self.nextthink = 0;
456
457         if(self.flags & FL_POWERUP) // do not spawn powerups initially!
458         {
459                 self.solid = SOLID_NOT;
460                 self.model = string_null;
461                 Item_ScheduleInitialRespawn(self);
462         }
463 }
464
465 // Savage: used for item garbage-collection
466 // TODO: perhaps nice special effect?
467 void RemoveItem(void)
468 {
469         remove(self);
470 }
471
472 // pickup evaluation functions
473 // these functions decide how desirable an item is to the bots
474
475 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;};
476
477 float weapon_pickupevalfunc(entity player, entity item)
478 {
479         // if we already have the weapon, rate it 1/5th normal value
480         if ((player.weapons & item.weapons) == item.weapons)
481                 return item.bot_pickupbasevalue * 0.2;
482         return item.bot_pickupbasevalue;
483 };
484
485 float commodity_pickupevalfunc(entity player, entity item)
486 {
487         float c;
488         c = 0;
489         // TODO: figure out if the player even has the weapon this ammo is for?
490         // may not affect strategy much though...
491         // find out how much more ammo/armor/health the player can hold
492         if (item.ammo_shells)
493         if (player.ammo_shells < g_pickup_shells_max)
494                 c = c + max(0, 1 - player.ammo_shells / g_pickup_shells_max);
495         if (item.ammo_nails)
496         if (player.ammo_nails < g_pickup_nails_max)
497                 c = c + max(0, 1 - player.ammo_nails / g_pickup_nails_max);
498         if (item.ammo_rockets)
499         if (player.ammo_rockets < g_pickup_rockets_max)
500                 c = c + max(0, 1 - player.ammo_rockets / g_pickup_rockets_max);
501         if (item.ammo_cells)
502         if (player.ammo_cells < g_pickup_cells_max)
503                 c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
504         if (item.armorvalue)
505         if (player.armorvalue < item.max_armorvalue)
506                 c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
507         if (item.health)
508         if (player.health < item.max_health)
509                 c = c + max(0, 1 - player.health / item.max_health);
510
511         return item.bot_pickupbasevalue * c;
512 };
513
514
515 .float is_item;
516 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)
517 {
518         startitem_failed = FALSE;
519
520         // is it a dropped weapon?
521         if (self.classname == "droppedweapon")
522         {
523                 self.reset = SUB_Remove;
524                 // it's a dropped weapon
525                 self.movetype = MOVETYPE_TOSS;
526                 self.solid = SOLID_TRIGGER;
527                 // Savage: remove thrown items after a certain period of time ("garbage collection")
528                 self.think = RemoveItem;
529                 self.nextthink = time + 60;
530                 // don't drop if in a NODROP zone (such as lava)
531                 traceline(self.origin, self.origin, MOVE_NORMAL, self);
532                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
533                 {
534                         startitem_failed = TRUE;
535                         remove(self);
536                         return;
537                 }
538         }
539         else
540         {
541                 self.reset = Item_Reset;
542                 // it's a level item
543                 if(self.spawnflags & 1)
544                         self.noalign = 1;
545                 if (self.noalign)
546                         self.movetype = MOVETYPE_NONE;
547                 else
548                         self.movetype = MOVETYPE_TOSS;
549                 self.solid = SOLID_TRIGGER;
550                 // do item filtering according to game mode and other things
551                 if (!self.noalign)
552                 {
553                         // first nudge it off the floor a little bit to avoid math errors
554                         setorigin(self, self.origin + '0 0 1');
555                         // set item size before we spawn a spawnfunc_waypoint
556                         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
557                                 setsize (self, '-16 -16 0', '16 16 48');
558                         else
559                                 setsize (self, '-16 -16 0', '16 16 32');
560                         // note droptofloor returns FALSE if stuck/or would fall too far
561                         droptofloor();
562                         waypoint_spawnforitem(self);
563                 }
564
565                 if(teams_matter)
566                 {
567                         if(self.notteam)
568                         {
569                                 print("removed non-teamplay ", self.classname, "\n");
570                                 startitem_failed = TRUE;
571                                 remove (self);
572                                 return;
573                         }
574                 }
575                 else
576                 {
577                         if(self.notfree)
578                         {
579                                 print("removed non-FFA ", self.classname, "\n");
580                                 startitem_failed = TRUE;
581                                 remove (self);
582                                 return;
583                         }
584                 }
585
586                 if(self.notq3a)
587                 {
588                         // We aren't TA or something like that, so we keep the Q3A entities
589                         print("removed non-Q3A ", self.classname, "\n");
590                         startitem_failed = TRUE;
591                         remove (self);
592                         return;
593                 }
594
595                 /*
596                  * can't do it that way, as it would break maps
597                  * TODO make a target_give like entity another way, that perhaps has
598                  * the weapon name in a key
599                 if(self.targetname)
600                 {
601                         // target_give not yet supported; maybe later
602                         print("removed targeted ", self.classname, "\n");
603                         startitem_failed = TRUE;
604                         remove (self);
605                         return;
606                 }
607                 */
608
609                 if(cvar("spawn_debug") >= 2)
610                 {
611                         entity otheritem;
612                         for(otheritem = findradius(self.origin, 3); otheritem; otheritem = otheritem.chain)
613                         {
614                                 if(otheritem.is_item)
615                                 {
616                                         dprint("XXX Found duplicated item: ", itemname, vtos(self.origin));
617                                         dprint(" vs ", otheritem.netname, vtos(otheritem.origin), "\n");
618                                         error("Mapper sucks.");
619                                 }
620                         }
621                         self.is_item = TRUE;
622                 }
623
624                 weaponsInMap |= weaponid;
625
626                 if(g_lms)
627                 {
628                         startitem_failed = TRUE;
629                         remove(self);
630                         return;
631                 }
632                 else if (g_weaponarena && ((weaponid & WEPBIT_ALL) || (itemid & IT_AMMO)))
633                 {
634                         startitem_failed = TRUE;
635                         remove(self);
636                         return;
637                 }
638                 else if (g_minstagib)
639                 {
640                         // don't remove dropped items and powerups
641                         if (self.classname != "minstagib")
642                         {
643                                 startitem_failed = TRUE;
644                                 remove (self);
645                                 return;
646                         }
647                 }
648                 else if ((!cvar("g_pickup_items") || g_nixnex) && itemid != IT_STRENGTH && itemid != IT_INVINCIBLE && itemid != IT_HEALTH)
649                 {
650                         startitem_failed = TRUE;
651                         remove (self);
652                         return;
653                 }
654
655                 precache_model (itemmodel);
656                 precache_sound (pickupsound);
657                 precache_sound ("misc/itemrespawn.wav");
658                 precache_sound ("misc/itemrespawncountdown.wav");
659
660                 if(itemid == IT_STRENGTH)
661                         precache_sound ("misc/strength_respawn.wav");
662                 if(itemid == IT_INVINCIBLE)
663                         precache_sound ("misc/shield_respawn.wav");
664
665                 if((itemid & (IT_STRENGTH | IT_INVINCIBLE | IT_HEALTH | IT_ARMOR | IT_KEY1 | IT_KEY2)) || (weaponid & WEPBIT_ALL))
666                         self.target = "###item###"; // for finding the nearest item using find()
667         }
668
669         self.bot_pickup = TRUE;
670         self.bot_pickupevalfunc = pickupevalfunc;
671         self.bot_pickupbasevalue = pickupbasevalue;
672         self.mdl = itemmodel;
673         self.item_pickupsound = pickupsound;
674         // let mappers override respawntime
675         if(!self.respawntime) // both set
676         {
677                 self.respawntime = defaultrespawntime;
678                 self.respawntimejitter = defaultrespawntimejitter;
679         }
680         self.netname = itemname;
681         self.items = itemid;
682         self.weapons = weaponid;
683         self.flags = FL_ITEM | itemflags;
684         self.touch = Item_Touch;
685         setmodel (self, self.mdl); // precision set below
686         self.effects |= EF_LOWPRECISION;
687         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
688                 setsize (self, '-16 -16 0', '16 16 48');
689         else
690                 setsize (self, '-16 -16 0', '16 16 32');
691         if(itemflags & FL_WEAPON)
692                 self.modelflags |= MF_ROTATE;
693
694         if (self.classname != "droppedweapon") // if dropped, colormap is already set up nicely
695         if (itemflags & FL_WEAPON)
696         {
697                 // neutral team color for pickup weapons
698                 self.colormap = 1024; // color shirt=0 pants=0 grey
699         }
700
701         if (cvar("g_fullbrightitems"))
702                 self.effects = self.effects | EF_FULLBRIGHT;
703
704         self.state = 0;
705         if(self.team)
706         {
707                 if(!self.cnt)
708                         self.cnt = 1; // item probability weight
709                 self.effects = self.effects | EF_NODRAW; // marker for item team search
710                 InitializeEntity(self, Item_FindTeam, INITPRIO_FINDTARGET);
711         }
712         else if(self.flags & FL_POWERUP) // do not spawn powerups initially!
713         {
714                 self.solid = SOLID_NOT;
715                 self.model = string_null;
716                 Item_ScheduleInitialRespawn(self);
717         }
718 }
719
720 /* replace items in minstagib
721  * IT_STRENGTH   = invisibility
722  * IT_NAILS      = extra lives
723  * IT_INVINCIBLE = speed
724  */
725 void minstagib_items (float itemid)
726 {
727         // we don't want to replace dropped weapons ;)
728         if (self.classname == "droppedweapon")
729         {
730                 self.ammo_cells = 25;
731                 StartItem ("models/weapons/g_nex.md3",
732                         "weapons/weaponpickup.wav", 15, 0,
733                         "MinstaNex", 0, WEPBIT_MINSTANEX, FL_WEAPON, generic_pickupevalfunc, 1000);
734                 return;
735         }
736
737         local float rnd;
738         self.classname = "minstagib";
739
740         // replace rocket launchers and nex guns with ammo cells
741         if (itemid == IT_CELLS)
742         {
743                 self.ammo_cells = 1;
744                 StartItem ("models/items/a_cells.md3",
745                         "misc/itempickup.wav", 45, 0,
746                         "Nex Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100);
747                 return;
748         }
749
750         // randomize
751         rnd = random() * 3;
752         if (rnd <= 1)
753                 itemid = IT_STRENGTH;
754         else if (rnd <= 2)
755                 itemid = IT_NAILS;
756         else
757                 itemid = IT_INVINCIBLE;
758
759         // replace with invis
760         if (itemid == IT_STRENGTH)
761         {
762                 self.effects = EF_ADDITIVE;
763                 self.strength_finished = 30;
764                 StartItem ("models/items/g_strength.md3",
765                         "misc/powerup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
766                         "Invisibility", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_MID);
767         }
768         // replace with extra lives
769         if (itemid == IT_NAILS)
770         {
771                 self.max_health = 1;
772                 StartItem ("models/items/g_h100.md3",
773                         "misc/megahealth.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
774                         "Extralife", IT_NAILS, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
775
776         }
777         // replace with speed
778         if (itemid == IT_INVINCIBLE)
779         {
780                 self.effects = EF_ADDITIVE;
781                 self.invincible_finished = 30;
782                 StartItem ("models/items/g_invincible.md3",
783                         "misc/powerup_shield.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup,
784                         "Speed", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, BOT_PICKUP_RATING_MID);
785         }
786
787 }
788
789 float minst_no_auto_cells;
790 void minst_remove_item (void) {
791         if(minst_no_auto_cells)
792                 remove(self);
793 }
794
795 float weaponswapping;
796 float internalteam;
797
798 void weapon_defaultspawnfunc(float wpn)
799 {
800         entity e;
801         float t;
802         var .float ammofield;
803         string s;
804         entity oldself;
805         float i, j;
806
807         // set the respawntime in advance (so replaced weapons can copy it)
808
809         if(!self.respawntime)
810         {
811                 e = get_weaponinfo(wpn);
812                 if(e.items == IT_SUPERWEAPON)
813                 {
814                         self.respawntime = g_pickup_respawntime_powerup;
815                         self.respawntimejitter = g_pickup_respawntimejitter_powerup;
816                 }
817                 else
818                 {
819                         self.respawntime = g_pickup_respawntime_weapon;
820                         self.respawntimejitter = g_pickup_respawntimejitter_weapon;
821                 }
822         }
823
824         if(self.classname != "droppedweapon" && self.classname != "replacedweapon")
825         {
826                 e = get_weaponinfo(wpn);
827                 s = cvar_string(strcat("g_weaponreplace_", e.netname));
828                 if(s == "0")
829                 {
830                         remove(self);
831                         startitem_failed = TRUE;
832                         return;
833                 }
834                 t = tokenize_console(s);
835                 if(t >= 2)
836                 {
837                         self.team = --internalteam;
838                         oldself = self;
839                         for(i = 1; i < t; ++i)
840                         {
841                                 s = argv(i);
842                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
843                                 {
844                                         e = get_weaponinfo(j);
845                                         if(e.netname == s)
846                                         {
847                                                 self = spawn();
848                                                 copyentity(oldself, self);
849                                                 self.classname = "replacedweapon";
850                                                 weapon_defaultspawnfunc(j);
851                                                 break;
852                                         }
853                                 }
854                                 if(j > WEP_LAST)
855                                 {
856                                         print("The weapon replace list for ", oldself.classname, " contains an unknown weapon ", s, ". Skipped.\n");
857                                 }
858                         }
859                         self = oldself;
860                 }
861                 if(t >= 1)
862                 {
863                         s = argv(0);
864                         wpn = 0;
865                         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
866                         {
867                                 e = get_weaponinfo(j);
868                                 if(e.netname == s)
869                                 {
870                                         wpn = j;
871                                         break;
872                                 }
873                         }
874                         if(j > WEP_LAST)
875                         {
876                                 print("The weapon replace list for ", self.classname, " contains an unknown weapon ", s, ". Skipped.\n");
877                         }
878                 }
879                 if(wpn == 0)
880                 {
881                         remove(self);
882                         startitem_failed = TRUE;
883                         return;
884                 }
885         }
886
887         e = get_weaponinfo(wpn);
888
889         if(e.items && e.items != IT_SUPERWEAPON)
890         {
891                 for(i = 0, j = 1; i < 24; ++i, j *= 2)
892                 {
893                         if(e.items & j)
894                         {
895                                 ammofield = Item_CounterField(j);
896                                 if(!self.ammofield)
897                                         self.ammofield = cvar(strcat("g_pickup_", Item_CounterFieldName(j)));
898                         }
899                 }
900         }
901         else
902         {
903                 self.flags |= FL_NO_WEAPON_STAY;
904         }
905
906         // weapon stay isn't supported for teamed weapons
907         if(self.team)
908                 self.flags |= FL_NO_WEAPON_STAY;
909
910         if(g_weapon_stay == 2 && self.classname != "droppedweapon")
911         {
912                 self.ammo_shells = 0;
913                 self.ammo_nails = 0;
914                 self.ammo_cells = 0;
915                 self.ammo_rockets = 0;
916                 // weapon stay 2: don't use ammo on weapon pickups; instead
917                 // initialize all ammo types to the pickup ammo unless set by g_start_ammo_*
918         }
919
920         StartItem(e.model, "weapons/weaponpickup.wav", self.respawntime, self.respawntimejitter, e.message, 0, e.weapons, FL_WEAPON, weapon_pickupevalfunc, e.bot_pickupbasevalue);
921         if (self.modelindex) // don't precache if self was removed
922                 weapon_action(e.weapon, WR_PRECACHE);
923 }
924
925 void spawnfunc_weapon_shotgun (void);
926 void spawnfunc_weapon_uzi (void) {
927         if(q3acompat_machineshotgunswap)
928         if(self.classname != "droppedweapon")
929         {
930                 weapon_defaultspawnfunc(WEP_SHOTGUN);
931                 return;
932         }
933         weapon_defaultspawnfunc(WEP_UZI);
934 }
935
936 void spawnfunc_weapon_shotgun (void) {
937         if(q3acompat_machineshotgunswap)
938         if(self.classname != "droppedweapon")
939         {
940                 weapon_defaultspawnfunc(WEP_UZI);
941                 return;
942         }
943         weapon_defaultspawnfunc(WEP_SHOTGUN);
944 }
945
946 void spawnfunc_weapon_nex (void)
947 {
948         if (g_minstagib)
949         {
950                 minstagib_items(IT_CELLS);
951                 self.think = minst_remove_item;
952                 self.nextthink = time;
953                 return;
954         }
955         weapon_defaultspawnfunc(WEP_NEX);
956 }
957
958 void spawnfunc_weapon_minstanex (void)
959 {
960         if (g_minstagib)
961         {
962                 minstagib_items(IT_CELLS);
963                 self.think = minst_remove_item;
964                 self.nextthink = time;
965                 return;
966         }
967         weapon_defaultspawnfunc(WEP_MINSTANEX);
968 }
969
970 void spawnfunc_weapon_rocketlauncher (void)
971 {
972         if (g_minstagib)
973         {
974                 minstagib_items(IT_CELLS);
975                 self.think = minst_remove_item;
976                 self.nextthink = time;
977                 return;
978         }
979         weapon_defaultspawnfunc(WEP_ROCKET_LAUNCHER);
980 }
981
982 void spawnfunc_item_rockets (void) {
983         if(!self.ammo_rockets)
984                 self.ammo_rockets = g_pickup_rockets;
985         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);
986 }
987
988 void spawnfunc_item_shells (void);
989 void spawnfunc_item_bullets (void) {
990         if(!weaponswapping)
991         if(q3acompat_machineshotgunswap)
992         if(self.classname != "droppedweapon")
993         {
994                 weaponswapping = TRUE;
995                 spawnfunc_item_shells();
996                 weaponswapping = FALSE;
997                 return;
998         }
999
1000         if(!self.ammo_nails)
1001                 self.ammo_nails = g_pickup_nails;
1002         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);
1003 }
1004
1005 void spawnfunc_item_cells (void) {
1006         if(!self.ammo_cells)
1007                 self.ammo_cells = g_pickup_cells;
1008         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);
1009 }
1010
1011 void spawnfunc_item_shells (void) {
1012         if(!weaponswapping)
1013         if(q3acompat_machineshotgunswap)
1014         if(self.classname != "droppedweapon")
1015         {
1016                 weaponswapping = TRUE;
1017                 spawnfunc_item_bullets();
1018                 weaponswapping = FALSE;
1019                 return;
1020         }
1021
1022         if(!self.ammo_shells)
1023                 self.ammo_shells = g_pickup_shells;
1024         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);
1025 }
1026
1027 void spawnfunc_item_armor_small (void) {
1028         if(!self.armorvalue)
1029                 self.armorvalue = g_pickup_armorsmall;
1030         if(!self.max_armorvalue)
1031                 self.max_armorvalue = g_pickup_armorsmall_max;
1032         StartItem ("models/items/g_a1.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);
1033 }
1034
1035 void spawnfunc_item_armor_medium (void) {
1036         if(!self.armorvalue)
1037                 self.armorvalue = g_pickup_armormedium;
1038         if(!self.max_armorvalue)
1039                 self.max_armorvalue = g_pickup_armormedium_max;
1040         StartItem ("models/items/g_armormedium.md3", "misc/armor10.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "25 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1041 }
1042
1043 void spawnfunc_item_armor_big (void) {
1044         if(!self.armorvalue)
1045                 self.armorvalue = g_pickup_armorbig;
1046         if(!self.max_armorvalue)
1047                 self.max_armorvalue = g_pickup_armorbig_max;
1048         StartItem ("models/items/g_a50.md3", "misc/armor17_5.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "50 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000);
1049 }
1050
1051 void spawnfunc_item_armor_large (void) {
1052         if(!self.armorvalue)
1053                 self.armorvalue = g_pickup_armorlarge;
1054         if(!self.max_armorvalue)
1055                 self.max_armorvalue = g_pickup_armorlarge_max;
1056         StartItem ("models/items/g_a25.md3", "misc/armor25.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1057 }
1058
1059 void spawnfunc_item_health_small (void) {
1060         if(!self.max_health)
1061                 self.max_health = g_pickup_healthsmall_max;
1062         if(!self.health)
1063                 self.health = g_pickup_healthsmall;
1064         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);
1065 }
1066
1067 void spawnfunc_item_health_medium (void) {
1068         if(!self.max_health)
1069                 self.max_health = g_pickup_healthmedium_max;
1070         if(!self.health)
1071                 self.health = g_pickup_healthmedium;
1072         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);
1073 }
1074
1075 void spawnfunc_item_health_large (void) {
1076         if(!self.max_health)
1077                 self.max_health = g_pickup_healthlarge_max;
1078         if(!self.health)
1079                 self.health = g_pickup_healthlarge;
1080         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);
1081 }
1082
1083 void spawnfunc_item_health_mega (void) {
1084         if(!cvar("g_powerup_superhealth"))
1085                 return;
1086
1087         if(g_arena && !cvar("g_arena_powerups"))
1088                 return;
1089
1090         if(g_minstagib) {
1091                 minstagib_items(IT_NAILS);
1092         } else {
1093                 if(!self.max_health)
1094                         self.max_health = g_pickup_healthmega_max;
1095                 if(!self.health)
1096                         self.health = g_pickup_healthmega;
1097                 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);
1098         }
1099 }
1100
1101 // support old misnamed entities
1102 void spawnfunc_item_armor1() { spawnfunc_item_armor_small(); }  // FIXME: in Quake this is green armor, in Nexuiz maps it is an armor shard
1103 void spawnfunc_item_armor25() { spawnfunc_item_armor_large(); }
1104 void spawnfunc_item_health1() { spawnfunc_item_health_small(); }
1105 void spawnfunc_item_health25() { spawnfunc_item_health_medium(); }
1106 void spawnfunc_item_health100() { spawnfunc_item_health_mega(); }
1107
1108 void spawnfunc_item_strength (void) {
1109         if(!cvar("g_powerup_strength"))
1110                 return;
1111
1112         if(g_arena && !cvar("g_arena_powerups"))
1113                 return;
1114
1115         if(g_minstagib) {
1116                 minstagib_items(IT_STRENGTH);
1117         } else {
1118                 precache_sound("weapons/strength_fire.wav");
1119                 self.strength_finished = 30;
1120                 self.effects = EF_ADDITIVE;
1121                 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);
1122         }
1123 }
1124
1125 void spawnfunc_item_invincible (void) {
1126         if(!cvar("g_powerup_shield"))
1127                 return;
1128
1129         if(g_arena && !cvar("g_arena_powerups"))
1130                 return;
1131
1132         if(g_minstagib) {
1133                 minstagib_items(IT_INVINCIBLE);
1134         } else {
1135                 self.invincible_finished = 30;
1136                 self.effects = EF_ADDITIVE;
1137                 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);
1138         }
1139 }
1140
1141 void spawnfunc_item_minst_cells (void) {
1142         if (g_minstagib)
1143         {
1144                 minst_no_auto_cells = 1;
1145                 minstagib_items(IT_CELLS);
1146         }
1147         else
1148                 remove(self);
1149 }
1150
1151 // compatibility:
1152 void spawnfunc_item_quad (void) {self.classname = "item_strength";spawnfunc_item_strength();}
1153
1154 float target_item_func_set(float a, float b)
1155 {
1156         if(b == 0)
1157                 return a;
1158         else if(b < 0)
1159                 return 0;
1160         else
1161                 return b;
1162 }
1163
1164 float target_item_func_min(float a, float b)
1165 {
1166         if(b == 0)
1167                 return a;
1168         else if(b < 0)
1169                 return 0;
1170         else
1171                 return min(a, b);
1172 }
1173
1174 float target_item_func_max(float a, float b)
1175 {
1176         return max(a, b);
1177 }
1178
1179 float target_item_func_bitset(float a, float b)
1180 {
1181         return b;
1182 }
1183
1184 float target_item_func_and(float a, float b)
1185 {
1186         return a & b;
1187 }
1188
1189 float target_item_func_itembitset(float a, float b)
1190 {
1191         return (a - (a & (IT_PICKUPMASK | IT_STRENGTH | IT_INVINCIBLE))) | b;
1192 }
1193
1194 float target_item_func_itemand(float a, float b)
1195 {
1196         return (a - (a & (IT_PICKUPMASK | IT_STRENGTH | IT_INVINCIBLE))) | (a & b);
1197 }
1198
1199 float target_item_func_or(float a, float b)
1200 {
1201         return a | b;
1202 }
1203
1204 float target_item_func_andnot(float a, float b)
1205 {
1206         return a - (a & b);
1207 }
1208
1209 float target_item_changed;
1210 void target_item_change(float binary, .float field, float(float a, float b) func, string sound_increase, string sound_decrease)
1211 {
1212         float n, d;
1213         n = func(activator.field, self.field);
1214
1215         if(binary)
1216         {
1217                 d = n & activator.field;
1218                 if(d != n) // bits added?
1219                         d = +1;
1220                 else if(d != activator.field) // bits removed?
1221                         d = -1;
1222                 else
1223                         d = 0;
1224         }
1225         else
1226                 d = n - activator.field;
1227
1228         if(d < 0)
1229         {
1230                 if(sound_decrease != "")
1231                         sound (activator, CHAN_AUTO, sound_decrease, VOL_BASE, ATTN_NORM);
1232                 target_item_changed = 1;
1233         }
1234         else if(d > 0)
1235         {
1236                 if(sound_increase != "")
1237                         sound (activator, CHAN_AUTO, sound_increase, VOL_BASE, ATTN_NORM);
1238                 target_item_changed = 1;
1239         }
1240         activator.field = n;
1241 }
1242
1243 void target_items_use (void)
1244 {
1245         float h0, a0, f0;
1246
1247         if(activator.classname == "droppedweapon")
1248         {
1249                 EXACTTRIGGER_TOUCH;
1250                 remove(activator);
1251                 return;
1252         }
1253
1254         if(activator.classname != "player")
1255                 return;
1256         if(activator.deadflag != DEAD_NO)
1257                 return;
1258         EXACTTRIGGER_TOUCH;
1259
1260         entity e;
1261         for(e = world; (e = find(e, classname, "droppedweapon")); )
1262                 if(e.enemy == activator)
1263                         remove(e);
1264
1265         float _switchweapon;
1266         _switchweapon = FALSE;
1267         if (activator.autoswitch)
1268                 if (activator.switchweapon == w_getbestweapon(activator))
1269                         _switchweapon = TRUE;
1270
1271         a0 = activator.armorvalue;
1272         h0 = activator.health;
1273         f0 = activator.ammo_fuel;
1274         target_item_changed = 0;
1275
1276         if(self.spawnflags == 0) // SET
1277         {
1278                 target_item_change(0, ammo_shells, target_item_func_set, "misc/itempickup.wav", "");
1279                 target_item_change(0, ammo_nails, target_item_func_set, "misc/itempickup.wav", "");
1280                 target_item_change(0, ammo_rockets, target_item_func_set, "misc/itempickup.wav", "");
1281                 target_item_change(0, ammo_cells, target_item_func_set, "misc/itempickup.wav", "");
1282                 target_item_change(0, ammo_fuel, target_item_func_set, "misc/itempickup.wav", "");
1283                 target_item_change(0, health, target_item_func_set, "misc/megahealth.wav", "");
1284                 target_item_change(0, armorvalue, target_item_func_set, "misc/armor25.wav", "");
1285                 target_item_change(1, items, target_item_func_itembitset, "misc/powerup.wav", "misc/poweroff.wav");
1286                 target_item_change(1, weapons, target_item_func_bitset, "weapons/weaponpickup.wav", "");
1287
1288                 if((self.items & activator.items) & IT_STRENGTH)
1289                         activator.strength_finished = time + self.strength_finished;
1290                 if((self.items & activator.items) & IT_INVINCIBLE)
1291                         activator.invincible_finished = time + self.invincible_finished;
1292         }
1293         else if(self.spawnflags == 1) // AND/MIN
1294         {
1295                 target_item_change(0, ammo_shells, target_item_func_min, "misc/itempickup.wav", "");
1296                 target_item_change(0, ammo_nails, target_item_func_min, "misc/itempickup.wav", "");
1297                 target_item_change(0, ammo_rockets, target_item_func_min, "misc/itempickup.wav", "");
1298                 target_item_change(0, ammo_cells, target_item_func_min, "misc/itempickup.wav", "");
1299                 target_item_change(0, ammo_fuel, target_item_func_min, "misc/itempickup.wav", "");
1300                 target_item_change(0, health, target_item_func_min, "misc/megahealth.wav", "");
1301                 target_item_change(0, armorvalue, target_item_func_min, "misc/armor25.wav", "");
1302                 target_item_change(1, items, target_item_func_itemand, "misc/powerup.wav", "misc/poweroff.wav");
1303                 target_item_change(1, weapons, target_item_func_and, "weapons/weaponpickup.wav", "");
1304
1305                 if((self.items & activator.items) & IT_STRENGTH)
1306                         activator.strength_finished = min(activator.strength_finished, time + self.strength_finished);
1307                 if((self.items & activator.items) & IT_INVINCIBLE)
1308                         activator.invincible_finished = min(activator.invincible_finished, time + self.invincible_finished);
1309         }
1310         else if(self.spawnflags == 2) // OR/MAX
1311         {
1312                 target_item_change(0, ammo_shells, target_item_func_max, "misc/itempickup.wav", "");
1313                 target_item_change(0, ammo_nails, target_item_func_max, "misc/itempickup.wav", "");
1314                 target_item_change(0, ammo_rockets, target_item_func_max, "misc/itempickup.wav", "");
1315                 target_item_change(0, ammo_cells, target_item_func_max, "misc/itempickup.wav", "");
1316                 target_item_change(0, ammo_fuel, target_item_func_max, "misc/itempickup.wav", "");
1317                 target_item_change(0, health, target_item_func_max, "misc/megahealth.wav", "");
1318                 target_item_change(0, armorvalue, target_item_func_max, "misc/armor25.wav", "");
1319                 target_item_change(1, items, target_item_func_or, "misc/powerup.wav", "misc/poweroff.wav");
1320                 target_item_change(1, weapons, target_item_func_or, "weapons/weaponpickup.wav", "");
1321
1322                 if((self.items & activator.items) & IT_STRENGTH)
1323                         activator.strength_finished = max(activator.strength_finished, time + self.strength_finished);
1324                 if((self.items & activator.items) & IT_INVINCIBLE)
1325                         activator.invincible_finished = max(activator.invincible_finished, time + self.invincible_finished);
1326         }
1327         else if(self.spawnflags == 4) // ANDNOT/MIN
1328         {
1329                 target_item_change(0, ammo_shells, target_item_func_min, "misc/itempickup.wav", "");
1330                 target_item_change(0, ammo_nails, target_item_func_min, "misc/itempickup.wav", "");
1331                 target_item_change(0, ammo_rockets, target_item_func_min, "misc/itempickup.wav", "");
1332                 target_item_change(0, ammo_cells, target_item_func_min, "misc/itempickup.wav", "");
1333                 target_item_change(0, ammo_fuel, target_item_func_min, "misc/itempickup.wav", "");
1334                 target_item_change(0, health, target_item_func_min, "misc/megahealth.wav", "");
1335                 target_item_change(0, armorvalue, target_item_func_min, "misc/armor25.wav", "");
1336                 target_item_change(1, items, target_item_func_andnot, "misc/powerup.wav", "misc/poweroff.wav");
1337                 target_item_change(1, weapons, target_item_func_andnot, "weapons/weaponpickup.wav", "");
1338
1339                 if((self.items & activator.items) & IT_STRENGTH)
1340                         activator.strength_finished = min(activator.strength_finished, time + self.strength_finished);
1341                 if((self.items & activator.items) & IT_INVINCIBLE)
1342                         activator.invincible_finished = min(activator.invincible_finished, time + self.invincible_finished);
1343         }
1344
1345         if not(activator.items & IT_STRENGTH)
1346                 activator.strength_finished = 0;
1347         if not(activator.items & IT_INVINCIBLE)
1348                 activator.invincible_finished = 0;
1349
1350         if(activator.health > h0)
1351                 activator.pauserothealth_finished = max(activator.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
1352         else if(activator.health < h0)
1353                 activator.pauseregen_finished = max(activator.pauseregen_finished, time + cvar("g_balance_pause_health_regen"));
1354
1355         if(activator.ammo_fuel > f0)
1356                 activator.pauserotfuel_finished = max(activator.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
1357         else if(activator.ammo_fuel < f0)
1358                 activator.pauseregen_finished = max(activator.pauseregen_finished, time + cvar("g_balance_pause_fuel_regen"));
1359
1360         if(activator.armorvalue > a0)
1361                 activator.pauserotarmor_finished = max(activator.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
1362
1363         if not(activator.weapons & W_WeaponBit(activator.switchweapon))
1364                 _switchweapon = TRUE;
1365         if(_switchweapon)
1366                 W_SwitchWeapon_Force(activator, w_getbestweapon(activator));
1367
1368         if(target_item_changed)
1369                 centerprint(activator, self.message);
1370 }
1371
1372 void spawnfunc_target_items (void)
1373 {
1374         float n, i, j;
1375         entity e;
1376         self.use = target_items_use;
1377         if(!self.strength_finished)
1378                 self.strength_finished = cvar("g_balance_powerup_strength_time");
1379         if(!self.invincible_finished)
1380                 self.invincible_finished = cvar("g_balance_powerup_invincible_time");
1381
1382         precache_sound("misc/itempickup.wav");
1383         precache_sound("misc/itempickup.wav");
1384         precache_sound("misc/itempickup.wav");
1385         precache_sound("misc/itempickup.wav");
1386         precache_sound("misc/megahealth.wav");
1387         precache_sound("misc/armor25.wav");
1388         precache_sound("misc/powerup.wav");
1389         precache_sound("misc/poweroff.wav");
1390         precache_sound("weapons/weaponpickup.wav");
1391
1392         n = tokenize_console(self.netname);
1393         for(i = 0; i < n; ++i)
1394         {
1395                 if     (argv(i) == "unlimited_ammo")         self.items |= IT_UNLIMITED_AMMO;
1396                 else if(argv(i) == "unlimited_weapon_ammo")  self.items |= IT_UNLIMITED_WEAPON_AMMO;
1397                 else if(argv(i) == "unlimited_superweapons") self.items |= IT_UNLIMITED_SUPERWEAPONS;
1398                 else if(argv(i) == "strength")               self.items |= IT_STRENGTH;
1399                 else if(argv(i) == "invincible")             self.items |= IT_INVINCIBLE;
1400                 else if(argv(i) == "jetpack")                self.items |= IT_JETPACK;
1401                 else if(argv(i) == "fuel_regen")             self.items |= IT_FUEL_REGEN;
1402                 else
1403                 {
1404                         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1405                         {
1406                                 e = get_weaponinfo(j);
1407                                 if(argv(i) == e.netname)
1408                                 {
1409                                         self.weapons |= e.weapons;
1410                                         if(self.spawnflags == 0 || self.spawnflags == 2)
1411                                                 weapon_action(e.weapon, WR_PRECACHE);
1412                                         break;
1413                                 }
1414                         }
1415                         if(j > WEP_LAST)
1416                                 print("target_items: invalid item ", argv(i), "\n");
1417                 }
1418         }
1419 }
1420
1421 void spawnfunc_item_fuel(void)
1422 {
1423         if(!self.ammo_fuel)
1424                 self.ammo_fuel = g_pickup_fuel;
1425         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);
1426 }
1427
1428 void spawnfunc_item_fuel_regen(void)
1429 {
1430         if(start_items & IT_FUEL_REGEN)
1431         {
1432                 spawnfunc_item_fuel();
1433                 return;
1434         }
1435         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);
1436 }
1437
1438 void spawnfunc_item_jetpack(void)
1439 {
1440         if(g_grappling_hook)
1441                 return; // sorry, but these two can't coexist (same button); spawn fuel instead
1442         if(!self.ammo_fuel)
1443                 self.ammo_fuel = g_pickup_fuel_jetpack;
1444         if(start_items & IT_JETPACK)
1445         {
1446                 spawnfunc_item_fuel();
1447                 return;
1448         }
1449         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);
1450 }