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