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