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