]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_items.qc
493ca337dc4822c6bb9d5a1a2b0519db2a359d96
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_items.qc
1 #ifdef CSQC
2 void ItemDraw()
3 {
4     if(self.gravity)
5     {
6         Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
7         if(self.move_flags & FL_ONGROUND)
8         { // For some reason move_avelocity gets set to '0 0 0' here ...
9             self.oldorigin = self.origin;
10             self.gravity = 0;
11
12             if(autocvar_cl_animate_items)
13             { // ... so reset it if animations are requested.
14                 if(self.ItemStatus & ITS_ANIMATE1)
15                     self.move_avelocity = '0 180 0';
16
17                 if(self.ItemStatus & ITS_ANIMATE2)
18                     self.move_avelocity = '0 -90 0';
19             }
20         }
21     }
22     else if (autocvar_cl_animate_items)
23     {
24         if(self.ItemStatus & ITS_ANIMATE1)
25         {
26             self.angles += self.move_avelocity * frametime;
27             setorigin(self, '0 0 10' + self.oldorigin + '0 0 8' * sin(time * 2));
28         }
29
30         if(self.ItemStatus & ITS_ANIMATE2)
31         {
32             self.angles += self.move_avelocity * frametime;
33             setorigin(self, '0 0 8' + self.oldorigin + '0 0 4' * sin(time * 3));
34         }
35     }
36 }
37
38 void ItemDrawSimple()
39 {
40     if(self.gravity)
41     {
42         Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
43
44         if(self.move_flags & FL_ONGROUND)
45             self.gravity = 0;
46     }
47 }
48
49 void ItemRead(float _IsNew)
50 {
51     float sf = ReadByte();
52
53     if(sf & ISF_LOCATION)
54     {
55         self.origin_x = ReadCoord();
56         self.origin_y = ReadCoord();
57         self.origin_z = ReadCoord();
58         setorigin(self, self.origin);
59         self.oldorigin = self.origin;
60     }
61
62     if(sf & ISF_ANGLES)
63     {
64         self.angles_x = ReadCoord();
65         self.angles_y = ReadCoord();
66         self.angles_z = ReadCoord();
67         self.move_angles = self.angles;
68     }
69
70     if(sf & ISF_STATUS) // need to read/write status frist so model can handle simple, fb etc.
71     {
72         self.ItemStatus = ReadByte();
73
74         if(self.ItemStatus & ITS_AVAILABLE)
75         {
76             self.alpha = 1;
77             self.colormod = self.glowmod = '1 1 1';
78         }
79         else
80         {
81             if (autocvar_cl_ghost_items_color)
82             {
83                 self.alpha = autocvar_cl_ghost_items;
84                 self.colormod = self.glowmod = autocvar_cl_ghost_items_color;
85             }
86             else
87                 self.alpha = -1;
88         }
89
90         if(autocvar_cl_fullbright_items)
91             if(self.ItemStatus & ITS_ALLOWFB)
92                 self.effects |= EF_FULLBRIGHT;
93
94         if(self.ItemStatus & ITS_STAYWEP)
95         {
96             self.colormod = self.glowmod = autocvar_cl_weapon_stay_color;
97             self.alpha = autocvar_cl_weapon_stay_alpha;
98
99         }
100
101         if(self.ItemStatus & ITS_POWERUP)
102         {
103             if(self.ItemStatus & ITS_AVAILABLE)
104                 self.effects |= (EF_ADDITIVE | EF_FULLBRIGHT);
105             else
106                  self.effects &= ~(EF_ADDITIVE | EF_FULLBRIGHT);
107         }
108     }
109
110     if(sf & ISF_MODEL)
111     {
112         self.drawmask  = MASK_NORMAL;
113         self.movetype  = MOVETYPE_NOCLIP;
114         self.draw       = ItemDraw;
115
116         if(self.mdl)
117             strunzone(self.mdl);
118
119         self.mdl = "";
120         string _fn = ReadString();
121
122         if(autocvar_cl_simple_items && (self.ItemStatus & ITS_ALLOWSI))
123         {
124             string _fn2 = substring(_fn, 0 , strlen(_fn) -4);
125             self.draw = ItemDrawSimple;
126
127
128
129             if(fexists(sprintf("%s%s.md3", _fn2, autocvr_cl_simpleitems_postfix)))
130                 self.mdl = strzone(sprintf("%s%s.md3", _fn2, autocvr_cl_simpleitems_postfix));
131             else if(fexists(sprintf("%s%s.dpm", _fn2, autocvr_cl_simpleitems_postfix)))
132                 self.mdl = strzone(sprintf("%s%s.dpm", _fn2, autocvr_cl_simpleitems_postfix));
133             else if(fexists(sprintf("%s%s.iqm", _fn2, autocvr_cl_simpleitems_postfix)))
134                 self.mdl = strzone(sprintf("%s%s.iqm", _fn2, autocvr_cl_simpleitems_postfix));
135             else if(fexists(sprintf("%s%s.obj", _fn2, autocvr_cl_simpleitems_postfix)))
136                 self.mdl = strzone(sprintf("%s%s.obj", _fn2, autocvr_cl_simpleitems_postfix));
137             else
138             {
139                 self.draw = ItemDraw;
140                 dprint("Simple item requested for ", _fn, " but no model exsist for it\n");
141             }
142         }
143
144         if(self.draw != ItemDrawSimple)
145             self.mdl = strzone(_fn);
146
147
148         if(self.mdl == "")
149             dprint("^1WARNING!^7 self.mdl is unset for item ", self.classname, " tell tZork aboute this!\n");
150
151         precache_model(self.mdl);
152         setmodel(self, self.mdl);
153     }
154
155     if(sf & ISF_COLORMAP)
156         self.colormap = ReadShort();
157
158     if(sf & ISF_DROP)
159     {
160         self.gravity = 1;
161         self.move_angles = '0 0 0';
162         self.move_movetype = MOVETYPE_TOSS;
163         self.move_velocity_x = ReadCoord();
164         self.move_velocity_y = ReadCoord();
165         self.move_velocity_z = ReadCoord();
166         self.velocity = self.move_velocity;
167         self.move_origin = self.oldorigin;
168
169         if(!self.move_time)
170         {
171             self.move_time = time;
172             self.spawntime = time;
173         }
174         else
175             self.move_time = max(self.move_time, time);
176     }
177
178     if(autocvar_cl_animate_items)
179     {
180         if(self.ItemStatus & ITS_ANIMATE1)
181             self.move_avelocity = '0 180 0';
182
183         if(self.ItemStatus & ITS_ANIMATE2)
184             self.move_avelocity = '0 -90 0';
185     }
186 }
187
188 #endif
189
190 #ifdef SVQC
191 float ItemSend(entity to, float sf)
192 {
193     if(self.gravity)
194         sf |= ISF_DROP;
195     else
196         sf &= ~ISF_DROP;
197
198         WriteByte(MSG_ENTITY, ENT_CLIENT_ITEM);
199         WriteByte(MSG_ENTITY, sf);
200
201         //WriteByte(MSG_ENTITY, self.cnt);
202     if(sf & ISF_LOCATION)
203     {
204         WriteCoord(MSG_ENTITY, self.origin_x);
205         WriteCoord(MSG_ENTITY, self.origin_y);
206         WriteCoord(MSG_ENTITY, self.origin_z);
207     }
208
209     if(sf & ISF_ANGLES)
210     {
211         WriteCoord(MSG_ENTITY, self.angles_x);
212         WriteCoord(MSG_ENTITY, self.angles_y);
213         WriteCoord(MSG_ENTITY, self.angles_z);
214     }
215
216     if(sf & ISF_STATUS)
217         WriteByte(MSG_ENTITY, self.ItemStatus);
218
219     if(sf & ISF_MODEL)
220     {
221
222         if(self.mdl == "")
223             dprint("^1WARNING!^7 self.mdl is unset for item ", self.classname, "exspect a crash just aboute now\n");
224
225         WriteString(MSG_ENTITY, self.mdl);
226     }
227
228
229     if(sf & ISF_COLORMAP)
230         WriteShort(MSG_ENTITY, self.colormap);
231
232     if(sf & ISF_DROP)
233     {
234         WriteCoord(MSG_ENTITY, self.velocity_x);
235         WriteCoord(MSG_ENTITY, self.velocity_y);
236         WriteCoord(MSG_ENTITY, self.velocity_z);
237     }
238
239     return TRUE;
240 }
241
242 void ItemUpdate(entity item)
243 {
244         item.SendFlags |= ISF_LOCATION;
245 }
246
247 float have_pickup_item(void)
248 {
249         if(self.flags & FL_POWERUP)
250         {
251                 if(autocvar_g_powerups > 0)
252                         return TRUE;
253                 if(autocvar_g_powerups == 0)
254                         return FALSE;
255         }
256         else
257         {
258                 if(autocvar_g_pickup_items > 0)
259                         return TRUE;
260                 if(autocvar_g_pickup_items == 0)
261                         return FALSE;
262                 if(g_weaponarena)
263                         if(self.weapons || (self.items & IT_AMMO))
264                                 return FALSE;
265         }
266         return TRUE;
267 }
268
269 floatfield Item_CounterField(float it)
270 {
271         switch(it)
272         {
273                 case IT_SHELLS:      return ammo_shells;
274                 case IT_NAILS:       return ammo_nails;
275                 case IT_ROCKETS:     return ammo_rockets;
276                 case IT_CELLS:       return ammo_cells;
277                 case IT_FUEL:        return ammo_fuel;
278                 case IT_5HP:         return health;
279                 case IT_25HP:        return health;
280                 case IT_HEALTH:      return health;
281                 case IT_ARMOR_SHARD: return armorvalue;
282                 case IT_ARMOR:       return armorvalue;
283                 // add more things here (health, armor)
284                 default:             error("requested item has no counter field");
285         }
286 #ifdef GMQCC
287         // should never happen
288         return health;
289 #endif
290 }
291
292 string Item_CounterFieldName(float it)
293 {
294         switch(it)
295         {
296                 case IT_SHELLS:      return "shells";
297                 case IT_NAILS:       return "nails";
298                 case IT_ROCKETS:     return "rockets";
299                 case IT_CELLS:       return "cells";
300                 case IT_FUEL:        return "fuel";
301
302                 // add more things here (health, armor)
303                 default:             error("requested item has no counter field name");
304         }
305 #ifdef GMQCC
306         // should never happen
307         return string_null;
308 #endif
309 }
310
311 /*
312 float Item_Customize()
313 {
314         if(self.spawnshieldtime)
315                 return TRUE;
316         if(self.weapons & ~other.weapons)
317         {
318                 self.colormod = '0 0 0';
319                 self.glowmod = self.colormod;
320                 self.alpha = 0.5 + 0.5 * g_ghost_items; // halfway more alpha
321                 return TRUE;
322         }
323         else
324         {
325                 if(g_ghost_items)
326                 {
327                         self.colormod = stov(autocvar_g_ghost_items_color);
328                         self.glowmod = self.colormod;
329                         self.alpha = g_ghost_items;
330                         return TRUE;
331                 }
332                 else
333                         return FALSE;
334         }
335 }
336 */
337
338 void Item_Show (entity e, float mode)
339 {
340         e.effects &= ~(EF_ADDITIVE | EF_STARDUST | EF_FULLBRIGHT | EF_NODEPTHTEST);
341         e.ItemStatus &= ~ITS_STAYWEP;
342         if (mode > 0)
343         {
344                 // make the item look normal, and be touchable
345                 e.model = e.mdl;
346                 e.solid = SOLID_TRIGGER;
347                 e.spawnshieldtime = 1;
348                 e.ItemStatus |= ITS_AVAILABLE;
349         }
350         else if (mode < 0)
351         {
352                 // hide the item completely
353                 e.model = string_null;
354                 e.solid = SOLID_NOT;
355                 e.spawnshieldtime = 1;
356                 e.ItemStatus &= ~ITS_AVAILABLE;
357         }
358         else if((e.flags & FL_WEAPON) && !(e.flags & FL_NO_WEAPON_STAY) && g_weapon_stay)
359         {
360                 // make the item translucent and not touchable
361                 e.model = e.mdl;
362                 e.solid = SOLID_TRIGGER; // can STILL be picked up!
363                 e.effects |= EF_STARDUST;
364                 e.spawnshieldtime = 0; // field indicates whether picking it up may give you anything other than the weapon
365                 e.ItemStatus |= (ITS_AVAILABLE | ITS_STAYWEP);
366         }
367         else
368         {
369                 //setmodel(e, "null");
370                 e.solid = SOLID_NOT;
371                 e.colormod = '0 0 0';
372                 e.glowmod = e.colormod;
373                 e.spawnshieldtime = 1;
374                 e.ItemStatus &= ~ITS_AVAILABLE;
375         }
376
377         if (e.items & IT_STRENGTH || e.items & IT_INVINCIBLE)
378             e.ItemStatus |= ITS_POWERUP;
379
380         if (autocvar_g_nodepthtestitems)
381                 e.effects |= EF_NODEPTHTEST;
382
383
384     if (autocvar_g_fullbrightitems)
385                 e.ItemStatus |= ITS_ALLOWFB;
386
387         if (autocvar_sv_simple_items)
388         e.ItemStatus |= ITS_ALLOWSI;
389
390         // relink entity (because solid may have changed)
391         setorigin(e, e.origin);
392     e.SendFlags |= ISF_STATUS;
393 }
394
395 void Item_Respawn (void)
396 {
397         Item_Show(self, 1);
398         // this is ugly...
399         if(self.items == IT_STRENGTH)
400                 sound (self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTEN_NORM);    // play respawn sound
401         else if(self.items == IT_INVINCIBLE)
402                 sound (self, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTEN_NORM);      // play respawn sound
403         else
404                 sound (self, CH_TRIGGER, "misc/itemrespawn.wav", VOL_BASE, ATTEN_NORM); // play respawn sound
405         setorigin (self, self.origin);
406
407         //pointparticles(particleeffectnum("item_respawn"), self.origin + self.mins_z * '0 0 1' + '0 0 48', '0 0 0', 1);
408         pointparticles(particleeffectnum("item_respawn"), self.origin + 0.5 * (self.mins + self.maxs), '0 0 0', 1);
409 }
410
411 void Item_RespawnCountdown (void)
412 {
413         if(self.count >= ITEM_RESPAWN_TICKS)
414         {
415                 if(self.waypointsprite_attached)
416                         WaypointSprite_Kill(self.waypointsprite_attached);
417                 Item_Respawn();
418         }
419         else
420         {
421                 self.nextthink = time + 1;
422                 self.count += 1;
423                 if(self.count == 1)
424                 {
425                         string name;
426                         vector rgb = '1 0 1';
427                         name = string_null;
428                         switch(self.items)
429                         {
430                                 case IT_FUEL_REGEN: name = "item-fuelregen"; rgb = '1 0.5 0'; break;
431                                 case IT_JETPACK:    name = "item-jetpack"; rgb = '0.5 0.5 0.5'; break;
432                                 case IT_STRENGTH:   name = "item-strength"; rgb = '0 0 1'; break;
433                                 case IT_INVINCIBLE: name = "item-shield"; rgb = '1 0 1'; break;
434                         }
435                         item_name = name;
436                         item_color = rgb;
437                         MUTATOR_CALLHOOK(Item_RespawnCountdown);
438                         name = item_name;
439                         rgb = item_color;
440                         if(self.flags & FL_WEAPON)
441                         {
442                                 entity wi = get_weaponinfo(self.weapon);
443                                 if(wi)
444                                 {
445                                         name = wi.model2;
446                                         rgb = '1 0 0';
447                                 }
448                         }
449                         if(name)
450                         {
451                                 WaypointSprite_Spawn(name, 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb);
452                                 if(self.waypointsprite_attached)
453                                         WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, time + ITEM_RESPAWN_TICKS);
454                         }
455                         else
456                         {
457                                 print("Unknown powerup-marked item is wanting to respawn\n");
458                                 localcmd(sprintf("prvm_edict server %d\n", num_for_edict(self)));
459                         }
460                 }
461                 sound (self, CH_TRIGGER, "misc/itemrespawncountdown.wav", VOL_BASE, ATTEN_NORM);        // play respawn sound
462                 if(self.waypointsprite_attached)
463                 {
464                         WaypointSprite_Ping(self.waypointsprite_attached);
465                         //WaypointSprite_UpdateHealth(self.waypointsprite_attached, self.count);
466                 }
467         }
468 }
469
470 void Item_ScheduleRespawnIn(entity e, float t)
471 {
472         if((e.flags & FL_POWERUP) || (e.weapons & WEPSET_SUPERWEAPONS))
473         {
474                 e.think = Item_RespawnCountdown;
475                 e.nextthink = time + max(0, t - ITEM_RESPAWN_TICKS);
476                 e.count = 0;
477         }
478         else
479         {
480                 e.think = Item_Respawn;
481                 e.nextthink = time + t;
482         }
483 }
484
485 void Item_ScheduleRespawn(entity e)
486 {
487         if(e.respawntime > 0)
488         {
489                 Item_Show(e, 0);
490                 Item_ScheduleRespawnIn(e, ITEM_RESPAWNTIME(e));
491         }
492         else // if respawntime is -1, this item does not respawn
493                 Item_Show(e, -1);
494 }
495
496 void Item_ScheduleInitialRespawn(entity e)
497 {
498         Item_Show(e, 0);
499         Item_ScheduleRespawnIn(e, game_starttime - time + ITEM_RESPAWNTIME_INITIAL(e));
500 }
501
502 float Item_GiveAmmoTo(entity item, entity player, .float ammofield, float ammomax, float mode)
503 {
504         if (!item.ammofield)
505                 return FALSE;
506
507         if (item.spawnshieldtime)
508         {
509                 if ((player.ammofield < ammomax) || item.pickup_anyway)
510                 {
511                         player.ammofield = bound(player.ammofield, ammomax, player.ammofield + item.ammofield);
512                         goto YEAH;
513                 }
514         }
515         else if(g_weapon_stay == 2)
516         {
517                 float mi = min(item.ammofield, ammomax);
518                 if (player.ammofield < mi)
519                 {
520                         player.ammofield = mi;
521                         goto YEAH;
522                 }
523         }
524
525         return FALSE;
526
527 :YEAH
528         switch(mode)
529         {
530                 case ITEM_MODE_FUEL:
531                         player.pauserotfuel_finished = max(player.pauserotfuel_finished, time + autocvar_g_balance_pause_fuel_rot);
532                         break;
533                 case ITEM_MODE_HEALTH:
534                         player.pauserothealth_finished = max(player.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
535                         break;
536                 case ITEM_MODE_ARMOR:
537                         player.pauserotarmor_finished = max(player.pauserotarmor_finished, time + autocvar_g_balance_pause_armor_rot);
538                         break;
539                 default:
540                         break;
541         }
542         return TRUE;
543 }
544
545 float Item_GiveTo(entity item, entity player)
546 {
547         float _switchweapon;
548         float pickedup;
549         float it;
550         float i;
551
552         // if nothing happens to player, just return without taking the item
553         pickedup = FALSE;
554         _switchweapon = FALSE;
555         // in case the player has autoswitch enabled do the following:
556         // if the player is using their best weapon before items are given, they
557         // probably want to switch to an even better weapon after items are given
558         if (player.autoswitch)
559         if (player.switchweapon == w_getbestweapon(player))
560                 _switchweapon = TRUE;
561
562         if (!(player.weapons & WepSet_FromWeapon(player.switchweapon)))
563                 _switchweapon = TRUE;
564
565         pickedup |= Item_GiveAmmoTo(item, player, ammo_fuel, g_pickup_fuel_max, ITEM_MODE_FUEL);
566         pickedup |= Item_GiveAmmoTo(item, player, ammo_shells, g_pickup_shells_max, ITEM_MODE_NONE);
567         pickedup |= Item_GiveAmmoTo(item, player, ammo_nails, g_pickup_nails_max, ITEM_MODE_NONE);
568         pickedup |= Item_GiveAmmoTo(item, player, ammo_rockets, g_pickup_rockets_max, ITEM_MODE_NONE);
569         pickedup |= Item_GiveAmmoTo(item, player, ammo_cells, g_pickup_cells_max, ITEM_MODE_NONE);
570         pickedup |= Item_GiveAmmoTo(item, player, health, item.max_health, ITEM_MODE_HEALTH);
571         pickedup |= Item_GiveAmmoTo(item, player, armorvalue, item.max_armorvalue, ITEM_MODE_ARMOR);
572
573         if (item.flags & FL_WEAPON)
574         {
575                 WepSet it;
576                 it = item.weapons;
577                 it &= ~player.weapons;
578
579                 if (it || (item.spawnshieldtime && item.pickup_anyway))
580                 {
581                         pickedup = TRUE;
582                         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
583                         if(it & WepSet_FromWeapon(i))
584                                 W_GiveWeapon(player, i);
585                 }
586         }
587
588         if((it = (item.items - (item.items & player.items)) & IT_PICKUPMASK))
589         {
590                 pickedup = TRUE;
591                 player.items |= it;
592                 sprint (player, strcat("You got the ^2", item.netname, "\n"));
593         }
594
595         if (item.strength_finished)
596         {
597                 pickedup = TRUE;
598                 player.strength_finished = max(player.strength_finished, time) + item.strength_finished;
599         }
600         if (item.invincible_finished)
601         {
602                 pickedup = TRUE;
603                 player.invincible_finished = max(player.invincible_finished, time) + item.invincible_finished;
604         }
605         if (item.superweapons_finished)
606         {
607                 pickedup = TRUE;
608                 player.superweapons_finished = max(player.superweapons_finished, time) + item.superweapons_finished;
609         }
610
611 :skip
612
613         // always eat teamed entities
614         if(item.team)
615                 pickedup = TRUE;
616
617         if (!pickedup)
618                 return 0;
619
620         if (_switchweapon)
621                 if (player.switchweapon != w_getbestweapon(player))
622                         W_SwitchWeapon_Force(player, w_getbestweapon(player));
623
624         return 1;
625 }
626
627 void Item_Touch (void)
628 {
629         entity e, head;
630
631         // remove the item if it's currnetly in a NODROP brush or hits a NOIMPACT surface (such as sky)
632         if(self.classname == "droppedweapon")
633         {
634                 if (ITEM_TOUCH_NEEDKILL())
635                 {
636                         remove(self);
637                         return;
638                 }
639         }
640
641         if (!IS_PLAYER(other))
642                 return;
643         if (other.deadflag)
644                 return;
645         if (self.solid != SOLID_TRIGGER)
646                 return;
647         if (self.owner == other)
648                 return;
649
650         switch(MUTATOR_CALLHOOK(ItemTouch))
651         {
652                 case MUT_ITEMTOUCH_RETURN: { return; }
653                 case MUT_ITEMTOUCH_PICKUP: { goto pickup; }
654         }
655
656         if (self.classname == "droppedweapon")
657         {
658                 self.strength_finished = max(0, self.strength_finished - time);
659                 self.invincible_finished = max(0, self.invincible_finished - time);
660                 self.superweapons_finished = max(0, self.superweapons_finished - time);
661         }
662
663         if(!Item_GiveTo(self, other))
664         {
665                 if (self.classname == "droppedweapon")
666                 {
667                         // undo what we did above
668                         self.strength_finished += time;
669                         self.invincible_finished += time;
670                         self.superweapons_finished += time;
671                 }
672                 return;
673         }
674
675         :pickup
676
677         other.last_pickup = time;
678
679         pointparticles(particleeffectnum("item_pickup"), self.origin, '0 0 0', 1);
680         sound (other, CH_TRIGGER, self.item_pickupsound, VOL_BASE, ATTEN_NORM);
681
682         if (self.classname == "droppedweapon")
683                 remove (self);
684         else if (!self.spawnshieldtime)
685                 return;
686         else
687         {
688                 if(self.team)
689                 {
690                         RandomSelection_Init();
691                         for(head = world; (head = findfloat(head, team, self.team)); )
692                         {
693                                 if(head.flags & FL_ITEM)
694                                 {
695                                         Item_Show(head, -1);
696                                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
697                                 }
698                         }
699                         e = RandomSelection_chosen_ent;
700
701                 }
702                 else
703                         e = self;
704                 Item_ScheduleRespawn(e);
705         }
706 }
707
708 void Item_Reset()
709 {
710         Item_Show(self, !self.state);
711         setorigin (self, self.origin);
712
713         if(self.classname != "droppedweapon")
714         {
715                 self.think = func_null;
716                 self.nextthink = 0;
717
718                 if(self.waypointsprite_attached)
719                         WaypointSprite_Kill(self.waypointsprite_attached);
720
721                 if((self.flags & FL_POWERUP) || (self.weapons & WEPSET_SUPERWEAPONS)) // do not spawn powerups initially!
722                         Item_ScheduleInitialRespawn(self);
723         }
724 }
725
726 void Item_FindTeam()
727 {
728         entity head, e;
729
730         if(self.effects & EF_NODRAW)
731         {
732                 // marker for item team search
733                 dprint("Initializing item team ", ftos(self.team), "\n");
734                 RandomSelection_Init();
735                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
736                         RandomSelection_Add(head, 0, string_null, head.cnt, 0);
737                 e = RandomSelection_chosen_ent;
738                 e.state = 0;
739                 Item_Show(e, 1);
740
741                 for(head = world; (head = findfloat(head, team, self.team)); ) if(head.flags & FL_ITEM)
742                 {
743                         if(head != e)
744                         {
745                                 // make it a non-spawned item
746                                 Item_Show(head, -1);
747                                 head.state = 1; // state 1 = initially hidden item
748                         }
749                         head.effects &= ~EF_NODRAW;
750                 }
751
752                 Item_Reset();
753         }
754 }
755
756 // Savage: used for item garbage-collection
757 // TODO: perhaps nice special effect?
758 void RemoveItem(void)
759 {
760         remove(self);
761 }
762
763 // pickup evaluation functions
764 // these functions decide how desirable an item is to the bots
765
766 float generic_pickupevalfunc(entity player, entity item) {return item.bot_pickupbasevalue;}
767
768 float weapon_pickupevalfunc(entity player, entity item)
769 {
770         float c, j, position;
771
772         // See if I have it already
773         if(item.weapons & ~player.weapons)
774         {
775                 // If I can pick it up
776                 if(!item.spawnshieldtime)
777                         c = 0;
778                 else if(player.ammo_cells || player.ammo_shells || player.ammo_nails || player.ammo_rockets)
779                 {
780                         // Skilled bots will grab more
781                         c = bound(0, skill / 10, 1) * 0.5;
782                 }
783                 else
784                         c = 0;
785         }
786         else
787                 c = 1;
788
789         // If custom weapon priorities for bots is enabled rate most wanted weapons higher
790         if( bot_custom_weapon && c )
791         {
792                 // Find the highest position on any range
793                 position = -1;
794                 for(j = 0; j < WEP_LAST ; ++j){
795                         if(
796                                         bot_weapons_far[j] == item.weapon ||
797                                         bot_weapons_mid[j] == item.weapon ||
798                                         bot_weapons_close[j] == item.weapon
799                           )
800                         {
801                                 position = j;
802                                 break;
803                         }
804                 }
805
806                 // Rate it
807                 if (position >= 0 )
808                 {
809                         position = WEP_LAST - position;
810                         // item.bot_pickupbasevalue is overwritten here
811                         return (BOT_PICKUP_RATING_LOW + ( (BOT_PICKUP_RATING_HIGH - BOT_PICKUP_RATING_LOW) * (position / WEP_LAST ))) * c;
812                 }
813         }
814
815         return item.bot_pickupbasevalue * c;
816 }
817
818 float commodity_pickupevalfunc(entity player, entity item)
819 {
820         float c, i;
821         float need_shells = FALSE, need_nails = FALSE, need_rockets = FALSE, need_cells = FALSE, need_fuel = FALSE;
822         entity wi;
823         c = 0;
824
825         // Detect needed ammo
826         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
827         {
828                 wi = get_weaponinfo(i);
829
830                 if (!(player.weapons & WepSet_FromWeapon(i)))
831                         continue;
832
833                 if(wi.items & IT_SHELLS)
834                         need_shells = TRUE;
835                 else if(wi.items & IT_NAILS)
836                         need_nails = TRUE;
837                 else if(wi.items & IT_ROCKETS)
838                         need_rockets = TRUE;
839                 else if(wi.items & IT_CELLS)
840                         need_cells = TRUE;
841                 else if(wi.items & IT_FUEL)
842                         need_cells = TRUE;
843         }
844
845         // TODO: figure out if the player even has the weapon this ammo is for?
846         // may not affect strategy much though...
847         // find out how much more ammo/armor/health the player can hold
848         if (need_shells)
849         if (item.ammo_shells)
850         if (player.ammo_shells < g_pickup_shells_max)
851                 c = c + max(0, 1 - player.ammo_shells / g_pickup_shells_max);
852         if (need_nails)
853         if (item.ammo_nails)
854         if (player.ammo_nails < g_pickup_nails_max)
855                 c = c + max(0, 1 - player.ammo_nails / g_pickup_nails_max);
856         if (need_rockets)
857         if (item.ammo_rockets)
858         if (player.ammo_rockets < g_pickup_rockets_max)
859                 c = c + max(0, 1 - player.ammo_rockets / g_pickup_rockets_max);
860         if (need_cells)
861         if (item.ammo_cells)
862         if (player.ammo_cells < g_pickup_cells_max)
863                 c = c + max(0, 1 - player.ammo_cells / g_pickup_cells_max);
864         if (need_fuel)
865         if (item.ammo_fuel)
866         if (player.ammo_fuel < g_pickup_fuel_max)
867                 c = c + max(0, 1 - player.ammo_fuel / g_pickup_fuel_max);
868         if (item.armorvalue)
869         if (player.armorvalue < item.max_armorvalue)
870                 c = c + max(0, 1 - player.armorvalue / item.max_armorvalue);
871         if (item.health)
872         if (player.health < item.max_health)
873                 c = c + max(0, 1 - player.health / item.max_health);
874
875         return item.bot_pickupbasevalue * c;
876 }
877
878 void Item_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
879 {
880         if(ITEM_DAMAGE_NEEDKILL(deathtype))
881                 RemoveItem();
882 }
883
884 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)
885 {
886         startitem_failed = FALSE;
887
888         if(self.model == "")
889                 self.model = itemmodel;
890
891         if(self.model == "")
892     {
893         error(strcat("^1Tried to spawn ", itemname, " with no model!\n"));
894         return;
895     }
896
897         if(self.item_pickupsound == "")
898                 self.item_pickupsound = pickupsound;
899
900         if(!self.respawntime) // both need to be set
901         {
902                 self.respawntime = defaultrespawntime;
903                 self.respawntimejitter = defaultrespawntimejitter;
904         }
905
906         self.items = itemid;
907         self.weapon = weaponid;
908
909         if(weaponid)
910                 self.weapons = WepSet_FromWeapon(weaponid);
911
912         self.flags = FL_ITEM | itemflags;
913
914         if(MUTATOR_CALLHOOK(FilterItem)) // error means we do not want the item
915         {
916                 startitem_failed = TRUE;
917                 remove(self);
918                 return;
919         }
920
921         // is it a dropped weapon?
922         if (self.classname == "droppedweapon")
923         {
924                 self.reset = SUB_Remove;
925                 // it's a dropped weapon
926                 self.movetype = MOVETYPE_TOSS;
927
928                 // Savage: remove thrown items after a certain period of time ("garbage collection")
929                 self.think = RemoveItem;
930                 self.nextthink = time + 20;
931
932                 self.takedamage = DAMAGE_YES;
933                 self.event_damage = Item_Damage;
934
935                 if(self.strength_finished || self.invincible_finished || self.superweapons_finished)
936                 /*
937                 if(self.items == 0)
938                 if(!(self.weapons & ~WEPSET_SUPERWEAPONS)) // only superweapons
939                 if(self.ammo_nails == 0)
940                 if(self.ammo_cells == 0)
941                 if(self.ammo_rockets == 0)
942                 if(self.ammo_shells == 0)
943                 if(self.ammo_fuel == 0)
944                 if(self.health == 0)
945                 if(self.armorvalue == 0)
946                 */
947                 {
948                         // if item is worthless after a timer, have it expire then
949                         self.nextthink = max(self.strength_finished, self.invincible_finished, self.superweapons_finished);
950                 }
951
952                 // don't drop if in a NODROP zone (such as lava)
953                 traceline(self.origin, self.origin, MOVE_NORMAL, self);
954                 if (trace_dpstartcontents & DPCONTENTS_NODROP)
955                 {
956                         startitem_failed = TRUE;
957                         remove(self);
958                         return;
959                 }
960         }
961         else
962         {
963                 if(!have_pickup_item())
964                 {
965                         startitem_failed = TRUE;
966                         remove (self);
967                         return;
968                 }
969
970                 if(self.angles != '0 0 0')
971             self.SendFlags |= ISF_ANGLES;
972
973                 self.reset = Item_Reset;
974                 // it's a level item
975                 if(self.spawnflags & 1)
976                         self.noalign = 1;
977                 if (self.noalign)
978                         self.movetype = MOVETYPE_NONE;
979                 else
980                         self.movetype = MOVETYPE_TOSS;
981                 // do item filtering according to game mode and other things
982                 if (!self.noalign)
983                 {
984                         // first nudge it off the floor a little bit to avoid math errors
985                         setorigin(self, self.origin + '0 0 1');
986                         // set item size before we spawn a spawnfunc_waypoint
987                         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
988                                 setsize (self, '-16 -16 0', '16 16 48');
989                         else
990                                 setsize (self, '-16 -16 0', '16 16 32');
991                         // note droptofloor returns FALSE if stuck/or would fall too far
992                         droptofloor();
993                         waypoint_spawnforitem(self);
994                 }
995
996                 /*
997                  * can't do it that way, as it would break maps
998                  * TODO make a target_give like entity another way, that perhaps has
999                  * the weapon name in a key
1000                 if(self.targetname)
1001                 {
1002                         // target_give not yet supported; maybe later
1003                         print("removed targeted ", self.classname, "\n");
1004                         startitem_failed = TRUE;
1005                         remove (self);
1006                         return;
1007                 }
1008                 */
1009
1010                 if(autocvar_spawn_debug >= 2)
1011                 {
1012                         entity otheritem;
1013                         for(otheritem = findradius(self.origin, 3); otheritem; otheritem = otheritem.chain)
1014                         {
1015                             // why not flags & fl_item?
1016                                 if(otheritem.is_item)
1017                                 {
1018                                         dprint("XXX Found duplicated item: ", itemname, vtos(self.origin));
1019                                         dprint(" vs ", otheritem.netname, vtos(otheritem.origin), "\n");
1020                                         error("Mapper sucks.");
1021                                 }
1022                         }
1023                         self.is_item = TRUE;
1024                 }
1025
1026                 weaponsInMap |= WepSet_FromWeapon(weaponid);
1027
1028                 precache_model (self.model);
1029                 precache_sound (self.item_pickupsound);
1030
1031                 precache_sound ("misc/itemrespawncountdown.wav");
1032                 if(itemid == IT_STRENGTH)
1033                         precache_sound ("misc/strength_respawn.wav");
1034                 else if(itemid == IT_INVINCIBLE)
1035                         precache_sound ("misc/shield_respawn.wav");
1036                 else
1037                         precache_sound ("misc/itemrespawn.wav");
1038
1039                 if((itemflags & (FL_POWERUP | FL_WEAPON)) || (itemid & (IT_HEALTH | IT_ARMOR | IT_KEY1 | IT_KEY2)))
1040                         self.target = "###item###"; // for finding the nearest item using find()
1041         }
1042
1043         self.bot_pickup = TRUE;
1044         self.bot_pickupevalfunc = pickupevalfunc;
1045         self.bot_pickupbasevalue = pickupbasevalue;
1046         self.mdl = self.model;
1047         self.netname = itemname;
1048         self.touch = Item_Touch;
1049         setmodel(self, "null"); // precision set below
1050         //self.effects |= EF_LOWPRECISION;
1051
1052         if((itemflags & FL_POWERUP) || self.health || self.armorvalue)
1053     {
1054         self.pos1 = '-16 -16 0';
1055         self.pos2 = '16 16 48';
1056     }
1057         else
1058     {
1059         self.pos1 = '-16 -16 0';
1060         self.pos2 = '16 16 32';
1061     }
1062     setsize (self, self.pos1, self.pos2);
1063
1064     if(itemflags & FL_POWERUP)
1065         self.ItemStatus |= ITS_ANIMATE1;
1066
1067         if(self.armorvalue || self.health)
1068         self.ItemStatus |= ITS_ANIMATE2;
1069
1070         if(itemflags & FL_WEAPON)
1071         {
1072                 if (self.classname != "droppedweapon") // if dropped, colormap is already set up nicely
1073             self.colormap = 1024; // color shirt=0 pants=0 grey
1074         else
1075             self.gravity = 1;
1076
1077                 self.ItemStatus |= ITS_ANIMATE1;
1078                 self.ItemStatus |= ISF_COLORMAP;
1079         }
1080
1081         self.state = 0;
1082         if(self.team) // broken, no idea why.
1083         {
1084                 if(!self.cnt)
1085                         self.cnt = 1; // item probability weight
1086
1087                 self.effects |= EF_NODRAW; // marker for item team search
1088                 InitializeEntity(self, Item_FindTeam, INITPRIO_FINDTARGET);
1089         }
1090         else
1091                 Item_Reset();
1092
1093     Net_LinkEntity(self, FALSE, 0, ItemSend);
1094
1095         // call this hook after everything else has been done
1096         if(MUTATOR_CALLHOOK(Item_Spawn))
1097         {
1098                 startitem_failed = TRUE;
1099                 remove(self);
1100                 return;
1101         }
1102 }
1103 void spawnfunc_item_rockets (void) {
1104         if(!self.ammo_rockets)
1105                 self.ammo_rockets = g_pickup_rockets;
1106         if(!self.pickup_anyway)
1107                 self.pickup_anyway = g_pickup_ammo_anyway;
1108         StartItem ("models/items/a_rockets.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "rockets", IT_ROCKETS, 0, 0, commodity_pickupevalfunc, 3000);
1109 }
1110
1111 void spawnfunc_item_shells (void);
1112 void spawnfunc_item_bullets (void) {
1113         if(!weaponswapping)
1114         if(autocvar_sv_q3acompat_machineshotgunswap)
1115         if(self.classname != "droppedweapon")
1116         {
1117                 weaponswapping = TRUE;
1118                 spawnfunc_item_shells();
1119                 weaponswapping = FALSE;
1120                 return;
1121         }
1122
1123         if(!self.ammo_nails)
1124                 self.ammo_nails = g_pickup_nails;
1125         if(!self.pickup_anyway)
1126                 self.pickup_anyway = g_pickup_ammo_anyway;
1127         StartItem ("models/items/a_bullets.mdl", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "bullets", IT_NAILS, 0, 0, commodity_pickupevalfunc, 2000);
1128 }
1129
1130 void spawnfunc_item_cells (void) {
1131         if(!self.ammo_cells)
1132                 self.ammo_cells = g_pickup_cells;
1133         if(!self.pickup_anyway)
1134                 self.pickup_anyway = g_pickup_ammo_anyway;
1135         StartItem ("models/items/a_cells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "cells", IT_CELLS, 0, 0, commodity_pickupevalfunc, 2000);
1136 }
1137
1138 void spawnfunc_item_shells (void) {
1139         if(!weaponswapping)
1140         if(autocvar_sv_q3acompat_machineshotgunswap)
1141         if(self.classname != "droppedweapon")
1142         {
1143                 weaponswapping = TRUE;
1144                 spawnfunc_item_bullets();
1145                 weaponswapping = FALSE;
1146                 return;
1147         }
1148
1149         if(!self.ammo_shells)
1150                 self.ammo_shells = g_pickup_shells;
1151         if(!self.pickup_anyway)
1152                 self.pickup_anyway = g_pickup_ammo_anyway;
1153         StartItem ("models/items/a_shells.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "shells", IT_SHELLS, 0, 0, commodity_pickupevalfunc, 500);
1154 }
1155
1156 void spawnfunc_item_armor_small (void) {
1157         if(!self.armorvalue)
1158                 self.armorvalue = g_pickup_armorsmall;
1159         if(!self.max_armorvalue)
1160                 self.max_armorvalue = g_pickup_armorsmall_max;
1161         if(!self.pickup_anyway)
1162                 self.pickup_anyway = g_pickup_armorsmall_anyway;
1163         StartItem ("models/items/item_armor_small.md3", "misc/armor1.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Armor", IT_ARMOR_SHARD, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1164 }
1165
1166 void spawnfunc_item_armor_medium (void) {
1167         if(!self.armorvalue)
1168                 self.armorvalue = g_pickup_armormedium;
1169         if(!self.max_armorvalue)
1170                 self.max_armorvalue = g_pickup_armormedium_max;
1171         if(!self.pickup_anyway)
1172                 self.pickup_anyway = g_pickup_armormedium_anyway;
1173         StartItem ("models/items/item_armor_medium.md3", "misc/armor10.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "25 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1174 }
1175
1176 void spawnfunc_item_armor_big (void) {
1177         if(!self.armorvalue)
1178                 self.armorvalue = g_pickup_armorbig;
1179         if(!self.max_armorvalue)
1180                 self.max_armorvalue = g_pickup_armorbig_max;
1181         if(!self.pickup_anyway)
1182                 self.pickup_anyway = g_pickup_armorbig_anyway;
1183         StartItem ("models/items/item_armor_big.md3", "misc/armor17_5.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "50 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, 20000);
1184 }
1185
1186 void spawnfunc_item_armor_large (void) {
1187         if(!self.armorvalue)
1188                 self.armorvalue = g_pickup_armorlarge;
1189         if(!self.max_armorvalue)
1190                 self.max_armorvalue = g_pickup_armorlarge_max;
1191         if(!self.pickup_anyway)
1192                 self.pickup_anyway = g_pickup_armorlarge_anyway;
1193         StartItem ("models/items/item_armor_large.md3", "misc/armor25.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Armor", IT_ARMOR, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1194 }
1195
1196 void spawnfunc_item_health_small (void) {
1197         if(!self.max_health)
1198                 self.max_health = g_pickup_healthsmall_max;
1199         if(!self.health)
1200                 self.health = g_pickup_healthsmall;
1201         if(!self.pickup_anyway)
1202                 self.pickup_anyway = g_pickup_healthsmall_anyway;
1203         StartItem ("models/items/g_h1.md3", "misc/minihealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "5 Health", IT_5HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1204 }
1205
1206 void spawnfunc_item_health_medium (void) {
1207         if(!self.max_health)
1208                 self.max_health = g_pickup_healthmedium_max;
1209         if(!self.health)
1210                 self.health = g_pickup_healthmedium;
1211         if(!self.pickup_anyway)
1212                 self.pickup_anyway = g_pickup_healthmedium_anyway;
1213         StartItem ("models/items/g_h25.md3", "misc/mediumhealth.wav", g_pickup_respawntime_short, g_pickup_respawntimejitter_short, "25 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1214 }
1215
1216 void spawnfunc_item_health_large (void) {
1217         if(!self.max_health)
1218                 self.max_health = g_pickup_healthlarge_max;
1219         if(!self.health)
1220                 self.health = g_pickup_healthlarge;
1221         if(!self.pickup_anyway)
1222                 self.pickup_anyway = g_pickup_healthlarge_anyway;
1223         StartItem ("models/items/g_h50.md3", "misc/mediumhealth.wav", g_pickup_respawntime_medium, g_pickup_respawntimejitter_medium, "50 Health", IT_25HP, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_MID);
1224 }
1225
1226 void spawnfunc_item_health_mega (void) {
1227                 if(!self.max_health)
1228                         self.max_health = g_pickup_healthmega_max;
1229                 if(!self.health)
1230                         self.health = g_pickup_healthmega;
1231                 if(!self.pickup_anyway)
1232                         self.pickup_anyway = g_pickup_healthmega_anyway;
1233                 StartItem ("models/items/g_h100.md3", "misc/megahealth.wav", g_pickup_respawntime_long, g_pickup_respawntimejitter_long, "100 Health", IT_HEALTH, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_HIGH);
1234 }
1235
1236 // support old misnamed entities
1237 void spawnfunc_item_armor1() { spawnfunc_item_armor_small(); }  // FIXME: in Quake this is green armor, in Xonotic maps it is an armor shard
1238 void spawnfunc_item_armor25() { spawnfunc_item_armor_large(); }
1239 void spawnfunc_item_health1() { spawnfunc_item_health_small(); }
1240 void spawnfunc_item_health25() { spawnfunc_item_health_medium(); }
1241 void spawnfunc_item_health100() { spawnfunc_item_health_mega(); }
1242
1243 void spawnfunc_item_strength (void) {
1244                 precache_sound("weapons/strength_fire.wav");
1245                 if(!self.strength_finished)
1246                         self.strength_finished = autocvar_g_balance_powerup_strength_time;
1247                 StartItem ("models/items/g_strength.md3", "misc/powerup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Strength Powerup", IT_STRENGTH, 0, FL_POWERUP, generic_pickupevalfunc, 100000);
1248 }
1249
1250 void spawnfunc_item_invincible (void) {
1251                 if(!self.invincible_finished)
1252                         self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1253                 StartItem ("models/items/g_invincible.md3", "misc/powerup_shield.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Shield", IT_INVINCIBLE, 0, FL_POWERUP, generic_pickupevalfunc, 100000);
1254 }
1255
1256 // compatibility:
1257 void spawnfunc_item_quad (void) {self.classname = "item_strength";spawnfunc_item_strength();}
1258
1259 void target_items_use (void)
1260 {
1261         if(activator.classname == "droppedweapon")
1262         {
1263                 EXACTTRIGGER_TOUCH;
1264                 remove(activator);
1265                 return;
1266         }
1267
1268         if (!IS_PLAYER(activator))
1269                 return;
1270         if(activator.deadflag != DEAD_NO)
1271                 return;
1272         EXACTTRIGGER_TOUCH;
1273
1274         entity e;
1275         for(e = world; (e = find(e, classname, "droppedweapon")); )
1276                 if(e.enemy == activator)
1277                         remove(e);
1278
1279         if(GiveItems(activator, 0, tokenize_console(self.netname)))
1280                 centerprint(activator, self.message);
1281 }
1282
1283 void spawnfunc_target_items (void)
1284 {
1285         float n, i, j;
1286         entity e;
1287
1288         self.use = target_items_use;
1289         if(!self.strength_finished)
1290                 self.strength_finished = autocvar_g_balance_powerup_strength_time;
1291         if(!self.invincible_finished)
1292                 self.invincible_finished = autocvar_g_balance_powerup_invincible_time;
1293         if(!self.superweapons_finished)
1294                 self.superweapons_finished = autocvar_g_balance_superweapons_time;
1295
1296         precache_sound("misc/itempickup.wav");
1297         precache_sound("misc/megahealth.wav");
1298         precache_sound("misc/armor25.wav");
1299         precache_sound("misc/powerup.wav");
1300         precache_sound("misc/poweroff.wav");
1301         precache_sound("weapons/weaponpickup.wav");
1302
1303         n = tokenize_console(self.netname);
1304         if(argv(0) == "give")
1305         {
1306                 self.netname = substring(self.netname, argv_start_index(1), argv_end_index(-1) - argv_start_index(1));
1307         }
1308         else
1309         {
1310                 for(i = 0; i < n; ++i)
1311                 {
1312                         if     (argv(i) == "unlimited_ammo")         self.items |= IT_UNLIMITED_AMMO;
1313                         else if(argv(i) == "unlimited_weapon_ammo")  self.items |= IT_UNLIMITED_WEAPON_AMMO;
1314                         else if(argv(i) == "unlimited_superweapons") self.items |= IT_UNLIMITED_SUPERWEAPONS;
1315                         else if(argv(i) == "strength")               self.items |= IT_STRENGTH;
1316                         else if(argv(i) == "invincible")             self.items |= IT_INVINCIBLE;
1317                         else if(argv(i) == "superweapons")           self.items |= IT_SUPERWEAPON;
1318                         else if(argv(i) == "jetpack")                self.items |= IT_JETPACK;
1319                         else if(argv(i) == "fuel_regen")             self.items |= IT_FUEL_REGEN;
1320                         else
1321                         {
1322                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1323                                 {
1324                                         e = get_weaponinfo(j);
1325                                         if(argv(i) == e.netname)
1326                                         {
1327                                                 self.weapons |= WepSet_FromWeapon(j);
1328                                                 if(self.spawnflags == 0 || self.spawnflags == 2)
1329                                                         WEP_ACTION(e.weapon, WR_INIT);
1330                                                 break;
1331                                         }
1332                                 }
1333                                 if(j > WEP_LAST)
1334                                         print("target_items: invalid item ", argv(i), "\n");
1335                         }
1336                 }
1337
1338                 string itemprefix, valueprefix;
1339                 if(self.spawnflags == 0)
1340                 {
1341                         itemprefix = "";
1342                         valueprefix = "";
1343                 }
1344                 else if(self.spawnflags == 1)
1345                 {
1346                         itemprefix = "max ";
1347                         valueprefix = "max ";
1348                 }
1349                 else if(self.spawnflags == 2)
1350                 {
1351                         itemprefix = "min ";
1352                         valueprefix = "min ";
1353                 }
1354                 else if(self.spawnflags == 4)
1355                 {
1356                         itemprefix = "minus ";
1357                         valueprefix = "max ";
1358                 }
1359                 else
1360                 {
1361                         error("invalid spawnflags");
1362 #ifdef GMQCC
1363                         itemprefix = string_null;
1364                         valueprefix = string_null;
1365 #endif
1366                 }
1367
1368                 self.netname = "";
1369                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_WEAPON_AMMO), "unlimited_weapon_ammo");
1370                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_UNLIMITED_SUPERWEAPONS), "unlimited_superweapons");
1371                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.strength_finished * !!(self.items & IT_STRENGTH), "strength");
1372                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.invincible_finished * !!(self.items & IT_INVINCIBLE), "invincible");
1373                 self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, self.superweapons_finished * !!(self.items & IT_SUPERWEAPON), "superweapons");
1374                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_JETPACK), "jetpack");
1375                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.items & IT_FUEL_REGEN), "fuel_regen");
1376                 if(self.ammo_shells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_shells), "shells");
1377                 if(self.ammo_nails != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_nails), "nails");
1378                 if(self.ammo_rockets != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_rockets), "rockets");
1379                 if(self.ammo_cells != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_cells), "cells");
1380                 if(self.ammo_fuel != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.ammo_fuel), "fuel");
1381                 if(self.health != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "health");
1382                 if(self.armorvalue != 0) self.netname = sprintf("%s %s%d %s", self.netname, valueprefix, max(0, self.health), "armor");
1383                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1384                 {
1385                         e = get_weaponinfo(j);
1386                         if(e.weapon)
1387                                 self.netname = sprintf("%s %s%d %s", self.netname, itemprefix, !!(self.weapons & WepSet_FromWeapon(j)), e.netname);
1388                 }
1389         }
1390         self.netname = strzone(self.netname);
1391         //print(self.netname, "\n");
1392
1393         n = tokenize_console(self.netname);
1394         for(i = 0; i < n; ++i)
1395         {
1396                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1397                 {
1398                         e = get_weaponinfo(j);
1399                         if(argv(i) == e.netname)
1400                         {
1401                                 WEP_ACTION(e.weapon, WR_INIT);
1402                                 break;
1403                         }
1404                 }
1405         }
1406 }
1407
1408 void spawnfunc_item_fuel(void)
1409 {
1410         if(!self.ammo_fuel)
1411                 self.ammo_fuel = g_pickup_fuel;
1412         if(!self.pickup_anyway)
1413                 self.pickup_anyway = g_pickup_ammo_anyway;
1414         StartItem ("models/items/g_fuel.md3", "misc/itempickup.wav", g_pickup_respawntime_ammo, g_pickup_respawntimejitter_ammo, "Fuel", IT_FUEL, 0, 0, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1415 }
1416
1417 void spawnfunc_item_fuel_regen(void)
1418 {
1419         if(start_items & IT_FUEL_REGEN)
1420         {
1421                 spawnfunc_item_fuel();
1422                 return;
1423         }
1424         StartItem ("models/items/g_fuelregen.md3", "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Fuel regenerator", IT_FUEL_REGEN, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1425 }
1426
1427 void spawnfunc_item_jetpack(void)
1428 {
1429         if(g_grappling_hook)
1430                 return; // sorry, but these two can't coexist (same button); spawn fuel instead
1431         if(!self.ammo_fuel)
1432                 self.ammo_fuel = g_pickup_fuel_jetpack;
1433         if(start_items & IT_JETPACK)
1434         {
1435                 spawnfunc_item_fuel();
1436                 return;
1437         }
1438         StartItem ("models/items/g_jetpack.md3", "misc/itempickup.wav", g_pickup_respawntime_powerup, g_pickup_respawntimejitter_powerup, "Jet pack", IT_JETPACK, 0, FL_POWERUP, commodity_pickupevalfunc, BOT_PICKUP_RATING_LOW);
1439 }
1440
1441 float GiveWeapon(entity e, float wpn, float op, float val)
1442 {
1443         WepSet v0, v1;
1444         v0 = (e.weapons & WepSet_FromWeapon(wpn));
1445         switch(op)
1446         {
1447                 case OP_SET:
1448                         if(val > 0)
1449                                 e.weapons |= WepSet_FromWeapon(wpn);
1450                         else
1451                                 e.weapons &= ~WepSet_FromWeapon(wpn);
1452                         break;
1453                 case OP_MIN:
1454                 case OP_PLUS:
1455                         if(val > 0)
1456                                 e.weapons |= WepSet_FromWeapon(wpn);
1457                         break;
1458                 case OP_MAX:
1459                         if(val <= 0)
1460                                 e.weapons &= ~WepSet_FromWeapon(wpn);
1461                         break;
1462                 case OP_MINUS:
1463                         if(val > 0)
1464                                 e.weapons &= ~WepSet_FromWeapon(wpn);
1465                         break;
1466         }
1467         v1 = (e.weapons & WepSet_FromWeapon(wpn));
1468         return (v0 != v1);
1469 }
1470
1471 float GiveBit(entity e, .float fld, float bit, float op, float val)
1472 {
1473         float v0, v1;
1474         v0 = (e.fld & bit);
1475         switch(op)
1476         {
1477                 case OP_SET:
1478                         if(val > 0)
1479                                 e.fld |= bit;
1480                         else
1481                                 e.fld &= ~bit;
1482                         break;
1483                 case OP_MIN:
1484                 case OP_PLUS:
1485                         if(val > 0)
1486                                 e.fld |= bit;
1487                         break;
1488                 case OP_MAX:
1489                         if(val <= 0)
1490                                 e.fld &= ~bit;
1491                         break;
1492                 case OP_MINUS:
1493                         if(val > 0)
1494                                 e.fld &= ~bit;
1495                         break;
1496         }
1497         v1 = (e.fld & bit);
1498         return (v0 != v1);
1499 }
1500
1501 float GiveValue(entity e, .float fld, float op, float val)
1502 {
1503         float v0, v1;
1504         v0 = e.fld;
1505         switch(op)
1506         {
1507                 case OP_SET:
1508                         e.fld = val;
1509                         break;
1510                 case OP_MIN:
1511                         e.fld = max(e.fld, val); // min 100 cells = at least 100 cells
1512                         break;
1513                 case OP_MAX:
1514                         e.fld = min(e.fld, val);
1515                         break;
1516                 case OP_PLUS:
1517                         e.fld += val;
1518                         break;
1519                 case OP_MINUS:
1520                         e.fld -= val;
1521                         break;
1522         }
1523         v1 = e.fld;
1524         return (v0 != v1);
1525 }
1526
1527 void GiveSound(entity e, float v0, float v1, float t, string snd_incr, string snd_decr)
1528 {
1529         if(v1 == v0)
1530                 return;
1531         if(v1 <= v0 - t)
1532         {
1533                 if(snd_decr != "")
1534                         sound (e, CH_TRIGGER, snd_decr, VOL_BASE, ATTEN_NORM);
1535         }
1536         else if(v0 >= v0 + t)
1537         {
1538                 if(snd_incr != "")
1539                         sound (e, CH_TRIGGER, snd_incr, VOL_BASE, ATTEN_NORM);
1540         }
1541 }
1542
1543 void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime)
1544 {
1545         if(v0 < v1)
1546                 e.rotfield = max(e.rotfield, time + rottime);
1547         else if(v0 > v1)
1548                 e.regenfield = max(e.regenfield, time + regentime);
1549 }
1550 float GiveItems(entity e, float beginarg, float endarg)
1551 {
1552         float got, i, j, val, op;
1553         float _switchweapon;
1554         entity wi;
1555         string cmd;
1556
1557         val = 999;
1558         op = OP_SET;
1559
1560         got = 0;
1561
1562         _switchweapon = FALSE;
1563         if (e.autoswitch)
1564                 if (e.switchweapon == w_getbestweapon(e))
1565                         _switchweapon = TRUE;
1566
1567         e.strength_finished = max(0, e.strength_finished - time);
1568         e.invincible_finished = max(0, e.invincible_finished - time);
1569         e.superweapons_finished = max(0, e.superweapons_finished - time);
1570
1571         PREGIVE(e, items);
1572         PREGIVE_WEAPONS(e);
1573         PREGIVE(e, strength_finished);
1574         PREGIVE(e, invincible_finished);
1575         PREGIVE(e, superweapons_finished);
1576         PREGIVE(e, ammo_nails);
1577         PREGIVE(e, ammo_cells);
1578         PREGIVE(e, ammo_shells);
1579         PREGIVE(e, ammo_rockets);
1580         PREGIVE(e, ammo_fuel);
1581         PREGIVE(e, armorvalue);
1582         PREGIVE(e, health);
1583
1584         for(i = beginarg; i < endarg; ++i)
1585         {
1586                 cmd = argv(i);
1587
1588                 if(cmd == "0" || stof(cmd))
1589                 {
1590                         val = stof(cmd);
1591                         continue;
1592                 }
1593                 switch(cmd)
1594                 {
1595                         case "no":
1596                                 op = OP_MAX;
1597                                 val = 0;
1598                                 continue;
1599                         case "max":
1600                                 op = OP_MAX;
1601                                 continue;
1602                         case "min":
1603                                 op = OP_MIN;
1604                                 continue;
1605                         case "plus":
1606                                 op = OP_PLUS;
1607                                 continue;
1608                         case "minus":
1609                                 op = OP_MINUS;
1610                                 continue;
1611                         case "ALL":
1612                                 got += GiveBit(e, items, IT_FUEL_REGEN, op, val);
1613                                 got += GiveValue(e, strength_finished, op, val);
1614                                 got += GiveValue(e, invincible_finished, op, val);
1615                                 got += GiveValue(e, superweapons_finished, op, val);
1616                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
1617                         case "all":
1618                                 got += GiveBit(e, items, IT_JETPACK, op, val);
1619                                 got += GiveValue(e, health, op, val);
1620                                 got += GiveValue(e, armorvalue, op, val);
1621                         case "allweapons":
1622                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1623                                 {
1624                                         wi = get_weaponinfo(j);
1625                                         if(wi.weapon)
1626                                                 if (!(wi.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1627                                                         got += GiveWeapon(e, j, op, val);
1628                                 }
1629                         case "allammo":
1630                                 got += GiveValue(e, ammo_cells, op, val);
1631                                 got += GiveValue(e, ammo_shells, op, val);
1632                                 got += GiveValue(e, ammo_nails, op, val);
1633                                 got += GiveValue(e, ammo_rockets, op, val);
1634                                 got += GiveValue(e, ammo_fuel, op, val);
1635                                 break;
1636                         case "unlimited_ammo":
1637                                 got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
1638                                 break;
1639                         case "unlimited_weapon_ammo":
1640                                 got += GiveBit(e, items, IT_UNLIMITED_WEAPON_AMMO, op, val);
1641                                 break;
1642                         case "unlimited_superweapons":
1643                                 got += GiveBit(e, items, IT_UNLIMITED_SUPERWEAPONS, op, val);
1644                                 break;
1645                         case "jetpack":
1646                                 got += GiveBit(e, items, IT_JETPACK, op, val);
1647                                 break;
1648                         case "fuel_regen":
1649                                 got += GiveBit(e, items, IT_FUEL_REGEN, op, val);
1650                                 break;
1651                         case "strength":
1652                                 got += GiveValue(e, strength_finished, op, val);
1653                                 break;
1654                         case "invincible":
1655                                 got += GiveValue(e, invincible_finished, op, val);
1656                                 break;
1657                         case "superweapons":
1658                                 got += GiveValue(e, superweapons_finished, op, val);
1659                                 break;
1660                         case "cells":
1661                                 got += GiveValue(e, ammo_cells, op, val);
1662                                 break;
1663                         case "shells":
1664                                 got += GiveValue(e, ammo_shells, op, val);
1665                                 break;
1666                         case "nails":
1667                         case "bullets":
1668                                 got += GiveValue(e, ammo_nails, op, val);
1669                                 break;
1670                         case "rockets":
1671                                 got += GiveValue(e, ammo_rockets, op, val);
1672                                 break;
1673                         case "health":
1674                                 got += GiveValue(e, health, op, val);
1675                                 break;
1676                         case "armor":
1677                                 got += GiveValue(e, armorvalue, op, val);
1678                                 break;
1679                         case "fuel":
1680                                 got += GiveValue(e, ammo_fuel, op, val);
1681                                 break;
1682                         default:
1683                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1684                                 {
1685                                         wi = get_weaponinfo(j);
1686                                         if(cmd == wi.netname)
1687                                         {
1688                                                 got += GiveWeapon(e, j, op, val);
1689                                                 break;
1690                                         }
1691                                 }
1692                                 if(j > WEP_LAST)
1693                                         print("give: invalid item ", cmd, "\n");
1694                                 break;
1695                 }
1696                 val = 999;
1697                 op = OP_SET;
1698         }
1699
1700         POSTGIVE_BIT(e, items, IT_FUEL_REGEN, "misc/itempickup.wav", string_null);
1701         POSTGIVE_BIT(e, items, IT_UNLIMITED_SUPERWEAPONS, "misc/powerup.wav", "misc/poweroff.wav");
1702         POSTGIVE_BIT(e, items, IT_UNLIMITED_WEAPON_AMMO, "misc/powerup.wav", "misc/poweroff.wav");
1703         POSTGIVE_BIT(e, items, IT_JETPACK, "misc/itempickup.wav", string_null);
1704         for(j = WEP_FIRST; j <= WEP_LAST; ++j)
1705         {
1706                 wi = get_weaponinfo(j);
1707                 if(wi.weapon)
1708                 {
1709                         POSTGIVE_WEAPON(e, j, "weapons/weaponpickup.wav", string_null);
1710                         if (!(save_weapons & WepSet_FromWeapon(j)))
1711                                 if(e.weapons & WepSet_FromWeapon(j))
1712                                         WEP_ACTION(wi.weapon, WR_INIT);
1713                 }
1714         }
1715         POSTGIVE_VALUE(e, strength_finished, 1, "misc/powerup.wav", "misc/poweroff.wav");
1716         POSTGIVE_VALUE(e, invincible_finished, 1, "misc/powerup_shield.wav", "misc/poweroff.wav");
1717         POSTGIVE_VALUE(e, ammo_nails, 0, "misc/itempickup.wav", string_null);
1718         POSTGIVE_VALUE(e, ammo_cells, 0, "misc/itempickup.wav", string_null);
1719         POSTGIVE_VALUE(e, ammo_shells, 0, "misc/itempickup.wav", string_null);
1720         POSTGIVE_VALUE(e, ammo_rockets, 0, "misc/itempickup.wav", string_null);
1721         POSTGIVE_VALUE_ROT(e, ammo_fuel, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, "misc/itempickup.wav", string_null);
1722         POSTGIVE_VALUE_ROT(e, armorvalue, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, "misc/armor25.wav", string_null);
1723         POSTGIVE_VALUE_ROT(e, health, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, "misc/megahealth.wav", string_null);
1724
1725         if(e.superweapons_finished <= 0)
1726                 if(self.weapons & WEPSET_SUPERWEAPONS)
1727                         e.superweapons_finished = autocvar_g_balance_superweapons_time;
1728
1729         if(e.strength_finished <= 0)
1730                 e.strength_finished = 0;
1731         else
1732                 e.strength_finished += time;
1733         if(e.invincible_finished <= 0)
1734                 e.invincible_finished = 0;
1735         else
1736                 e.invincible_finished += time;
1737         if(e.superweapons_finished <= 0)
1738                 e.superweapons_finished = 0;
1739         else
1740                 e.superweapons_finished += time;
1741
1742         if (!(e.weapons & WepSet_FromWeapon(e.switchweapon)))
1743                 _switchweapon = TRUE;
1744         if(_switchweapon)
1745                 W_SwitchWeapon_Force(e, w_getbestweapon(e));
1746
1747         return got;
1748 }
1749 #endif