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