Fix some typo.
[xonotic/xonotic-data.pk3dir.git] / 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 .float pickup_anyway;
44
45 float Item_Customize()
46 {
47         if(self.spawnshieldtime)
48                 return TRUE;
49         if(self.weapons != (self.weapons & other.weapons))
50         {
51                 self.colormod = '0 0 0';
52                 self.glowmod = self.colormod;
53                 self.alpha = 0.5 + 0.5 * g_ghost_items; // halfway more alpha
54                 return TRUE;
55         }
56         else
57         {
58                 if(g_ghost_items)
59                 {
60                         self.colormod = stov(cvar_string("g_ghost_items_color"));
61                         self.glowmod = self.colormod;
62                         self.alpha = g_ghost_items;
63                         return TRUE;
64                 }
65                 else
66                         return FALSE;
67         }
68 }
69
70 void Item_Show (entity e, float mode)
71 {
72         e.effects &~= EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST;
73         if (mode > 0)
74         {
75                 // make the item look normal, and be touchable
76                 e.model = e.mdl;
77                 e.solid = SOLID_TRIGGER;
78                 e.colormod = '0 0 0';
79                 self.glowmod = self.colormod;
80                 e.alpha = 0;
81                 e.customizeentityforclient = func_null;
82
83                 e.spawnshieldtime = 1;
84         }
85         else if (mode < 0)
86         {
87                 // hide the item completely
88                 e.model = string_null;
89                 e.solid = SOLID_NOT;
90                 e.colormod = '0 0 0';
91                 self.glowmod = self.colormod;
92                 e.alpha = 0;
93                 e.customizeentityforclient = func_null;
94
95                 e.spawnshieldtime = 1;
96         }
97         else if((e.flags & FL_WEAPON) && (g_weapon_stay == 3))
98         {
99                 // make the item translucent green and not touchable
100                 e.model = e.mdl;
101                 e.solid = SOLID_TRIGGER; // can STILL be picked up!
102                 e.colormod = '0 0 0';
103                 self.glowmod = self.colormod;
104                 e.effects |= EF_STARDUST;
105                 e.customizeentityforclient = Item_Customize;
106
107                 e.spawnshieldtime = 0; // field indicates whether picking it up may give you anything other than the weapon
108         }
109         else if(g_ghost_items)
110         {
111                 // make the item translucent green and not touchable
112                 e.model = e.mdl;
113                 e.solid = SOLID_NOT;
114                 e.colormod = stov(cvar_string("g_ghost_items_color"));
115                 self.glowmod = self.colormod;
116                 e.alpha = g_ghost_items;
117                 e.customizeentityforclient = func_null;
118
119                 e.spawnshieldtime = 1;
120         }
121         else
122         {
123                 // hide the item completely
124                 e.model = string_null;
125                 e.solid = SOLID_NOT;
126                 e.colormod = stov(cvar_string("g_ghost_items_color"));
127                 self.glowmod = self.colormod;
128                 e.alpha = 0;
129                 e.customizeentityforclient = func_null;
130
131                 e.spawnshieldtime = 1;
132         }
133
134         if (e.strength_finished || e.invincible_finished)
135                 e.effects |= EF_ADDITIVE | EF_FULLBRIGHT;
136         if (cvar("g_nodepthtestitems"))
137                 e.effects |= EF_NODEPTHTEST;
138         if (cvar("g_fullbrightitems"))
139                 e.effects |= EF_FULLBRIGHT;
140
141         // relink entity (because solid may have changed)
142         setorigin(e, e.origin);
143 }
144
145 void Item_Respawn (void)
146 {
147         Item_Show(self, 1);
148         if(!g_minstagib && self.items == IT_STRENGTH)
149                 sound (self, CHAN_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);   // play respawn sound
150         else if(!g_minstagib && self.items == IT_INVINCIBLE)
151                 sound (self, CHAN_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);     // play respawn sound
152         else
153                 sound (self, CHAN_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTN_NORM);        // play respawn sound
154         setorigin (self, self.origin);
155
156         //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
157         pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
158 }
159
160 void Item_RespawnCountdown (void)
161 {
162         if(self.count >= ITEM_RESPAWN_TICKS)
163         {
164                 if(self.waypointsprite_attached)
165                         WaypointSprite_Kill(self.waypointsprite_attached);
166                 Item_Respawn();
167         }
168         else
169         {
170                 self.nextthink = time + 1;
171                 self.count += 1;
172                 if(self.count == 1)
173                 {
174                         string name;
175                         vector rgb;
176                         name = string_null;
177                         if(g_minstagib)
178                         {
179                                 switch(self.items)
180                                 {
181                                         case IT_STRENGTH:   name = "item-invis"; rgb = '0 0 1'; break;
182                                         case IT_NAILS:      name = "item-extralife"; rgb = '1 0 0'; break;
183                                         case IT_INVINCIBLE: name = "item-speed"; rgb = '1 0 1'; break;
184                                 }
185                         }
186                         else
187                         {
188                                 switch(self.items)
189                                 {
190                                         case IT_STRENGTH:   name = "item-strength"; rgb = '0 0 1'; break;
191                                         case IT_INVINCIBLE: name = "item-shield"; rgb = '1 0 1'; break;
192                                 }
193                         }
194                         switch(self.items)
195                         {
196                                 case IT_FUEL_REGEN:     name = "item-fuelregen"; rgb = '1 0.5 0'; break;
197                                 case IT_JETPACK:        name = "item-jetpack"; rgb = '0.5 0.5 0.5'; break;
198                         }
199                         if(name)
200                         {
201                                 WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE);
202                                 if(self.waypointsprite_attached)
203                                 {
204                                         WaypointSprite_UpdateTeamRadar(self.waypointsprite_attached, RADARICON_POWERUP, rgb);
205                                         //WaypointSprite_UpdateMaxHealth(self.waypointsprite_attached, ITEM_RESPAWN_TICKS + 1);
206                                         WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, time + ITEM_RESPAWN_TICKS);
207                                 }
208                         }
209                 }
210                 sound (self, CHAN_TRIGGER, "misc/itemrespawncountdown.wav", VOL_BASE, ATTN_NORM);       // play respawn sound
211                 if(self.waypointsprite_attached)
212                 {
213                         WaypointSprite_Ping(self.waypointsprite_attached);
214                         //WaypointSprite_UpdateHealth(self.waypointsprite_attached, self.count);
215                 }
216         }
217 }
218
219 void Item_ScheduleRespawnIn(entity e, float t)
220 {
221         if(e.flags & FL_POWERUP)
222         {
223                 e.think = Item_RespawnCountdown;
224                 e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
225                 e.count = 0;
226         }
227         else
228         {
229                 e.think = Item_Respawn;
230                 e.nextthink = time + t;
231         }
232 }
233
234 void Item_ScheduleRespawn(entity e)
235 {
236         Item_Show(e, 0);
237         Item_ScheduleRespawnIn(e, ITEM_RESPAWNTIME(e));
238 }
239
240 void Item_ScheduleInitialRespawn(entity e)
241 {
242         Item_Show(e, 0);
243         Item_ScheduleRespawnIn(e, game_starttime - time + ITEM_RESPAWNTIME_INITIAL(e));
244 }
245
246 float Item_GiveTo(entity item, entity player)
247 {
248         float _switchweapon;
249         float pickedup;
250         float it;
251         float i;
252         entity e;
253
254         // if nothing happens to player, just return without taking the item
255         pickedup = FALSE;
256         _switchweapon = FALSE;
257
258         if (g_minstagib)
259         {
260                 if(item.spawnshieldtime)
261                 {
262                         if (item.ammo_fuel)
263                         if (player.ammo_fuel < g_pickup_fuel_max)
264                         {
265                                 pickedup = TRUE;
266                                 player.ammo_fuel = min(player.ammo_fuel + item.ammo_fuel, g_pickup_fuel_max);
267                                 player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
268                         }
269                         if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
270                         {
271                                 pickedup = TRUE;
272                                 player.items |= it;
273                                 sprint (player, strcat("You got the ^2", item.netname, "\n"));
274                         }
275
276                         _switchweapon = TRUE;
277                         if (item.ammo_cells)
278                         {
279                                 pickedup = TRUE;
280                                 // play some cool sounds ;)
281                                 centerprint(player, "\n");
282                                 if (clienttype(player) == CLIENTTYPE_REAL)
283                                 {
284                                         if(player.health <= 5)
285                                                 AnnounceTo(player, "lastsecond");
286                                         else if(player.health < 50)
287                                                 AnnounceTo(player, "narrowly");
288                                 }
289                                 // sound not available
290                                 // else if(item.items == IT_CELLS)
291                                 //      AnnounceTo(player, "ammo");
292
293                                 if (item.weapons & WEPBIT_MINSTANEX)
294                                         W_GiveWeapon (player, WEP_MINSTANEX, item.netname);
295                                 if (item.ammo_cells)
296                                         player.ammo_cells = min (player.ammo_cells + cvar("g_minstagib_ammo_drop"), 999);
297                                 player.health = 100;
298                         }
299
300                         // extralife powerup
301                         if (item.max_health)
302                         {
303                                 pickedup = TRUE;
304                                 // sound not available
305                                 // AnnounceTo(player, "_lives");
306                                 player.armorvalue = player.armorvalue + cvar("g_minstagib_extralives");
307                                 sprint(player, "^3You picked up some extra lives\n");
308                         }
309
310                         // invis powerup
311                         if (item.strength_finished)
312                         {
313                                 pickedup = TRUE;
314                                 // sound not available
315                                 // AnnounceTo(player, "invisible");
316                                 player.strength_finished = max(player.strength_finished, time) + cvar("g_balance_powerup_strength_time");
317                         }
318
319                         // speed powerup
320                         if (item.invincible_finished)
321                         {
322                                 pickedup = TRUE;
323                                 // sound not available
324                                 // AnnounceTo(player, "speed");
325                                 player.invincible_finished = max(player.invincible_finished, time) + cvar("g_balance_powerup_strength_time");
326                         }
327
328                         if (item.ammo_fuel)
329                         if (player.ammo_fuel < g_pickup_fuel_max) 
330                         {
331                                 pickedup = TRUE;
332                                 player.ammo_fuel = min(player.ammo_fuel + item.ammo_fuel, g_pickup_fuel_max);
333                                 player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
334                         }
335                 }
336         }
337         else
338         {
339                 if (g_weapon_stay == 1)
340                 if not(item.flags & FL_NO_WEAPON_STAY)
341                 if (item.flags & FL_WEAPON)
342                 {
343                         if(item.classname == "droppedweapon")
344                         {
345                                 if (player.weapons & item.weapons)      // don't let players stack ammo by tossing weapons
346                                         goto skip;
347                         }
348                         else
349                         {
350                                 if (player.weapons & item.weapons)
351                                         goto skip;
352                         }
353                 }
354
355                 // in case the player has autoswitch enabled do the following:
356                 // if the player is using their best weapon before items are given, they
357                 // probably want to switch to an even better weapon after items are given
358                 if (player.autoswitch)
359                 if (player.switchweapon == w_getbestweapon(player))
360                         _switchweapon = TRUE;
361
362                 if not(player.weapons & W_WeaponBit(player.switchweapon))
363                         _switchweapon = TRUE;
364
365                 if(item.spawnshieldtime)
366                 {
367                         if (item.ammo_shells)
368                         if ((player.ammo_shells < g_pickup_shells_max) || item.pickup_anyway)
369                         {
370                                 pickedup = TRUE;
371                                 player.ammo_shells = min (player.ammo_shells + item.ammo_shells, g_pickup_shells_max);
372                         }
373                         if (item.ammo_nails)
374                         if ((player.ammo_nails < g_pickup_nails_max) || item.pickup_anyway)
375                         {
376                                 pickedup = TRUE;
377                                 player.ammo_nails = min (player.ammo_nails + item.ammo_nails, g_pickup_nails_max);
378                         }
379                         if (item.ammo_rockets)
380                         if ((player.ammo_rockets < g_pickup_rockets_max) || item.pickup_anyway)
381                         {
382                                 pickedup = TRUE;
383                                 player.ammo_rockets = min (player.ammo_rockets + item.ammo_rockets, g_pickup_rockets_max);
384                         }
385                         if (item.ammo_cells)
386                         if ((player.ammo_cells < g_pickup_cells_max) || item.pickup_anyway)
387                         {
388                                 pickedup = TRUE;
389                                 player.ammo_cells = min (player.ammo_cells + item.ammo_cells, g_pickup_cells_max);
390                         }
391                         if (item.ammo_fuel)
392                         if ((player.ammo_fuel < g_pickup_fuel_max) || item.pickup_anyway)
393                         {
394                                 pickedup = TRUE;
395                                 player.ammo_fuel = min(player.ammo_fuel + item.ammo_fuel, g_pickup_fuel_max);
396                                 player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + cvar("g_balance_pause_fuel_rot"));
397                         }
398                 }
399
400                 if (item.flags & FL_WEAPON)
401                 if ((it = item.weapons - (item.weapons & player.weapons)) || g_pickup_weapons_anyway)
402                 {
403                         pickedup = TRUE;
404                         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
405                         {
406                                 e = get_weaponinfo(i);
407                                 if(it & e.weapons)
408                                         W_GiveWeapon (player, e.weapon, item.netname);
409                         }
410                 }
411
412                 if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
413                 {
414                         pickedup = TRUE;
415                         player.items |= it;
416                         sprint (player, strcat("You got the ^2", item.netname, "\n"));
417                 }
418
419                 if(item.spawnshieldtime)
420                 {
421                         if (item.strength_finished)
422                         {
423                                 pickedup = TRUE;
424                                 player.strength_finished = max(player.strength_finished, time) + cvar("g_balance_powerup_strength_time");
425                         }
426                         if (item.invincible_finished)
427                         {
428                                 pickedup = TRUE;
429                                 player.invincible_finished = max(player.invincible_finished, time) + cvar("g_balance_powerup_invincible_time");
430                         }
431
432                         if (item.health)
433                         if ((player.health < item.max_health) || item.pickup_anyway)
434                         {
435                                 pickedup = TRUE;
436                                 player.health = min(player.health + item.health, item.max_health);
437                                 player.pauserothealth_finished = max(player.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
438                         }
439                         if (item.armorvalue)
440                         if ((player.armorvalue < item.max_armorvalue) || item.pickup_anyway)
441                         {
442                                 pickedup = TRUE;
443                                 player.armorvalue = min(player.armorvalue + item.armorvalue, item.max_armorvalue);
444                                 player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + cvar("g_balance_pause_armor_rot"));
445                         }
446                 }
447         }
448
449 :skip
450         // always eat teamed entities
451         if(item.team)
452                 pickedup = TRUE;
453
454         if (!pickedup)
455                 return 0;
456
457         sound (player, CHAN_AUTO, item.item_pickupsound, VOL_BASE, ATTN_NORM);
458         if (_switchweapon)
459                 if (player.switchweapon != w_getbestweapon(player))
460                         W_SwitchWeapon_Force(player, w_getbestweapon(player));
461
462         return 1;
463 }
464
465 void Item_Touch (void)
466 {
467         entity e, head;
468
469         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
470         if (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT))
471         {
472                 remove(self);
473                 return;
474         }
475         if (other.classname != "player")
476                 return;
477         if (other.deadflag)
478                 return;
479         if (self.solid != SOLID_TRIGGER)
480                 return;
481         if (self.owner == other)
482                 return;
483
484         if(!Item_GiveTo(self, other))
485                 return;
486
487         pointparticles(particleeffectnum("item_pickup"), self.origin, '0 0 0', 1);
488
489         if (self.classname == "droppedweapon")
490                 remove (self);
491         else if not(self.spawnshieldtime)
492                 return;
493         else if((self.flags & FL_WEAPON) && !(self.flags & FL_NO_WEAPON_STAY) && (g_weapon_stay == 1 || g_weapon_stay == 2))
494                 return;
495         else
496         {
497                 if(self.team)
498                 {
499                         RandomSelection_Init();
500                         for(head = world; (head = findfloat(head, team, self.team)); )
501                         {
502                                 if(head.flags & FL_ITEM)
503                                 {
504                                         Item_Show(head, -1);
505                                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
506                                 }
507                         }
508                         e = RandomSelection_chosen_ent;
509                 }
510                 else
511                         e = self;
512                 Item_ScheduleRespawn(e);
513         }
514 }
515
516 void Item_FindTeam()
517 {
518         entity head, e;
519
520         if(self.effects & EF_NODRAW)
521         {
522                 // marker for item team search
523                 dprint("Initializing item team ", ftos(self.team), "\n");
524                 RandomSelection_Init();
525                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
526                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
527                 e = RandomSelection_chosen_ent;
528                 e.state = 0;
529                 Item_Show(e, 1);
530
531                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
532                 {
533                         if(head != e)
534                         {
535                                 // make it a non-spawned item
536                                 Item_Show(head, -1);
537                                 head.state = 1; // state 1 = initially hidden item
538                         }
539                         head.effects &~= EF_NODRAW;
540                 }
541
542                 if(e.flags & FL_POWERUP) // do not spawn powerups initially!
543                         Item_ScheduleInitialRespawn(e);
544         }
545 }
546
547 void Item_Reset()
548 {
549         Item_Show(self, !self.state);
550         setorigin (self, self.origin);
551         self.think = SUB_Null;
552         self.nextthink = 0;
553
554         if(self.flags & FL_POWERUP) // do not spawn powerups initially!
555                 Item_ScheduleInitialRespawn(self);
556 }
557
558 // Savage: used for item garbage-collection
559 // TODO: perhaps nice special effect?
560 void RemoveItem(void)
561 {
562         remove(self);
563 }
564
565 // pickup evaluation functions
566 // these functions decide how desirable an item is to the bots
567
568 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;};
569
570 float weapon_pickupevalfunc(entity player, entity item)
571 {
572         float c, i, j, position;
573
574         // See if I have it already
575         if(player.weapons & item.weapons == item.weapons)
576         {
577                 // If I can pick it up
578                 if(g_weapon_stay == 1 || g_weapon_stay == 2 || !item.spawnshieldtime)
579                         c = 0;
580                 else if(player.ammo_cells || player.ammo_shells || player.ammo_nails || player.ammo_rockets)
581                 {
582                         // Skilled bots will grab more
583                         c = bound(0, skill / 10, 1) * 0.5;
584                 }
585                 else
586                         c = 0;
587         }
588         else
589                 c = 1;
590
591         // If custom weapon priorities for bots is enabled rate most wanted weapons higher
592         if( bot_custom_weapon && c )
593         {
594                 for(i = WEP_FIRST; i < WEP_LAST ; ++i)
595                 {
596                         // Find weapon
597                         if( (get_weaponinfo(i)).weapons & item.weapons  != item.weapons )
598                                 continue;
599
600                         // Find the highest position on any range
601                         position = -1;
602                         for(j = 0; j < WEP_LAST ; ++j){
603                                 if(
604                                                 bot_weapons_far[j] == i ||
605                                                 bot_weapons_mid[j] == i ||
606                                                 bot_weapons_close[j] == i
607                                   )
608                                 {
609                                         position = j;
610                                         break;
611                                 }
612                         }
613
614                         // Rate it
615                         if (position >= 0 )
616                         {
617                                 position = WEP_LAST - position;
618                                 // item.bot_pickupbasevalue is overwritten here
619                                 return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
620                         }
621                 }
622         }
623
624         return item.bot_pickupbasevalue * c;
625 };
626
627 float commodity_pickupevalfunc(entity player, entity item)
628 {
629         float c, i, need_shells, need_nails, need_rockets, need_cells;
630         entity wi;
631         c = 0;
632
633         // Detect needed ammo
634         for(i = WEP_FIRST; i < WEP_LAST ; ++i)
635         {
636                 wi = get_weaponinfo(i);
637
638                 if not(wi.weapons & player.weapons)
639                         continue;
640
641                 if(wi.items & IT_SHELLS)
642                         need_shells = TRUE;
643                 else if(wi.items & IT_NAILS)
644                         need_nails = TRUE;
645                 else if(wi.items & IT_ROCKETS)
646                         need_rockets = TRUE;
647                 else if(wi.items & IT_CELLS)
648                         need_cells = TRUE;
649         }
650
651         // TODO: figure out if the player even has the weapon this ammo is for?
652         // may not affect strategy much though...
653         // find out how much more ammo/armor/health the player can hold
654         if (need_shells)
655         if (item.ammo_shells)
656         if (player.ammo_shells < g_pickup_shells_max)
657                 c = c + max(0, 1 - player.ammo_shells / g_pickup_shells_max);
658         if (need_nails)
659         if (item.ammo_nails)
660         if (player.ammo_nails < g_pickup_nails_max)
661                 c = c + max(0, 1 - player.ammo_nails / g_pickup_nails_max);
662         if (need_rockets)
663         if (item.ammo_rockets)
664         if (player.ammo_rockets < g_pickup_rockets_max)
665                 c = c + max(0, 1 - player.ammo_rockets / g_pickup_rockets_max);
666         if (need_cells)
667         if (item.ammo_cells)
668         if (player.ammo_cells < g_pickup_cells_max)
669                 c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
670         if (item.armorvalue)
671         if (player.armorvalue < item.max_armorvalue)
672                 c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
673         if (item.health)
674         if (player.health < item.max_health)
675                 c = c + max(0, 1 - player.health / item.max_health);
676
677         return item.bot_pickupbasevalue * c;
678 };
679
680
681 .float is_item;
682 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)
683 {
684         startitem_failed = FALSE;
685
686         self.items = itemid;
687         self.weapons = weaponid;
688
689         // is it a dropped weapon?
690         if (self.classname == "droppedweapon")
691         {
692                 self.reset = SUB_Remove;
693                 // it's a dropped weapon
694                 self.movetype = MOVETYPE_TOSS;
695                 // Savage: remove thrown items after a certain period of time ("garbage collection")
696                 self.think = RemoveItem;
697                 self.nextthink = time + 60;
698                 // don't drop if in a NODROP zone (such as lava)
699                 traceline(self.origin, self.origin, MOVE_NORMAL, self);
700                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
701                 {
702                         startitem_failed = TRUE;
703                         remove(self);
704                         return;
705                 }
706         }
707         else
708         {
709                 if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
710                 {
711                         startitem_failed = TRUE;
712                         remove(self);
713                         return;
714                 }
715
716                 self.r