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