]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/t_items.qc
make item respawn times customizable as groups; fix weapons cfg
[divverent/nexuiz.git] / data / qcsrc / server / t_items.qc
1 floatfield Item_CounterField(float it)
2 {
3         switch(it)
4         {
5                 case IT_SHELLS:      return ammo_shells;
6                 case IT_NAILS:       return ammo_nails;
7                 case IT_ROCKETS:     return ammo_rockets;
8                 case IT_CELLS:       return ammo_cells;
9                 case IT_5HP:         return health;
10                 case IT_25HP:        return health;
11                 case IT_HEALTH:      return health;
12                 case IT_ARMOR_SHARD: return armorvalue;
13                 case IT_ARMOR:       return armorvalue;
14                 // add more things here (health, armor)
15                 default:             error("requested item has no counter field");
16         }
17 }
18
19 string Item_CounterFieldName(float it)
20 {
21         switch(it)
22         {
23                 case IT_SHELLS:      return "shells";
24                 case IT_NAILS:       return "nails";
25                 case IT_ROCKETS:     return "rockets";
26                 case IT_CELLS:       return "cells";
27                 // add more things here (health, armor)
28                 default:             error("requested item has no counter field name");
29         }
30 }
31
32 void Item_SpawnByWeaponCode(float it)
33 {
34         weapon_action(it, WR_SPAWNFUNC);
35 }
36
37 .float max_armorvalue;
38
39 void Item_Respawn (void)
40 {
41         self.model = self.mdl;          // restore original model
42         self.solid = SOLID_TRIGGER;     // allow it to be touched again
43         sound (self, CHAN_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTN_NORM);        // play respawn sound
44         setorigin (self, self.origin);
45
46         //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
47         pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
48 }
49
50 float Item_GiveTo(entity item, entity player)
51 {
52         float _switchweapon;
53         float pickedup;
54         float it;
55         float i;
56         entity e;
57
58         // if nothing happens to player, just return without taking the item
59         pickedup = FALSE;
60         _switchweapon = FALSE;
61
62         if (g_minstagib)
63         {
64                 _switchweapon = TRUE;
65                 if (item.ammo_cells)
66                 {
67                         pickedup = TRUE;
68                         // play some cool sounds ;)
69                         centerprint(player, "\n");
70                         if(player.health <= 5)
71                                 announce(player, "announcer/robotic/lastsecond.ogg");
72                         else if(player.health < 50)
73                                 announce(player, "announcer/robotic/narrowly.ogg");
74                         // sound not available
75                         // else if(item.items == IT_CELLS)
76                         //      play2(player, "announce/robotic/ammo.ogg");
77
78                         if (item.weapons & WEPBIT_MINSTANEX)
79                                 W_GiveWeapon (player, WEP_MINSTANEX, "Nex");
80                         if (item.ammo_cells)
81                                 player.ammo_cells = min (player.ammo_cells + cvar("g_minstagib_ammo_drop"), 999);
82                         player.health = 100;
83                 }
84
85                 // extralife powerup
86                 if (item.max_health)
87                 {
88                         pickedup = TRUE;
89                         // sound not available
90                         // play2(player, "announce/robotic/extra.ogg\nplay2 announce/robotic/_lives.ogg");
91                         player.armorvalue = player.armorvalue + cvar("g_minstagib_extralives");
92                         sprint(player, "^3You picked up some extra lives\n");
93                 }
94
95                 // invis powerup
96                 if (item.strength_finished)
97                 {
98                         pickedup = TRUE;
99                         // sound not available
100                         // play2(player, "announce/robotic/invisible.ogg");
101                         player.strength_finished = max(player.strength_finished, time) + cvar("g_balance_powerup_strength_time");
102                 }
103
104                 // speed powerup
105                 if (item.invincible_finished)
106                 {
107                         pickedup = TRUE;
108                         // sound not available
109                         // play2(player, "announce/robotic/speed.ogg");
110                         player.invincible_finished = max(player.invincible_finished, time) + cvar("g_balance_powerup_strength_time");
111                 }
112         }
113         else
114         {
115                 if (cvar("deathmatch") == 2 || cvar("g_weapon_stay"))
116                 {
117                         if (item.flags & FL_WEAPON && player.weapons & item.weapons && item.classname != "droppedweapon")
118                                 return 0;
119                         if (player.weapons & item.weapons && item.flags & FL_TOSSED)    // don't let players stack ammo by tossing weapons
120                                 return 0;
121                 }
122
123                 // in case the player has autoswitch enabled do the following:
124                 // if the player is using their best weapon before items are given, they
125                 // probably want to switch to an even better weapon after items are given
126                 if (player.autoswitch)
127                 if (player.switchweapon == w_getbestweapon(player))
128                         _switchweapon = TRUE;
129
130                 if (item.ammo_shells)
131                 if (player.ammo_shells < g_pickup_shells_max)
132                 {
133                         pickedup = TRUE;
134                         player.ammo_shells = min (player.ammo_shells + item.ammo_shells, g_pickup_shells_max);
135                 }
136                 if (item.ammo_nails)
137                 if (player.ammo_nails < g_pickup_nails_max)
138                 {
139                         pickedup = TRUE;
140                         player.ammo_nails = min (player.ammo_nails + item.ammo_nails, g_pickup_nails_max);
141                 }
142                 if (item.ammo_rockets)
143                 if (player.ammo_rockets < g_pickup_rockets_max)
144                 {
145                         pickedup = TRUE;
146                         player.ammo_rockets = min (player.ammo_rockets + item.ammo_rockets, g_pickup_rockets_max);
147                 }
148                 if (item.ammo_cells)
149                 if (player.ammo_cells < g_pickup_cells_max)
150                 {
151                         pickedup = TRUE;
152                         player.ammo_cells = min (player.ammo_cells + item.ammo_cells, g_pickup_cells_max);
153                 }
154
155                 if (item.flags & FL_WEAPON)
156                 if ((it = item.weapons - (item.weapons & player.weapons)))
157                 {
158                         pickedup = TRUE;
159                         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
160                         {
161                                 e = get_weaponinfo(i);
162                                 if(it & e.weapons)
163                                         W_GiveWeapon (player, e.weapon, item.netname);
164                         }
165                 }
166
167                 if (item.strength_finished)
168                 {
169                         pickedup = TRUE;
170                         player.strength_finished = max(player.strength_finished, time) + cvar("g_balance_powerup_strength_time");
171                 }
172                 if (item.invincible_finished)
173                 {
174                         pickedup = TRUE;
175                         player.invincible_finished = max(player.invincible_finished, time) + cvar("g_balance_powerup_invincible_time");
176                 }
177                 //if (item.speed_finished)
178                 //{
179                 //      pickedup = TRUE;
180                 //      player.speed_finished = max(player.speed_finished, time) + cvar("g_balance_powerup_speed_time");
181                 //}
182                 //if (item.slowmo_finished)
183                 //{
184                 //      pickedup = TRUE;
185                 //      player.slowmo_finished = max(player.slowmo_finished, time) + (cvar("g_balance_powerup_slowmo_time") * cvar("g_balance_powerup_slowmo_speed"));
186                 //}
187
188                 if (item.health)
189                 if (player.health < item.max_health)
190                 {
191                         pickedup = TRUE;
192                         player.health = min(player.health + item.health, item.max_health);
193                         player.pauserothealth_finished = max(player.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
194                 }
195                 if (item.armorvalue)
196                 if (player.armorvalue < item.max_armorvalue)
197                 {
198                         pickedup = TRUE;
199                         player.armorvalue = min(player.armorvalue + item.armorvalue, item.max_armorvalue);
200                         player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + cvar("g_balance_pause_armor_rot"));
201                 }
202         }
203
204         // always eat teamed entities
205         if(item.team)
206                 pickedup = TRUE;
207
208         if (!pickedup)
209                 return 0;
210
211         sound (player, CHAN_AUTO, item.item_pickupsound, VOL_BASE, ATTN_NORM);
212         if (_switchweapon)
213                 W_SwitchWeapon_Force(player, w_getbestweapon(item));
214
215         return 1;
216 }
217
218 void Item_Touch (void)
219 {
220         entity e, head;
221         float n;
222
223         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
224         if (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
225         {
226                 remove(self);
227                 return;
228         }
229         if (other.classname != "player")
230                 return;
231         if (other.deadflag)
232                 return;
233         if (self.solid != SOLID_TRIGGER)
234                 return;
235         if (self.owner == other)
236                 return;
237
238         if(!Item_GiveTo(self, other))
239                 return;
240
241         if (self.classname == "droppedweapon")
242                 remove (self);
243         else if((self.flags & FL_WEAPON) && !self.team && (cvar("deathmatch") == 2 || cvar("g_weapon_stay")))
244                 return;
245         else
246         {
247                 self.solid = SOLID_NOT;
248                 self.model = string_null;
249                 if(self.team)
250                 {
251                         RandomSelection_Init();
252                         for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
253                                 RandomSelection_Add(head, 0, 1, 0);
254                         e = RandomSelection_chosen_ent;
255                 }
256                 else
257                         e = self;
258                 e.nextthink = time + self.respawntime;
259                 e.think = Item_Respawn;
260         }
261 }
262
263 void Item_FindTeam()
264 {
265         entity head, e;
266
267         if(self.effects & EF_NODRAW)
268         {
269                 // marker for item team search
270                 dprint("Initializing item team ", ftos(self.team), "\n");
271                 RandomSelection_Init();
272                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
273                         RandomSelection_Add(head, 0, 1, 0);
274                 e = RandomSelection_chosen_ent;
275
276                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
277                 {
278                         if(head != e)
279                         {
280                                 // make it a non-spawned item
281                                 head.solid = SOLID_NOT;
282                                 head.model = string_null;
283                         }
284                         head.effects = head.effects - (head.effects & EF_NODRAW);
285                 }
286         }
287 }
288
289 // Savage: used for item garbage-collection
290 // TODO: perhaps nice special effect?
291 void RemoveItem(void) /*FIXDECL*/
292 {
293         remove(self);
294 }
295
296 // pickup evaluation functions
297 // these functions decide how desirable an item is to the bots
298
299 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;};
300
301 float weapon_pickupevalfunc(entity player, entity item)
302 {
303         // if we already have the weapon, rate it 1/5th normal value
304         if ((player.weapons & item.weapons) == item.weapons)
305                 return item.bot_pickupbasevalue * 0.2;
306         return item.bot_pickupbasevalue;
307 };
308
309 float commodity_pickupevalfunc(entity player, entity item)
310 {
311         float c;
312         c = 0;
313         // TODO: figure out if the player even has the weapon this ammo is for?
314         // may not affect strategy much though...
315         // find out how much more ammo/armor/health the player can hold
316         if (item.ammo_shells)
317         if (player.ammo_shells < g_pickup_shells_max)
318                 c = c + max(0, 1 - player.ammo_shells / g_pickup_shells_max);
319         if (item.ammo_nails)
320         if (player.ammo_nails < g_pickup_nails_max)
321                 c = c + max(0, 1 - player.ammo_nails / g_pickup_nails_max);
322         if (item.ammo_rockets)
323         if (player.ammo_rockets < g_pickup_rockets_max)
324                 c = c + max(0, 1 - player.ammo_rockets / g_pickup_rockets_max);
325         if (item.ammo_cells)
326         if (player.ammo_cells < g_pickup_cells_max)
327                 c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
328         if (item.armorvalue)
329         if (player.armorvalue < item.max_armorvalue)
330                 c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
331         if (item.health)
332         if (player.health < item.max_health)
333                 c = c + max(0, 1 - player.health / item.max_health);
334
335         return item.bot_pickupbasevalue * c;
336 };
337
338
339 .float is_item;
340 void StartItem (string itemmodel, string pickupsound, float defaultrespawntime, string itemname, float itemid, float weaponid, float itemflags, float(entity player, entity item) pickupevalfunc, float pickupbasevalue)
341 {
342         startitem_failed = FALSE;
343
344         // is it a dropped weapon?
345         if (self.classname == "droppedweapon")
346         {
347                 // it's a dropped weapon
348                 self.movetype = MOVETYPE_TOSS;
349                 self.solid = SOLID_TRIGGER;
350                 // Savage: remove thrown items after a certain period of time ("garbage collection")
351                 self.think = RemoveItem;
352                 self.nextthink = time + 60;
353                 // don't drop if in a NODROP zone (such as lava)
354                 traceline(self.origin, self.origin, MOVE_NORMAL, self);
355                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
356                 {
357                         startitem_failed = TRUE;
358                         remove(self);
359                         return;
360                 }
361         }
362         else
363         {
364                 // it's a level item
365                 if(self.spawnflags & 1)
366                         self.noalign = 1;
367                 if (self.noalign)
368                         self.movetype = MOVETYPE_NONE;
369                 else
370                         self.movetype = MOVETYPE_TOSS;
371                 self.solid = SOLID_TRIGGER;
372                 // do item filtering according to game mode and other things
373                 if (!self.noalign)
374                 {
375                         // first nudge it off the floor a little bit to avoid math errors
376                         setorigin(self, self.origin + '0 0 1');
377                         // set item size before we spawn a spawnfunc_waypoint
378                         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
379                                 setsize (self, '-16 -16 0', '16 16 48');
380                         else
381                                 setsize (self, '-16 -16 0', '16 16 32');
382                         // note droptofloor returns FALSE if stuck/or would fall too far
383                         droptofloor();
384                         waypoint_spawnforitem(self);
385                 }
386
387                 if(teams_matter)
388                 {
389                         if(self.notteam)
390                         {
391                                 print("removed non-teamplay ", self.classname, "\n");
392                                 startitem_failed = TRUE;
393                                 remove (self);
394                                 return;
395                         }
396                 }
397                 else
398                 {
399                         if(self.notfree)
400                         {
401                                 print("removed non-FFA ", self.classname, "\n");
402                                 startitem_failed = TRUE;
403                                 remove (self);
404                                 return;
405                         }
406                 }
407
408                 if(self.notq3a)
409                 {
410                         // We aren't TA or something like that, so we keep the Q3A entities
411                         print("removed non-Q3A ", self.classname, "\n");
412                         startitem_failed = TRUE;
413                         remove (self);
414                         return;
415                 }
416
417                 /*
418                  * can't do it that way, as it would break maps
419                  * TODO make a target_give like entity another way, that perhaps has
420                  * the weapon name in a key
421                 if(self.targetname)
422                 {
423                         // target_give not yet supported; maybe later
424                         print("removed targeted ", self.classname, "\n");
425                         startitem_failed = TRUE;
426                         remove (self);
427                         return;
428                 }
429                 */
430
431                 if(cvar("spawn_debug") >= 2)
432                 {
433                         entity otheritem;
434                         for(otheritem = findradius(self.origin, 3); otheritem; otheritem = otheritem.chain)
435                         {
436                                 if(otheritem.is_item)
437                                 {
438                                         dprint("XXX Found duplicated item: ", itemname, vtos(self.origin));
439                                         dprint(" vs ", otheritem.netname, vtos(otheritem.origin), "\n");
440                                         error("Mapper sucks.");
441                                 }
442                         }
443                         self.is_item = TRUE;
444                 }
445
446                 weaponsInMap |= weaponid;
447
448                 if(g_lms || g_rocketarena)
449                 {
450                         startitem_failed = TRUE;
451                         remove(self);
452                         return;
453                 }
454                 else if (g_minstagib)
455                 {
456                         // don't remove dropped items and powerups
457                         if (self.classname != "minstagib")
458                         {
459                                 startitem_failed = TRUE;
460                                 remove (self);
461                                 return;
462                         }
463                 }
464                 else if ((!cvar("g_pickup_items") || g_nixnex) && itemid != IT_STRENGTH && itemid != IT_INVINCIBLE && itemid != IT_HEALTH)
465                 {
466                         startitem_failed = TRUE;
467                         remove (self);
468                         return;
469                 }
470
471                 precache_model (itemmodel);
472                 precache_sound (pickupsound);
473                 precache_sound ("misc/itemrespawn.wav");
474
475                 if((itemid & (IT_STRENGTH | IT_INVINCIBLE | IT_HEALTH | IT_ARMOR | IT_KEY1 | IT_KEY2)) || (weaponid & WEPBIT_ALL))
476                         self.target = "###item###"; // for finding the nearest item using find()
477         }
478
479         self.bot_pickup = TRUE;
480         self.bot_pickupevalfunc = pickupevalfunc;
481         self.bot_pickupbasevalue = pickupbasevalue;
482         self.mdl = itemmodel;
483         self.item_pickupsound = pickupsound;
484         // let mappers override respawntime
485         if (!self.respawntime)
486                 self.respawntime = defaultrespawntime;
487         self.netname = itemname;
488         self.items = itemid;
489         self.weapons = weaponid;
490         self.flags = FL_ITEM | itemflags;
491         self.touch = Item_Touch;
492         setmodel (self, self.mdl); // precision set below
493         self.effects |= EF_LOWPRECISION;
494         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
495                 setsize (self, '-16 -16 0', '16 16 48');
496         else
497                 setsize (self, '-16 -16 0', '16 16 32');
498         if (itemflags & FL_WEAPON)
499         {
500                 // neutral team color for pickup weapons
501                 self.colormap = 160 * 1024 + 160;
502         }
503
504         if (cvar("g_fullbrightitems"))
505                 self.effects = self.effects | EF_FULLBRIGHT;
506         
507         if(self.team)
508         {
509                 self.effects = self.effects | EF_NODRAW; // marker for item team search
510                 InitializeEntity(self, Item_FindTeam, INITPRIO_FINDTARGET);
511         }
512 }
513
514 /* replace items in minstagib
515  * IT_STRENGTH   = invisibility
516  * IT_NAILS      = extra lives
517  * IT_INVINCIBLE = speed
518  */
519 void minstagib_items (float itemid)
520 {
521         // we don't want to replace dropped weapons ;)
522         if (self.classname == "droppedweapon")
523         {
524                 self.ammo_cells = 25;
525                 StartItem ("models/weapons/g_nex.md3",
526                         "weapons/weaponpickup.wav", 15,
527                         "MinstaNex", 0, WEPBIT_MINSTANEX, FL_WEAPON, generic_pickupevalfunc, 1000);
528                 return;
529         }
530
531         local float rnd;
532         self.classname = "minstagib";
533
534         // replace rocket launchers and nex guns with ammo cells
535         if (itemid == IT_CELLS)
536         {
537                 self.ammo_cells = 1;
538                 StartItem ("models/items/a_cells.md3",
539                         "misc/itempickup.wav", 45,
540                         "Nex Ammo", IT_CELLS, 0, 0, generic_pickupevalfunc, 100);
541                 return;
542         }
543
544         // randomize
545         rnd = random() * 3;
546         if (rnd <= 1)
547                 itemid = IT_STRENGTH;
548         else if (rnd <= 2)
549                 itemid = IT_NAILS;
550         else
551                 itemid = IT_INVINCIBLE;
552
553         // replace with invis
554         if (itemid == IT_STRENGTH)
555         {
556                 self.effects = EF_ADDITIVE;
557                 self.strength_finished = 30;
558                 StartItem ("models/items/g_strength.md3",
559                         "misc/powerup.wav", g_pickup_respawntime_powerup,
560                         "Invisibility", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, 1000);
561         }
562         // replace with extra lives
563         if (itemid == IT_NAILS)
564         {
565                 self.max_health = 1;
566                 StartItem ("models/items/g_h100.md3",
567                         "misc/megahealth.wav", g_pickup_respawntime_powerup,
568                         "Extralife", IT_NAILS, 0, FL_POWERUP, generic_pickupevalfunc, 1000);
569
570         }
571         // replace with speed
572         if (itemid == IT_INVINCIBLE)
573         {
574                 self.effects = EF_ADDITIVE;
575                 self.invincible_finished = 30;
576                 StartItem ("models/items/g_invincible.md3",
577                         "misc/powerup_shield.wav", g_pickup_respawntime_powerup,
578                         "Speed", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, 1000);
579         }
580
581 }
582
583 float minst_no_auto_cells;
584 void minst_remove_item (void) {
585         if(minst_no_auto_cells)
586                 remove(self);
587 }
588
589 float weaponswapping;
590
591 void weapon_defaultspawnfunc(float wpn, float weight)
592 {
593         entity e;
594         float t;
595         var .float ammofield;
596         e = get_weaponinfo(wpn);
597
598         t = g_pickup_respawntime_short;
599
600         if(e.items && e.items != IT_SUPERWEAPON)
601         {
602                 ammofield = Item_CounterField(e.items);
603                 if(!self.ammofield)
604                         self.ammofield = cvar(strcat("g_pickup_", Item_CounterFieldName(e.items)));
605         }
606
607         if(e.items == IT_SUPERWEAPON)
608                 t = g_pickup_respawntime_powerup;
609
610         StartItem (e.model, "weapons/weaponpickup.wav", t, e.message, 0, e.weapons, FL_WEAPON, weapon_pickupevalfunc, weight);
611         if (self.modelindex) // don't precache if self was removed
612                 weapon_action(e.weapon, WR_PRECACHE);
613 }
614
615 void spawnfunc_weapon_shotgun (void);
616 void spawnfunc_weapon_uzi (void) {
617         if(!weaponswapping)
618         if(q3acompat_machineshotgunswap)
619         if(self.classname != "droppedweapon")
620         {
621                 weapon_defaultspawnfunc(WEP_SHOTGUN, 2500);
622                 return;
623         }
624         weapon_defaultspawnfunc(WEP_UZI, 5000);
625 }
626
627 void spawnfunc_weapon_shotgun (void) {
628         if(!weaponswapping)
629         if(q3acompat_machineshotgunswap)
630         if(self.classname != "droppedweapon")
631         {
632                 weapon_defaultspawnfunc(WEP_UZI, 5000);
633                 return;
634         }
635         weapon_defaultspawnfunc(WEP_SHOTGUN, 2500);
636 }
637
638 void spawnfunc_weapon_nex (void)
639 {
640         if (g_minstagib)
641         {
642                 minstagib_items(IT_CELLS);
643                 self.think = minst_remove_item;
644                 self.nextthink = time + cvar("sys_ticrate");
645                 return;
646         }
647         weapon_defaultspawnfunc(WEP_NEX, 10000);
648 }
649
650 void spawnfunc_weapon_minstanex (void)
651 {
652         if (g_minstagib)
653         {
654                 minstagib_items(IT_CELLS);
655                 self.think = minst_remove_item;
656                 self.nextthink = time + cvar("sys_ticrate");
657                 return;
658         }
659         weapon_defaultspawnfunc(WEP_MINSTANEX, 10000);
660 }
661
662 void spawnfunc_weapon_rocketlauncher (void)
663 {
664         if (g_minstagib)
665         {
666                 minstagib_items(IT_CELLS);
667                 self.think = minst_remove_item;
668                 self.nextthink = time + cvar("sys_ticrate");
669                 return;
670         }
671         weapon_defaultspawnfunc(WEP_ROCKET_LAUNCHER, 10000);
672 }
673
674 void spawnfunc_item_rockets (void) {
675         if(!self.ammo_rockets)
676                 self.ammo_rockets = g_pickup_rockets;
677         StartItem ("models/items/a_rockets.md3", "misc/itempickup.wav", g_pickup_respawntime_short, "rockets", IT_ROCKETS, 0, 0, commodity_pickupevalfunc, 3000);
678 }
679
680 void spawnfunc_item_shells (void);
681 void spawnfunc_item_bullets (void) {
682         if(!weaponswapping)
683         if(q3acompat_machineshotgunswap)
684         if(self.classname != "droppedweapon")
685         {
686                 weaponswapping = TRUE;
687                 spawnfunc_item_shells();
688                 weaponswapping = FALSE;
689                 return;
690         }
691
692         if(!self.ammo_nails)
693                 self.ammo_nails = g_pickup_nails;
694         StartItem ("models/items/a_bullets.mdl", "misc/itempickup.wav", g_pickup_respawntime_short, "bullets", IT_NAILS, 0, 0, commodity_pickupevalfunc, 2000);
695 }
696
697 void spawnfunc_item_cells (void) {
698         if(!self.ammo_cells)
699                 self.ammo_cells = g_pickup_cells;
700         StartItem ("models/items/a_cells.md3", "misc/itempickup.wav", g_pickup_respawntime_short, "cells", IT_CELLS, 0, 0, commodity_pickupevalfunc, 2000);
701 }
702
703 void spawnfunc_item_shells (void) {
704         if(!weaponswapping)
705         if(q3acompat_machineshotgunswap)
706         if(self.classname != "droppedweapon")
707         {
708                 weaponswapping = TRUE;
709                 spawnfunc_item_bullets();
710                 weaponswapping = FALSE;
711                 return;
712         }
713
714         if(!self.ammo_shells)
715                 self.ammo_shells = g_pickup_shells;
716         StartItem ("models/items/a_shells.md3", "misc/itempickup.wav", g_pickup_respawntime_short, "shells", IT_SHELLS, 0, 0, commodity_pickupevalfunc, 500);
717 }
718
719 void spawnfunc_item_armor_small (void) {
720         if(!self.armorvalue)
721                 self.armorvalue = g_pickup_armorsmall;
722         if(!self.max_armorvalue)
723                 self.max_armorvalue = g_pickup_armorsmall_max;
724         StartItem ("models/items/g_a1.md3", "misc/armor1.wav", g_pickup_respawntime_short, "5 Armor", IT_ARMOR_SHARD, 0, 0, commodity_pickupevalfunc, 1000);
725 }
726
727 void spawnfunc_item_armor_medium (void) {
728         if(!self.armorvalue)
729                 self.armorvalue = g_pickup_armormedium;
730         if(!self.max_armorvalue)
731                 self.max_armorvalue = g_pickup_armormedium_max;
732         StartItem ("models/items/g_armormedium.md3", "misc/armor1.wav", g_pickup_respawntime_medium, "25 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000);
733 }
734
735 void spawnfunc_item_armor_large (void) {
736         if(!self.armorvalue)
737                 self.armorvalue = g_pickup_armorlarge;
738         if(!self.max_armorvalue)
739                 self.max_armorvalue = g_pickup_armorlarge_max;
740         StartItem ("models/items/g_a25.md3", "misc/armor25.wav", g_pickup_respawntime_long, "100 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000);
741 }
742
743 void spawnfunc_item_health_small (void) {
744         if(!self.max_health)
745                 self.max_health = g_pickup_healthsmall_max;
746         if(!self.health)
747                 self.health = g_pickup_healthsmall;
748         StartItem ("models/items/g_h1.md3", "misc/minihealth.wav", g_pickup_respawntime_short, "5 Health", IT_5HP, 0, 0, commodity_pickupevalfunc, 20000);
749 }
750
751 void spawnfunc_item_health_medium (void) {
752         if(!self.max_health)
753                 self.max_health = g_pickup_healthmedium_max;
754         if(!self.health)
755                 self.health = g_pickup_healthmedium;
756         StartItem ("models/items/g_h25.md3", "misc/mediumhealth.wav", g_pickup_respawntime_short, "25 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, 20000);
757 }
758
759 void spawnfunc_item_health_large (void) {
760         if(!self.max_health)
761                 self.max_health = g_pickup_healthlarge_max;
762         if(!self.health)
763                 self.health = g_pickup_healthlarge;
764         StartItem ("models/items/g_h50.md3", "misc/mediumhealth.wav", g_pickup_respawntime_medium, "50 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, 20000);
765 }
766
767 void spawnfunc_item_health_mega (void) {
768         if(!cvar("g_powerup_superhealth"))
769                 return;
770
771         if(g_arena && !cvar("g_arena_powerups"))
772                 return;
773
774         if(g_minstagib) {
775                 minstagib_items(IT_NAILS);
776         } else {
777                 if(!self.max_health)
778                         self.max_health = g_pickup_healthmega_max;
779                 if(!self.health)
780                         self.health = g_pickup_healthmega;
781                 StartItem ("models/items/g_h100.md3", "misc/megahealth.wav", g_pickup_respawntime_long, "100 Health", IT_HEALTH, 0, 0, commodity_pickupevalfunc, 20000);
782         }
783 }
784
785 // support old misnamed entities
786 void spawnfunc_item_armor1() { spawnfunc_item_armor_small(); }  // FIXME: in Quake this is green armor, in Nexuiz maps it is an armor shard
787 void spawnfunc_item_armor25() { spawnfunc_item_armor_large(); }
788 void spawnfunc_item_health1() { spawnfunc_item_health_small(); }
789 void spawnfunc_item_health25() { spawnfunc_item_health_medium(); }
790 void spawnfunc_item_health100() { spawnfunc_item_health_mega(); }
791
792 void spawnfunc_item_strength (void) {
793         if(!cvar("g_powerup_strength"))
794                 return;
795
796         if(g_arena && !cvar("g_arena_powerups"))
797                 return;
798
799         if(g_minstagib) {
800                 minstagib_items(IT_STRENGTH);
801         } else {
802                 precache_sound("weapons/strength_fire.wav");
803                 self.strength_finished = 30;
804                 self.effects = EF_ADDITIVE;
805                 StartItem ("models/items/g_strength.md3", "misc/powerup.wav", g_pickup_respawntime_powerup, "Strength Powerup", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, 100000);
806         }
807 }
808
809 void spawnfunc_item_invincible (void) {
810         if(!cvar("g_powerup_shield"))
811                 return;
812
813         if(g_arena && !cvar("g_arena_powerups"))
814                 return;
815
816         if(g_minstagib) {
817                 minstagib_items(IT_INVINCIBLE);
818         } else {
819                 self.invincible_finished = 30;
820                 self.effects = EF_ADDITIVE;
821                 StartItem ("models/items/g_invincible.md3", "misc/powerup_shield.wav", g_pickup_respawntime_powerup, "Invulnerability", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, 100000);
822         }
823 }
824
825 void spawnfunc_item_minst_cells (void) {
826         if (g_minstagib)
827         {
828                 minst_no_auto_cells = 1;
829                 minstagib_items(IT_CELLS);
830         }
831         else
832                 remove(self);
833 }
834
835 // compatibility:
836 void spawnfunc_item_quad (void) {self.classname = "item_strength";spawnfunc_item_strength();}
837
838 void spawnfunc_misc_models (void)
839 {
840         SetBrushEntityModel();
841 }
842
843 void func_wall_use (void)
844 {
845         if(teams_matter)
846         {
847                 if(activator.team)
848                         self.colormap = (activator.team - 1) * 0x11;
849                 else
850                         self.colormap = 0x00;
851         }
852         else
853                 self.colormap = ceil(random() * 256) - 1;
854         self.colormap |= 1024; // RENDER_COLORMAPPED
855 }
856
857 void spawnfunc_func_wall (void)
858 {
859         SetBrushEntityModel();
860         self.solid = SOLID_BSP;
861         self.use = func_wall_use;
862 }
863