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