]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator_buffs.qc
Remove a now useless deathtype
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator_buffs.qc
1 float buffs_BuffModel_Customize()
2 {
3         float same_team = (SAME_TEAM(other, self.owner) || (IS_SPEC(other) && SAME_TEAM(other.enemy, self.owner)));
4         if(self.owner.alpha <= 0.5 && !same_team && self.owner.alpha != 0)
5                 return FALSE;
6                 
7         if(other == self.owner || (IS_SPEC(other) && other.enemy == self.owner))
8         {
9                 // somewhat hide the model, but keep the glow
10                 self.effects = 0;
11                 self.alpha = -1;
12         }
13         else
14         {
15                 self.effects = EF_FULLBRIGHT | EF_LOWPRECISION;
16                 self.alpha = 1;
17         }
18         return TRUE;
19 }
20
21 // buff item
22 float buff_Waypoint_visible_for_player(entity plr)
23 {
24     if(!self.owner.buff_active && !self.owner.buff_activetime)
25         return FALSE;
26                 
27         if(plr.buffs)
28         {
29                 if(plr.cvar_cl_buffs_autoreplace)
30                 {
31                         if(plr.buffs == self.owner.buffs)
32                                 return FALSE;
33                 }
34                 else
35                         return FALSE;
36         }
37
38     return WaypointSprite_visible_for_player(plr);
39 }
40
41 void buff_Waypoint_Spawn(entity e)
42 {
43     WaypointSprite_Spawn(Buff_PrettyName(e.buffs), 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs_z, world, e.team, e, buff_waypoint, TRUE, RADARICON_POWERUP, e.glowmod);
44     WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_POWERUP, e.glowmod);
45     e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player;
46 }
47
48 void buff_SetCooldown(float cd)
49 {
50         cd = max(0, cd);
51
52         if(!self.buff_waypoint)
53                 buff_Waypoint_Spawn(self);
54
55         WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd);
56         self.buff_activetime = cd;
57         self.buff_active = !cd;
58 }
59
60 void buff_Respawn(entity ent)
61 {
62         if(gameover) { return; }
63         
64         vector oldbufforigin = ent.origin;
65         
66         if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256))
67         {
68                 entity spot = SelectSpawnPoint(TRUE);
69                 setorigin(self, (spot.origin + '0 0 200') + (randomvec() * 300));
70                 self.angles = spot.angles;
71         }
72         
73         makevectors(ent.angles);
74         ent.velocity = '0 0 200';
75         ent.angles = '0 0 0';
76         if(autocvar_g_buffs_random_lifetime > 0)
77                 ent.lifetime = time + autocvar_g_buffs_random_lifetime;
78
79         pointparticles(particleeffectnum("electro_combo"), oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1);
80         pointparticles(particleeffectnum("electro_combo"), CENTER_OR_VIEWOFS(ent), '0 0 0', 1);
81         
82         WaypointSprite_Ping(ent.buff_waypoint);
83         
84         sound(ent, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
85 }
86
87 void buff_Touch()
88 {
89         if(gameover) { return; }
90
91         if(ITEM_TOUCH_NEEDKILL())
92         {
93                 buff_Respawn(self);
94                 return;
95         }
96
97         if((self.team && DIFF_TEAM(other, self))
98         || (other.freezetag_frozen)
99         || (other.vehicle)
100         || (!IS_PLAYER(other))
101         || (!self.buff_active)
102         )
103         {
104                 // can't touch this
105                 return;
106         }
107
108         if(other.buffs)
109         {
110                 if(other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs)
111                 {
112                         //Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_DROP, other.buffs);
113                         Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ITEM_BUFF_LOST, other.netname, other.buffs);
114
115                         other.buffs = 0;
116                         //sound(other, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM);
117                 }
118                 else { return; } // do nothing
119         }
120                 
121         self.owner = other;
122         self.buff_active = FALSE;
123         self.lifetime = 0;
124         
125         Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, self.buffs);
126         Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, self.buffs);
127
128         pointparticles(particleeffectnum("item_pickup"), CENTER_OR_VIEWOFS(self), '0 0 0', 1);
129         sound(other, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM);
130         other.buffs |= (self.buffs);
131 }
132
133 float buff_Available(float buffid)
134 {
135         if(buffid == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only"))))
136                 return FALSE;
137
138         if(buffid == BUFF_VAMPIRE && cvar("g_vampire"))
139                 return FALSE;
140
141         if(!cvar(strcat("g_buffs_", Buff_Name(buffid))))
142                 return FALSE;
143
144         return TRUE;
145 }
146
147 void buff_NewType(entity ent, float cb)
148 {
149         entity e;
150         RandomSelection_Init();
151         for(e = Buff_Type_first; e; e = e.enemy)
152         if(buff_Available(e.items))
153         {
154                 RandomSelection_Add(world, e.items, string_null, 1, 1 / e.count); // if it's already been chosen, give it a lower priority
155                 e.count += 1;
156         }
157         ent.buffs = RandomSelection_chosen_float;
158 }
159
160 void buff_Think()
161 {
162         if(self.buffs != self.oldbuffs)
163         {
164                 self.color = Buff_Color(self.buffs);
165                 self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color);
166                 self.skin = Buff_Skin(self.buffs);
167                 
168                 setmodel(self, "models/relics/relic.md3");
169
170                 if(self.buff_waypoint)
171                 {
172                         //WaypointSprite_Disown(self.buff_waypoint, 1);
173                         WaypointSprite_Kill(self.buff_waypoint);
174                         buff_Waypoint_Spawn(self);
175                         if(self.buff_activetime)
176                                 WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime);
177                 }
178
179                 self.oldbuffs = self.buffs;
180         }
181
182         if(!gameover)
183         if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
184         if(!self.buff_activetime_updated)
185         {
186                 buff_SetCooldown(self.buff_activetime);
187                 self.buff_activetime_updated = TRUE;
188     }
189
190         if(!self.buff_active && !self.buff_activetime)
191         if(!self.owner || self.owner.freezetag_frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs))
192         {
193                 buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime);
194                 self.owner = world;
195                 if(autocvar_g_buffs_randomize)
196                         buff_NewType(self, self.buffs);
197                         
198                 if(autocvar_g_buffs_random_location || (self.spawnflags & 1))
199                         buff_Respawn(self);
200         }
201         
202         if(self.buff_activetime)
203         if(!gameover)
204         if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime)
205         {
206                 self.buff_activetime = max(0, self.buff_activetime - frametime);
207
208                 if(!self.buff_activetime)
209                 {
210                         self.buff_active = TRUE;
211                         sound(self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM);
212                         pointparticles(particleeffectnum("item_respawn"), CENTER_OR_VIEWOFS(self), '0 0 0', 1);
213                 }
214         }
215
216         if(!self.buff_active)
217         {
218                 self.alpha = 0.3;
219                 self.effects &= ~(EF_FULLBRIGHT);
220                 self.pflags = 0;
221         }
222         else
223         {
224                 self.alpha = 1;
225                 self.effects |= EF_FULLBRIGHT;
226                 self.light_lev = 220 + 36 * sin(time);
227                 self.pflags = PFLAGS_FULLDYNAMIC;
228
229                 if(self.team && !self.buff_waypoint)
230                         buff_Waypoint_Spawn(self);
231                         
232                 if(self.lifetime)
233                 if(time >= self.lifetime)
234                         buff_Respawn(self);
235         }
236     
237         self.nextthink = time;
238         //self.angles_y = time * 110.1;
239 }
240
241 void buff_Waypoint_Reset()
242 {
243         WaypointSprite_Kill(self.buff_waypoint);
244
245         if(self.buff_activetime) { buff_Waypoint_Spawn(self); }
246 }
247
248 void buff_Reset()
249 {
250         if(autocvar_g_buffs_randomize)
251                 buff_NewType(self, self.buffs);
252         self.owner = world;
253         buff_SetCooldown(autocvar_g_buffs_cooldown_activate);
254         buff_Waypoint_Reset();
255         self.buff_activetime_updated = FALSE;
256         
257         if(autocvar_g_buffs_random_location || (self.spawnflags & 1))
258                 buff_Respawn(self);
259 }
260
261 void buff_Init(entity ent)
262 {
263         if(!cvar("g_buffs")) { remove(self); return; }
264         
265         if(!teamplay && self.team) { self.team = 0; }
266
267         entity oldself = self;
268         self = ent;
269         if(!self.buffs || buff_Available(self.buffs))
270                 buff_NewType(self, 0);
271         
272         self.classname = "item_buff";
273         self.solid = SOLID_TRIGGER;
274         self.flags = FL_ITEM;
275         self.think = buff_Think;
276         self.touch = buff_Touch;
277         self.reset = buff_Reset;
278         self.nextthink = time + 0.1;
279         self.gravity = 1;
280         self.movetype = MOVETYPE_TOSS;
281         self.scale = 1;
282         self.skin = Buff_Skin(self.buffs);
283         self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW;
284         self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY;
285         //self.gravity = 100;
286         self.color = Buff_Color(self.buffs);
287         self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color);
288         buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime);
289         self.buff_active = !self.buff_activetime;
290         self.pflags = PFLAGS_FULLDYNAMIC;
291
292         setmodel(self, "models/relics/relic.md3");
293         setsize(self, BUFF_MIN, BUFF_MAX);
294         
295         if(cvar("g_buffs_random_location") || (self.spawnflags & 1))
296                 buff_Respawn(self);
297         
298         self = oldself;
299 }
300
301 void buff_Init_Compat(entity ent, float replacement)
302 {
303         if(ent.spawnflags & 2)
304                 ent.team = NUM_TEAM_1;
305         else if(ent.spawnflags & 4)
306                 ent.team = NUM_TEAM_2;
307
308         ent.buffs = replacement;
309
310         buff_Init(ent);
311 }
312
313 void buff_SpawnReplacement(entity ent, entity old)
314 {
315         setorigin(ent, old.origin);
316         ent.angles = old.angles;
317         ent.noalign = old.noalign;
318         
319         buff_Init(ent);
320 }
321
322 // mutator hooks
323 MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_SplitHealthArmor)
324 {
325         if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't
326
327         if(frag_target.buffs & BUFF_RESISTANCE)
328         {
329                 vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage);
330                 damage_take = v_x;
331                 damage_save = v_y;
332         }
333
334         return FALSE;
335 }
336
337 MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate)
338 {
339         if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't
340
341         if(frag_target.buffs & BUFF_SPEED)
342         if(frag_target != frag_attacker)
343                 frag_damage *= autocvar_g_buffs_speed_damage_take;
344
345         if(frag_target.buffs & BUFF_MEDIC)
346         if((frag_target.health - frag_damage) <= 0)
347         if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
348         if(frag_attacker)
349         if(random() <= autocvar_g_buffs_medic_survive_chance)
350                 frag_damage = frag_target.health - autocvar_g_buffs_medic_survive_health;
351                 
352         if(frag_target.buffs & BUFF_VENGEANCE)
353         if(frag_attacker)
354         if(frag_attacker != frag_target)
355         if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
356         {
357                 vector v = healtharmor_applydamage(frag_attacker.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_BUFF_VENGEANCE, frag_damage * autocvar_g_buffs_vengeance_damage_multiplier);
358                 float take = v_x;
359                 Damage(frag_attacker, frag_target, frag_target, take, DEATH_BUFF_VENGEANCE, frag_attacker.origin, '0 0 0');
360         }
361
362         if(frag_target.buffs & BUFF_BASH)
363         if(frag_attacker != frag_target)
364         if(vlen(frag_force))
365                 frag_force = '0 0 0';
366         
367         if(frag_attacker.buffs & BUFF_BASH)
368         if(vlen(frag_force))
369         if(frag_attacker == frag_target)
370                 frag_force *= autocvar_g_buffs_bash_force_self;
371         else
372                 frag_force *= autocvar_g_buffs_bash_force;
373         
374         if(frag_attacker.buffs & BUFF_DISABILITY)
375         if(frag_target != frag_attacker)
376                 frag_target.buff_disability_time = time + autocvar_g_buffs_disability_time;
377
378         if(frag_attacker.buffs & BUFF_MEDIC)
379         if(SAME_TEAM(frag_attacker, frag_target))
380         if(frag_attacker != frag_target)
381         {
382                 frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage);
383                 frag_damage = 0;
384         }
385
386         // this... is ridiculous (TODO: fix!)
387         if(frag_attacker.buffs & BUFF_VAMPIRE)
388         if(!frag_target.vehicle)
389         if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype))
390         if(frag_target.deadflag == DEAD_NO)
391         if(IS_PLAYER(frag_target) || (frag_target.flags & FL_MONSTER))
392         if(frag_attacker != frag_target)
393         if(!frag_target.freezetag_frozen)
394         if(frag_target.takedamage)
395         if(DIFF_TEAM(frag_attacker, frag_target))
396                 frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max);
397
398         return FALSE;
399 }
400
401 MUTATOR_HOOKFUNCTION(buffs_PlayerSpawn)
402 {
403         self.buffs = 0;
404         // reset timers here to prevent them continuing after re-spawn
405         self.buff_disability_time = 0;
406         self.buff_disability_effect_time = 0;
407         return FALSE;
408 }
409
410 MUTATOR_HOOKFUNCTION(buffs_PlayerPhysics)
411 {
412         if(self.buffs & BUFF_SPEED)
413         {
414                 self.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed;
415                 self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed;
416         }
417         
418         if(time < self.buff_disability_time)
419         {
420                 self.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed;
421                 self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed;
422         }
423
424         return FALSE;
425 }
426
427 MUTATOR_HOOKFUNCTION(buffs_PlayerJump)
428 {
429         if(self.buffs & BUFF_JUMP)
430                 player_jumpheight = autocvar_g_buffs_jump_height;
431
432         return FALSE;
433 }
434
435 MUTATOR_HOOKFUNCTION(buffs_MonsterMove)
436 {
437         if(time < self.buff_disability_time)
438         {
439                 monster_speed_walk *= autocvar_g_buffs_disability_speed;
440                 monster_speed_run *= autocvar_g_buffs_disability_speed;
441         }
442
443         return FALSE;
444 }
445
446 MUTATOR_HOOKFUNCTION(buffs_PlayerDies)
447 {
448         if(self.buffs)
449         {
450                 Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
451                 self.buffs = 0;
452                 
453                 if(self.buff_model)
454                 {
455                         remove(self.buff_model);
456                         self.buff_model = world;
457                 }
458         }
459         return FALSE;
460 }
461
462 MUTATOR_HOOKFUNCTION(buffs_PlayerUseKey)
463 {
464         if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
465         if(self.buffs)
466         {
467                 Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, self.buffs);
468                 Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
469
470                 self.buffs = 0;
471                 sound(self, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM);
472                 return TRUE;
473         }
474         return FALSE;
475 }
476
477 MUTATOR_HOOKFUNCTION(buffs_RemovePlayer)
478 {
479         if(self.buff_model)
480         {
481                 remove(self.buff_model);
482                 self.buff_model = world;
483         }
484         
485         // also reset timers here to prevent them continuing after spectating
486         self.buff_disability_time = 0;
487         self.buff_disability_effect_time = 0;
488
489         return FALSE;
490 }
491
492 MUTATOR_HOOKFUNCTION(buffs_OnEntityPreSpawn)
493 {
494         if(autocvar_g_buffs_replace_powerups)
495         switch(self.classname)
496         {
497                 case "item_strength":
498                 case "item_invincible":
499                 {
500                         entity e = spawn();
501                         buff_SpawnReplacement(e, self);
502                         return TRUE;
503                 }
504         }
505         return FALSE;
506 }
507
508 MUTATOR_HOOKFUNCTION(buffs_WeaponRate)
509 {
510         if(self.buffs & BUFF_SPEED)
511                 weapon_rate *= autocvar_g_buffs_speed_rate;
512                 
513         if(time < self.buff_disability_time)
514                 weapon_rate *= autocvar_g_buffs_disability_rate;
515         
516         return FALSE;
517 }
518
519 MUTATOR_HOOKFUNCTION(buffs_PlayerThink)
520 {
521         if(gameover || self.deadflag != DEAD_NO) { return FALSE; }
522         
523         if(time < self.buff_disability_time)
524         if(time >= self.buff_disability_effect_time)
525         {
526                 pointparticles(particleeffectnum("smoking"), self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1);
527                 self.buff_disability_effect_time = time + 0.5;
528         }
529         
530         if(self.freezetag_frozen)
531         {
532                 if(self.buffs)
533                 {
534                         Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs);
535                         self.buffs = 0;
536                 }
537         }
538
539         if(!(self.oldbuffs & BUFF_JUMP) && !(self.buffs & BUFF_JUMP))
540                 self.stat_jumpheight = autocvar_sv_jumpvelocity; // reset so we don't break anything
541         else if((self.buffs & BUFF_JUMP) && self.stat_jumpheight != autocvar_g_buffs_jump_height)
542                 self.stat_jumpheight = autocvar_g_buffs_jump_height;
543                 
544         if((self.buffs & BUFF_INVISIBLE) && (self.oldbuffs & BUFF_INVISIBLE))
545         if(self.alpha != autocvar_g_buffs_invisible_alpha)
546                 self.alpha = autocvar_g_buffs_invisible_alpha;
547
548         if(self.buffs != self.oldbuffs)
549         {
550                 if(self.oldbuffs & BUFF_AMMO)
551                 {
552                         if(self.buff_ammo_prev_infitems)
553                                 self.items |= IT_UNLIMITED_WEAPON_AMMO;
554                         else
555                                 self.items &= ~IT_UNLIMITED_WEAPON_AMMO;
556                 }
557                 else if(self.buffs & BUFF_AMMO)
558                 {
559                         self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO);
560                         self.items |= IT_UNLIMITED_WEAPON_AMMO;
561                         if(!self.ammo_shells) { self.ammo_shells = 20; }
562                         if(!self.ammo_cells) { self.ammo_cells = 20; }
563                         if(!self.ammo_rockets) { self.ammo_rockets = 20; }
564                         if(!self.ammo_nails) { self.ammo_nails = 20; }
565                         if(!self.ammo_fuel) { self.ammo_fuel = 20; }
566                 }
567                 
568                 if(self.oldbuffs & BUFF_JUMP)
569                         self.stat_jumpheight = autocvar_sv_jumpvelocity;
570                 else if(self.buffs & BUFF_JUMP)
571                         self.stat_jumpheight = autocvar_g_buffs_jump_height;
572                 
573                 if(self.oldbuffs & BUFF_INVISIBLE)
574                 {
575                         if(time < self.strength_finished && g_minstagib)
576                                 self.alpha = autocvar_g_minstagib_invis_alpha;
577                         else
578                                 self.alpha = self.buff_invisible_prev_alpha;
579                 }
580                 else if(self.buffs & BUFF_INVISIBLE)
581                 {
582                         if(time < self.strength_finished && g_minstagib)
583                                 self.buff_invisible_prev_alpha = default_player_alpha;
584                         else
585                                 self.buff_invisible_prev_alpha = self.alpha;
586                         self.alpha = autocvar_g_buffs_invisible_alpha;
587                 }
588                 
589                 if(self.oldbuffs & BUFF_FLIGHT)
590                         self.gravity = self.buff_flight_prev_gravity;
591                 else if(self.buffs & BUFF_FLIGHT)
592                 {
593                         self.buff_flight_prev_gravity = self.gravity;
594                         self.gravity = autocvar_g_buffs_flight_gravity;
595                 }
596
597                 self.oldbuffs = self.buffs;
598                 if(self.buffs)
599                 {
600                         if(!self.buff_model)
601                         {
602                                 self.buff_model = spawn();
603                                 setmodel(self.buff_model, "models/relics/relic.md3");
604                                 setsize(self.buff_model, '0 0 -40', '0 0 40');
605                                 setattachment(self.buff_model, self, "");
606                                 setorigin(self.buff_model, '0 0 1' * (self.buff_model.maxs_z * 1));
607                                 self.buff_model.owner = self;
608                                 self.buff_model.scale = 0.7;
609                                 self.buff_model.pflags = PFLAGS_FULLDYNAMIC;
610                                 self.buff_model.light_lev = 200;
611                                 self.buff_model.customizeentityforclient = buffs_BuffModel_Customize;
612                         }
613                         self.buff_model.color = Buff_Color(self.buffs);
614                         self.buff_model.glowmod = ((self.buff_model.team) ? Team_ColorRGB(self.buff_model.team) + '0.1 0.1 0.1' : self.buff_model.color);
615                         self.buff_model.skin = Buff_Skin(self.buffs);
616                         
617                         self.effects |= EF_NOSHADOW;
618                 }
619                 else
620                 {
621                         remove(self.buff_model);
622                         self.buff_model = world;
623                         
624                         self.effects &= ~(EF_NOSHADOW);
625                 }
626         }
627
628         if(self.buff_model)
629         {
630                 self.buff_model.effects = self.effects;
631                 self.buff_model.effects |= EF_LOWPRECISION;
632                 self.buff_model.effects = self.buff_model.effects & EFMASK_CHEAP; // eat performance
633                 
634                 self.buff_model.alpha = self.alpha;
635         }
636
637         return FALSE;
638 }
639
640 MUTATOR_HOOKFUNCTION(buffs_SpectateCopy)
641 {
642         self.buffs = other.buffs;
643         return FALSE;
644 }
645
646 MUTATOR_HOOKFUNCTION(buffs_VehicleEnter)
647 {
648         vh_vehicle.buffs = vh_player.buffs;
649         vh_player.buffs = 0;
650         return FALSE;
651 }
652
653 MUTATOR_HOOKFUNCTION(buffs_VehicleExit)
654 {
655         vh_player.buffs = vh_vehicle.buffs;
656         vh_vehicle.buffs = 0;
657         return FALSE;
658 }
659
660 MUTATOR_HOOKFUNCTION(buffs_PlayerRegen)
661 {
662         if(self.buffs & BUFF_MEDIC)
663         {
664                 regen_mod_rot = autocvar_g_buffs_medic_rot;
665                 regen_mod_limit = regen_mod_max = autocvar_g_buffs_medic_max;
666                 regen_mod_regen = autocvar_g_buffs_medic_regen;
667         }
668         
669         if(self.buffs & BUFF_SPEED)
670                 regen_mod_regen = autocvar_g_buffs_speed_regen;
671
672         return FALSE;
673 }
674
675 MUTATOR_HOOKFUNCTION(buffs_GetCvars)
676 {
677         GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace");
678         return FALSE;
679 }
680
681 MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsString)
682 {
683         ret_string = strcat(ret_string, ":Buffs");
684         return FALSE;
685 }
686
687 MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsPrettyString)
688 {
689         ret_string = strcat(ret_string, ", Buffs");
690         return FALSE;
691 }
692
693 void buffs_DelayedInit()
694 {
695         if(autocvar_g_buffs_spawn_count > 0)
696         if(find(world, classname, "item_buff") == world)
697         {
698                 float i;
699                 for(i = 0; i < autocvar_g_buffs_spawn_count; ++i)
700                 {
701                         entity e = spawn();
702                         e.spawnflags |= 1; // always randomize
703                         e.velocity = randomvec() * 250; // this gets reset anyway if random location works
704                         buff_Init(e);
705                 }
706         }
707 }
708
709 void buffs_Initialize()
710 {
711         precache_model("models/relics/relic.md3");
712         precache_sound("misc/strength_respawn.wav");
713         precache_sound("misc/shield_respawn.wav");
714         precache_sound("relics/relic_effect.wav");
715         precache_sound("weapons/rocket_impact.wav");
716         precache_sound("keepaway/respawn.wav");
717
718         addstat(STAT_BUFFS, AS_INT, buffs);
719         addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_jumpheight);
720         
721         InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET);
722 }
723
724 MUTATOR_DEFINITION(mutator_buffs)
725 {
726         MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, buffs_PlayerDamage_SplitHealthArmor, CBC_ORDER_ANY);
727         MUTATOR_HOOK(PlayerDamage_Calculate, buffs_PlayerDamage_Calculate, CBC_ORDER_LAST);
728         MUTATOR_HOOK(PlayerSpawn, buffs_PlayerSpawn, CBC_ORDER_ANY);
729         MUTATOR_HOOK(PlayerPhysics, buffs_PlayerPhysics, CBC_ORDER_ANY);
730         MUTATOR_HOOK(PlayerJump, buffs_PlayerJump, CBC_ORDER_ANY);
731         MUTATOR_HOOK(MonsterMove, buffs_MonsterMove, CBC_ORDER_ANY);
732         MUTATOR_HOOK(SpectateCopy, buffs_SpectateCopy, CBC_ORDER_ANY);
733         MUTATOR_HOOK(VehicleEnter, buffs_VehicleEnter, CBC_ORDER_ANY);
734         MUTATOR_HOOK(VehicleExit, buffs_VehicleExit, CBC_ORDER_ANY);
735         MUTATOR_HOOK(PlayerRegen, buffs_PlayerRegen, CBC_ORDER_ANY);
736         MUTATOR_HOOK(PlayerDies, buffs_PlayerDies, CBC_ORDER_ANY);
737         MUTATOR_HOOK(PlayerUseKey, buffs_PlayerUseKey, CBC_ORDER_ANY);
738         MUTATOR_HOOK(MakePlayerObserver, buffs_RemovePlayer, CBC_ORDER_ANY);
739         MUTATOR_HOOK(ClientDisconnect, buffs_RemovePlayer, CBC_ORDER_ANY);
740         MUTATOR_HOOK(OnEntityPreSpawn, buffs_OnEntityPreSpawn, CBC_ORDER_ANY);
741         MUTATOR_HOOK(WeaponRateFactor, buffs_WeaponRate, CBC_ORDER_ANY);
742         MUTATOR_HOOK(PlayerPreThink, buffs_PlayerThink, CBC_ORDER_ANY);
743         MUTATOR_HOOK(GetCvars, buffs_GetCvars, CBC_ORDER_ANY);
744         MUTATOR_HOOK(BuildMutatorsString, buffs_BuildMutatorsString, CBC_ORDER_ANY);
745         MUTATOR_HOOK(BuildMutatorsPrettyString, buffs_BuildMutatorsPrettyString, CBC_ORDER_ANY);
746
747         MUTATOR_ONADD
748         {
749                 buffs_Initialize();
750         }
751
752         return FALSE;
753 }