Buffs: rename registry globals
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
1 #include "g_damage.qh"
2 #include "_all.qh"
3
4 #include "g_hook.qh"
5 #include "mutators/mutators_include.qh"
6 #include "scores.qh"
7 #include "spawnpoints.qh"
8 #include "t_items.qh"
9 #include "../common/vehicles/all.qh"
10 #include "weapons/accuracy.qh"
11 #include "weapons/csqcprojectile.qh"
12 #include "weapons/selection.qh"
13 #include "../common/buffs/all.qh"
14 #include "../common/constants.qh"
15 #include "../common/deathtypes.qh"
16 #include "../common/notifications.qh"
17 #include "../common/movetypes/movetypes.qh"
18 #include "../common/playerstats.qh"
19 #include "../common/teams.qh"
20 #include "../common/util.qh"
21 #include "../common/weapons/all.qh"
22 #include "../csqcmodellib/sv_model.qh"
23 #include "../warpzonelib/common.qh"
24
25 float Damage_DamageInfo_SendEntity(entity to, int sf)
26 {SELFPARAM();
27         WriteByte(MSG_ENTITY, ENT_CLIENT_DAMAGEINFO);
28         WriteShort(MSG_ENTITY, self.projectiledeathtype);
29         WriteCoord(MSG_ENTITY, floor(self.origin.x));
30         WriteCoord(MSG_ENTITY, floor(self.origin.y));
31         WriteCoord(MSG_ENTITY, floor(self.origin.z));
32         WriteByte(MSG_ENTITY, bound(1, self.dmg, 255));
33         WriteByte(MSG_ENTITY, bound(0, self.dmg_radius, 255));
34         WriteByte(MSG_ENTITY, bound(1, self.dmg_edge, 255));
35         WriteShort(MSG_ENTITY, self.oldorigin.x);
36         WriteByte(MSG_ENTITY, self.species);
37         return true;
38 }
39
40 void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, int deathtype, float bloodtype, entity dmgowner)
41 {
42         // TODO maybe call this from non-edgedamage too?
43         // TODO maybe make the client do the particle effects for the weapons and the impact sounds using this info?
44
45         entity e;
46
47         if(!sound_allowed(MSG_BROADCAST, dmgowner))
48                 deathtype |= 0x8000;
49
50         e = spawn();
51         setorigin(e, org);
52         e.projectiledeathtype = deathtype;
53         e.dmg = coredamage;
54         e.dmg_edge = edgedamage;
55         e.dmg_radius = rad;
56         e.dmg_force = vlen(force);
57         e.velocity = force;
58         e.oldorigin_x = compressShortVector(e.velocity);
59         e.species = bloodtype;
60
61         Net_LinkEntity(e, false, 0.2, Damage_DamageInfo_SendEntity);
62 }
63
64 void UpdateFrags(entity player, float f)
65 {
66         PlayerTeamScore_AddScore(player, f);
67 }
68
69 void GiveFrags (entity attacker, entity targ, float f, int deathtype)
70 {SELFPARAM();
71         // TODO route through PlayerScores instead
72         if(gameover) return;
73
74         if(f < 0)
75         {
76                 if(targ == attacker)
77                 {
78                         // suicide
79                         PlayerScore_Add(attacker, SP_SUICIDES, 1);
80                 }
81                 else
82                 {
83                         // teamkill
84                         PlayerScore_Add(attacker, SP_KILLS, -1); // or maybe add a teamkills field?
85                 }
86         }
87         else
88         {
89                 // regular frag
90                 PlayerScore_Add(attacker, SP_KILLS, 1);
91                 if(targ.playerid)
92                         PS_GR_P_ADDVAL(attacker, sprintf("kills-%d", targ.playerid), 1);
93         }
94
95         PlayerScore_Add(targ, SP_DEATHS, 1);
96
97         if(targ != attacker) // not for suicides
98         if(g_weaponarena_random)
99         {
100                 // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
101                 float culprit;
102                 culprit = DEATH_WEAPONOF(deathtype);
103                 if(!culprit)
104                         culprit = attacker.weapon;
105                 else if(!(attacker.weapons & WepSet_FromWeapon(culprit)))
106                         culprit = attacker.weapon;
107
108                 if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER.m_id) // WEAPONTODO: Shouldn't this be in a mutator?
109                 {
110                         // no exchange
111                 }
112                 else
113                 {
114                         if(!GiveFrags_randomweapons)
115                         {
116                                 GiveFrags_randomweapons = spawn();
117                                 GiveFrags_randomweapons.classname = "GiveFrags_randomweapons";
118                         }
119
120                         if(warmup_stage)
121                                 GiveFrags_randomweapons.weapons = WARMUP_START_WEAPONS;
122                         else
123                                 GiveFrags_randomweapons.weapons = start_weapons;
124
125                         // all others (including the culprit): remove
126                         GiveFrags_randomweapons.weapons &= ~attacker.weapons;
127                         GiveFrags_randomweapons.weapons &= ~WepSet_FromWeapon(culprit);
128
129                         // among the remaining ones, choose one by random
130                         W_RandomWeapons(GiveFrags_randomweapons, 1);
131
132                         if(GiveFrags_randomweapons.weapons)
133                         {
134                                 attacker.weapons |= GiveFrags_randomweapons.weapons;
135                                 attacker.weapons &= ~WepSet_FromWeapon(culprit);
136                         }
137                 }
138
139                 // after a frag, choose another random weapon set
140                 if (!(attacker.weapons & WepSet_FromWeapon(attacker.weapon)))
141                         W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker));
142         }
143
144         // FIXME fix the mess this is (we have REAL points now!)
145         if(MUTATOR_CALLHOOK(GiveFragsForKill, self, attacker, targ, f))
146         {
147                 f = frag_score;
148         }
149
150         attacker.totalfrags += f;
151
152         if(f)
153                 UpdateFrags(attacker, f);
154 }
155
156 string AppendItemcodes(string s, entity player)
157 {
158         float w;
159         w = player.weapon;
160         //if(w == 0)
161         //      w = player.switchweapon;
162         if(w == 0)
163                 w = player.cnt; // previous weapon!
164         s = strcat(s, ftos(w));
165         if(time < player.strength_finished)
166                 s = strcat(s, "S");
167         if(time < player.invincible_finished)
168                 s = strcat(s, "I");
169         if(player.flagcarried != world)
170                 s = strcat(s, "F");
171         if(player.BUTTON_CHAT)
172                 s = strcat(s, "T");
173         if(player.kh_next)
174                 s = strcat(s, "K");
175         return s;
176 }
177
178 void LogDeath(string mode, int deathtype, entity killer, entity killed)
179 {
180         string s;
181         if(!autocvar_sv_eventlog)
182                 return;
183         s = strcat(":kill:", mode);
184         s = strcat(s, ":", ftos(killer.playerid));
185         s = strcat(s, ":", ftos(killed.playerid));
186         s = strcat(s, ":type=", Deathtype_Name(deathtype));
187         s = strcat(s, ":items=");
188         s = AppendItemcodes(s, killer);
189         if(killed != killer)
190         {
191                 s = strcat(s, ":victimitems=");
192                 s = AppendItemcodes(s, killed);
193         }
194         GameLogEcho(s);
195 }
196
197 void Obituary_SpecialDeath(
198         entity notif_target,
199         float murder,
200         int deathtype,
201         string s1, string s2, string s3,
202         float f1, float f2, float f3)
203 {
204         if(DEATH_ISSPECIAL(deathtype))
205         {
206                 entity deathent = deathtypes[(deathtype - DT_FIRST)];
207                 if (!deathent) { backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); return; }
208
209                 if(murder)
210                 {
211                         if(deathent.death_msgmurder)
212                         {
213                                 Send_Notification_WOCOVA(
214                                         NOTIF_ONE,
215                                         notif_target,
216                                         MSG_MULTI,
217                                         deathent.death_msgmurder.nent_id,
218                                         s1, s2, s3, "",
219                                         f1, f2, f3, 0
220                                 );
221                                 Send_Notification_WOCOVA(
222                                         NOTIF_ALL_EXCEPT,
223                                         notif_target,
224                                         MSG_INFO,
225                                         deathent.death_msgmurder.nent_msginfo.nent_id,
226                                         s1, s2, s3, "",
227                                         f1, f2, f3, 0
228                                 );
229                         }
230                 }
231                 else
232                 {
233                         if(deathent.death_msgself)
234                         {
235                                 Send_Notification_WOCOVA(
236                                         NOTIF_ONE,
237                                         notif_target,
238                                         MSG_MULTI,
239                                         deathent.death_msgself.nent_id,
240                                         s1, s2, s3, "",
241                                         f1, f2, f3, 0
242                                 );
243                                 Send_Notification_WOCOVA(
244                                         NOTIF_ALL_EXCEPT,
245                                         notif_target,
246                                         MSG_INFO,
247                                         deathent.death_msgself.nent_msginfo.nent_id,
248                                         s1, s2, s3, "",
249                                         f1, f2, f3, 0
250                                 );
251                         }
252                 }
253         }
254         else { backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); return; }
255 }
256
257 float Obituary_WeaponDeath(
258         entity notif_target,
259         float murder,
260         int deathtype,
261         string s1, string s2, string s3,
262         float f1, float f2)
263 {
264         float death_weapon = DEATH_WEAPONOF(deathtype);
265         if(death_weapon)
266         {
267                 w_deathtype = deathtype;
268                 Weapon w = get_weaponinfo(death_weapon);
269                 int death_message = ((murder) ? w.wr_killmessage : w.wr_suicidemessage)(w);
270                 w_deathtype = false;
271
272                 if (death_message)
273                 {
274                         Send_Notification_WOCOVA(
275                                 NOTIF_ONE,
276                                 notif_target,
277                                 MSG_MULTI,
278                                 death_message,
279                                 s1, s2, s3, "",
280                                 f1, f2, 0, 0
281                         );
282                         Send_Notification_WOCOVA(
283                                 NOTIF_ALL_EXCEPT,
284                                 notif_target,
285                                 MSG_INFO,
286                                 msg_multi_notifs[death_message - 1].nent_msginfo.nent_id,
287                                 s1, s2, s3, "",
288                                 f1, f2, 0, 0
289                         );
290                 }
291                 else
292                 {
293                         LOG_TRACEF(
294                                 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
295                                 deathtype,
296                                 death_weapon
297                         );
298                 }
299
300                 return true;
301         }
302         return false;
303 }
304
305 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
306 {
307         // Sanity check
308         if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
309
310         // Declarations
311         float notif_firstblood = false;
312         float kill_count_to_attacker, kill_count_to_target;
313
314         // Set final information for the death
315         targ.death_origin = targ.origin;
316         if(targ != attacker) { targ.killer_origin = attacker.origin; }
317         string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
318
319         #ifdef NOTIFICATIONS_DEBUG
320         Debug_Notification(
321                 sprintf(
322                         "Obituary(%s, %s, %s, %s = %d);\n",
323                         attacker.netname,
324                         inflictor.netname,
325                         targ.netname,
326                         Deathtype_Name(deathtype),
327                         deathtype
328                 )
329         );
330         #endif
331
332         // =======
333         // SUICIDE
334         // =======
335         if(targ == attacker)
336         {
337                 if(DEATH_ISSPECIAL(deathtype))
338                 {
339                         if(deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
340                         {
341                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
342                         }
343                         else
344                         {
345                                 switch(deathtype)
346                                 {
347                                         case DEATH_MIRRORDAMAGE:
348                                         {
349                                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
350                                                 break;
351                                         }
352
353                                         default:
354                                         {
355                                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
356                                                 break;
357                                         }
358                                 }
359                         }
360                 }
361                 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0))
362                 {
363                         backtrace("SUICIDE: what the hell happened here?\n");
364                         return;
365                 }
366                 LogDeath("suicide", deathtype, targ, targ);
367                 GiveFrags(attacker, targ, -1, deathtype);
368         }
369
370         // ======
371         // MURDER
372         // ======
373         else if(IS_PLAYER(attacker))
374         {
375                 if(SAME_TEAM(attacker, targ))
376                 {
377                         LogDeath("tk", deathtype, attacker, targ);
378                         GiveFrags(attacker, targ, -1, deathtype);
379
380                         attacker.killcount = 0;
381
382                         Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
383                         Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
384                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(targ.team, INFO_DEATH_TEAMKILL_), targ.netname, attacker.netname, deathlocation, targ.killcount);
385
386                         // In this case, the death message will ALWAYS be "foo was betrayed by bar"
387                         // No need for specific death/weapon messages...
388                 }
389                 else
390                 {
391                         LogDeath("frag", deathtype, attacker, targ);
392                         GiveFrags(attacker, targ, 1, deathtype);
393
394                         attacker.taunt_soundtime = time + 1;
395                         attacker.killcount = attacker.killcount + 1;
396
397                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
398                                 case counta: \
399                                 { \
400                                         Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
401                                         PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
402                                         break; \
403                                 }
404                         switch(attacker.killcount)
405                         {
406                                 KILL_SPREE_LIST
407                                 default: break;
408                         }
409                         #undef SPREE_ITEM
410
411                         if(!checkrules_firstblood)
412                         {
413                                 checkrules_firstblood = true;
414                                 notif_firstblood = true; // modify the current messages so that they too show firstblood information
415                                 PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
416                                 PS_GR_P_ADDVAL(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
417
418                                 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
419                                 kill_count_to_attacker = -1;
420                                 kill_count_to_target = -2;
421                         }
422                         else
423                         {
424                                 kill_count_to_attacker = attacker.killcount;
425                                 kill_count_to_target = 0;
426                         }
427
428                         if(targ.istypefrag)
429                         {
430                                 Send_Notification(
431                                         NOTIF_ONE,
432                                         attacker,
433                                         MSG_CHOICE,
434                                         CHOICE_TYPEFRAG,
435                                         targ.netname,
436                                         kill_count_to_attacker,
437                                         (IS_BOT_CLIENT(targ) ? NO_MSG : targ.ping)
438                                 );
439                                 Send_Notification(
440                                         NOTIF_ONE,
441                                         targ,
442                                         MSG_CHOICE,
443                                         CHOICE_TYPEFRAGGED,
444                                         attacker.netname,
445                                         kill_count_to_target,
446                                         attacker.health,
447                                         attacker.armorvalue,
448                                         (IS_BOT_CLIENT(attacker) ? NO_MSG : attacker.ping)
449                                 );
450                         }
451                         else
452                         {
453                                 Send_Notification(
454                                         NOTIF_ONE,
455                                         attacker,
456                                         MSG_CHOICE,
457                                         CHOICE_FRAG,
458                                         targ.netname,
459                                         kill_count_to_attacker,
460                                         (IS_BOT_CLIENT(targ) ? NO_MSG : targ.ping)
461                                 );
462                                 Send_Notification(
463                                         NOTIF_ONE,
464                                         targ,
465                                         MSG_CHOICE,
466                                         CHOICE_FRAGGED,
467                                         attacker.netname,
468                                         kill_count_to_target,
469                                         attacker.health,
470                                         attacker.armorvalue,
471                                         (IS_BOT_CLIENT(attacker) ? NO_MSG : attacker.ping)
472                                 );
473                         }
474
475                         float f3 = 0;
476                         if(deathtype == DEATH_BUFF)
477                                 f3 = attacker.buffs;
478
479                         if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker))
480                                 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker, f3);
481                 }
482         }
483
484         // =============
485         // ACCIDENT/TRAP
486         // =============
487         else
488         {
489                 switch(deathtype)
490                 {
491                         // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
492                         // Later on you will only be able to make custom messages using DEATH_CUSTOM,
493                         // and there will be a REAL DEATH_VOID implementation which mappers will use.
494                         case DEATH_HURTTRIGGER:
495                         {
496                                 Obituary_SpecialDeath(targ, false, deathtype,
497                                         targ.netname,
498                                         inflictor.message,
499                                         deathlocation,
500                                         targ.killcount,
501                                         0,
502                                         0);
503                                 break;
504                         }
505
506                         case DEATH_CUSTOM:
507                         {
508                                 Obituary_SpecialDeath(targ, false, deathtype,
509                                         targ.netname,
510                                         ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
511                                         deathlocation,
512                                         targ.killcount,
513                                         0,
514                                         0);
515                                 break;
516                         }
517
518                         default:
519                         {
520                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
521                                 break;
522                         }
523                 }
524
525                 LogDeath("accident", deathtype, targ, targ);
526                 GiveFrags(targ, targ, -1, deathtype);
527
528                 if(PlayerScore_Add(targ, SP_SCORE, 0) == -5)
529                 {
530                         Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
531                         PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
532                 }
533         }
534
535         // reset target kill count
536         if(targ.killcount) { targ.killcount = 0; }
537 }
538
539 void Ice_Think()
540 {SELFPARAM();
541         if(!self.owner.frozen || self.owner.iceblock != self)
542         {
543                 remove(self);
544                 return;
545         }
546         setorigin(self, self.owner.origin - '0 0 16');
547         self.nextthink = time;
548 }
549
550 void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
551 {SELFPARAM();
552         if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // only specified entities can be freezed
553                 return;
554
555         if(targ.frozen)
556                 return;
557
558         float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
559
560         targ.frozen = frozen_type;
561         targ.revive_progress = ((frozen_type == 3) ? 1 : 0);
562         targ.health = ((frozen_type == 3) ? targ_maxhealth : 1);
563         targ.revive_speed = freeze_time;
564         self.bot_attack = false;
565
566         entity ice, head;
567         ice = spawn();
568         ice.owner = targ;
569         ice.classname = "ice";
570         ice.scale = targ.scale;
571         ice.think = Ice_Think;
572         ice.nextthink = time;
573         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
574         setmodel(ice, MDL_ICE);
575         ice.alpha = 1;
576         ice.colormod = Team_ColorRGB(targ.team);
577         ice.glowmod = ice.colormod;
578         targ.iceblock = ice;
579         targ.revival_time = 0;
580
581         WITH(entity, self, ice, Ice_Think());
582
583         RemoveGrapplingHook(targ);
584
585         FOR_EACH_PLAYER(head)
586         if(head.hook.aiment == targ)
587                 RemoveGrapplingHook(head);
588
589         // add waypoint
590         if(show_waypoint)
591                 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
592 }
593
594 void Unfreeze (entity targ)
595 {
596         if(!targ.frozen)
597                 return;
598
599         if(targ.frozen && targ.frozen != 3) // only reset health if target was frozen
600                 targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
601
602         entity head;
603         targ.frozen = 0;
604         targ.revive_progress = 0;
605         targ.revival_time = time;
606         self.bot_attack = true;
607
608         WaypointSprite_Kill(targ.waypointsprite_attached);
609
610         FOR_EACH_PLAYER(head)
611         if(head.hook.aiment == targ)
612                 RemoveGrapplingHook(head);
613
614         // remove the ice block
615         if(targ.iceblock)
616                 remove(targ.iceblock);
617         targ.iceblock = world;
618 }
619
620 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
621 {SELFPARAM();
622         float mirrordamage;
623         float mirrorforce;
624         float complainteamdamage = 0;
625         entity attacker_save;
626         mirrordamage = 0;
627         mirrorforce = 0;
628
629         if (gameover || targ.killcount == -666)
630                 return;
631
632         setself(targ);
633         damage_targ = targ;
634         damage_inflictor = inflictor;
635         damage_attacker = attacker;
636                 attacker_save = attacker;
637
638         if(IS_PLAYER(targ))
639                 if(targ.hook)
640                         if(targ.hook.aiment)
641                                 if(targ.hook.aiment == attacker)
642                                         RemoveGrapplingHook(targ); // STOP THAT, you parasite!
643
644         // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
645         if(DEATH_ISWEAPON(deathtype, WEP_HOOK.m_id) || DEATH_ISWEAPON(deathtype, WEP_TUBA.m_id))
646         {
647                 if(IS_PLAYER(targ))
648                         if(SAME_TEAM(targ, attacker))
649                         {
650                                 setself(this);
651                                 return;
652                         }
653         }
654
655         if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
656         {
657                 // exit the vehicle before killing (fixes a crash)
658                 if(IS_PLAYER(targ) && targ.vehicle)
659                         vehicles_exit(VHEF_RELEASE);
660
661                 // These are ALWAYS lethal
662                 // No damage modification here
663                 // Instead, prepare the victim for his death...
664                 targ.armorvalue = 0;
665                 targ.spawnshieldtime = 0;
666                 targ.health = 0.9; // this is < 1
667                 targ.flags -= targ.flags & FL_GODMODE;
668                 damage = 100000;
669         }
670         else if(deathtype == DEATH_MIRRORDAMAGE || deathtype == DEATH_NOAMMO)
671         {
672                 // no processing
673         }
674         else
675         {
676                 // nullify damage if teamplay is on
677                 if(deathtype != DEATH_TELEFRAG)
678                 if(IS_PLAYER(attacker))
679                 {
680                         if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
681                         {
682                                 damage = 0;
683                                 force = '0 0 0';
684                         }
685                         else if(SAME_TEAM(attacker, targ))
686                         {
687                                 if(autocvar_teamplay_mode == 1)
688                                         damage = 0;
689                                 else if(attacker != targ)
690                                 {
691                                         if(autocvar_teamplay_mode == 3)
692                                                 damage = 0;
693                                         else if(autocvar_teamplay_mode == 4)
694                                         {
695                                                 if(IS_PLAYER(targ) && targ.deadflag == DEAD_NO)
696                                                 {
697                                                         attacker.dmg_team = attacker.dmg_team + damage;
698                                                         complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
699                                                         if(complainteamdamage > 0)
700                                                                 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
701                                                         mirrorforce = autocvar_g_mirrordamage * vlen(force);
702                                                         damage = autocvar_g_friendlyfire * damage;
703                                                         // mirrordamage will be used LATER
704
705                                                         if(autocvar_g_mirrordamage_virtual)
706                                                         {
707                                                                 vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
708                                                                 attacker.dmg_take += v.x;
709                                                                 attacker.dmg_save += v.y;
710                                                                 attacker.dmg_inflictor = inflictor;
711                                                                 mirrordamage = v.z;
712                                                                 mirrorforce = 0;
713                                                         }
714
715                                                         if(autocvar_g_friendlyfire_virtual)
716                                                         {
717                                                                 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
718                                                                 targ.dmg_take += v.x;
719                                                                 targ.dmg_save += v.y;
720                                                                 targ.dmg_inflictor = inflictor;
721                                                                 damage = 0;
722                                                                 if(!autocvar_g_friendlyfire_virtual_force)
723                                                                         force = '0 0 0';
724                                                         }
725                                                 }
726                                                 else
727                                                         damage = 0;
728                                         }
729                                 }
730                         }
731                 }
732
733                 if (!DEATH_ISSPECIAL(deathtype))
734                 {
735                         damage *= g_weapondamagefactor;
736                         mirrordamage *= g_weapondamagefactor;
737                         complainteamdamage *= g_weapondamagefactor;
738                         force = force * g_weaponforcefactor;
739                         mirrorforce *= g_weaponforcefactor;
740                 }
741
742                 // should this be changed at all? If so, in what way?
743                 MUTATOR_CALLHOOK(PlayerDamage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force);
744                 damage = frag_damage;
745                 mirrordamage = frag_mirrordamage;
746                 force = frag_force;
747
748                 if(targ.frozen)
749                 if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE)
750                 {
751                         if(autocvar_g_freezetag_revive_falldamage > 0)
752                         if(deathtype == DEATH_FALL)
753                         if(damage >= autocvar_g_freezetag_revive_falldamage)
754                         {
755                                 Unfreeze(targ);
756                                 targ.health = autocvar_g_freezetag_revive_falldamage_health;
757                                 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
758                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
759                                 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
760                         }
761
762                         damage = 0;
763                         force *= autocvar_g_freezetag_frozen_force;
764                 }
765
766                 if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger)
767                 {
768                         Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
769
770                         setself(targ);
771                         entity spot = SelectSpawnPoint (false);
772
773                         if(spot)
774                         {
775                                 damage = 0;
776                                 self.deadflag = DEAD_NO;
777
778                                 self.angles = spot.angles;
779
780                                 self.effects = 0;
781                                 self.effects |= EF_TELEPORT_BIT;
782
783                                 self.angles_z = 0; // never spawn tilted even if the spot says to
784                                 self.fixangle = true; // turn this way immediately
785                                 self.velocity = '0 0 0';
786                                 self.avelocity = '0 0 0';
787                                 self.punchangle = '0 0 0';
788                                 self.punchvector = '0 0 0';
789                                 self.oldvelocity = self.velocity;
790
791                                 self.spawnorigin = spot.origin;
792                                 setorigin (self, spot.origin + '0 0 1' * (1 - self.mins.z - 24));
793                                 // don't reset back to last position, even if new position is stuck in solid
794                                 self.oldorigin = self.origin;
795                                 self.prevorigin = self.origin;
796
797                                 Send_Effect(EFFECT_TELEPORT, self.origin, '0 0 0', 1);
798                         }
799
800                         setself(this);
801                 }
802
803                 if(!g_instagib)
804                 {
805                         // apply strength multiplier
806                         if (attacker.items & ITEM_Strength.m_itemid)
807                         {
808                                 if(targ == attacker)
809                                 {
810                                         damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
811                                         force = force * autocvar_g_balance_powerup_strength_selfforce;
812                                 }
813                                 else
814                                 {
815                                         damage = damage * autocvar_g_balance_powerup_strength_damage;
816                                         force = force * autocvar_g_balance_powerup_strength_force;
817                                 }
818                         }
819
820                         // apply invincibility multiplier
821                         if (targ.items & ITEM_Shield.m_itemid)
822                                 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
823                 }
824
825                 if (targ == attacker)
826                         damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
827
828                 // count the damage
829                 if(attacker)
830                 if(!targ.deadflag)
831                 if(deathtype != DEATH_BUFF)
832                 if(targ.takedamage == DAMAGE_AIM)
833                 if(targ != attacker)
834                 {
835                         entity victim;
836                         if(IS_VEHICLE(targ) && targ.owner)
837                                 victim = targ.owner;
838                         else
839                                 victim = targ;
840
841                         if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim))
842                         {
843                                 if(DIFF_TEAM(victim, attacker) && !victim.frozen)
844                                 {
845                                         if(damage > 0)
846                                         {
847                                                 if(deathtype != DEATH_FIRE)
848                                                 {
849                                                         if(victim.BUTTON_CHAT)
850                                                                 attacker.typehitsound += 1;
851                                                         else
852                                                                 attacker.damage_dealt += damage;
853                                                 }
854
855                                                 damage_goodhits += 1;
856                                                 damage_gooddamage += damage;
857
858                                                 if (!DEATH_ISSPECIAL(deathtype))
859                                                 {
860                                                         if(IS_PLAYER(targ)) // don't do this for vehicles
861                                                         if(IsFlying(victim))
862                                                                 yoda = 1;
863                                                 }
864                                         }
865                                 }
866                                 else
867                                 {
868                                         if(deathtype != DEATH_FIRE)
869                                         {
870                                                 attacker.typehitsound += 1;
871                                         }
872                                         if(complainteamdamage > 0)
873                                                 if(time > attacker.teamkill_complain)
874                                                 {
875                                                         attacker.teamkill_complain = time + 5;
876                                                         attacker.teamkill_soundtime = time + 0.4;
877                                                         attacker.teamkill_soundsource = targ;
878                                                 }
879                                 }
880                         }
881                 }
882         }
883
884         // apply push
885         if (self.damageforcescale)
886         if (vlen(force))
887         if (!IS_PLAYER(self) || time >= self.spawnshieldtime || self == attacker)
888         {
889                 vector farce = damage_explosion_calcpush(self.damageforcescale * force, self.velocity, autocvar_g_balance_damagepush_speedfactor);
890                 if(self.movetype == MOVETYPE_PHYSICS)
891                 {
892                         entity farcent;
893                         farcent = spawn();
894                         farcent.classname = "farce";
895                         farcent.enemy = self;
896                         farcent.movedir = farce * 10;
897                         if(self.mass)
898                                 farcent.movedir = farcent.movedir * self.mass;
899                         farcent.origin = hitloc;
900                         farcent.forcetype = FORCETYPE_FORCEATPOS;
901                         farcent.nextthink = time + 0.1;
902                         farcent.think = SUB_Remove;
903                 }
904                 else
905                 {
906                         self.velocity = self.velocity + farce;
907                         self.move_velocity = self.velocity;
908                 }
909                 self.flags &= ~FL_ONGROUND;
910                 self.move_flags &= ~FL_ONGROUND;
911                 UpdateCSQCProjectile(self);
912         }
913         // apply damage
914         if (damage != 0 || (self.damageforcescale && vlen(force)))
915         if (self.event_damage)
916                 self.event_damage (inflictor, attacker, damage, deathtype, hitloc, force);
917         setself(this);
918
919         // apply mirror damage if any
920         if(mirrordamage > 0 || mirrorforce > 0)
921         {
922                 attacker = attacker_save;
923
924                 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
925                 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE, attacker.origin, force);
926         }
927 }
928
929 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float inflictorselfdamage, float forceintensity, int deathtype, entity directhitentity)
930         // Returns total damage applies to creatures
931 {
932         entity  targ;
933         vector  force;
934         float   total_damage_to_creatures;
935         entity  next;
936         float   tfloordmg;
937         float   tfloorforce;
938
939         float stat_damagedone;
940
941         if(RadiusDamage_running)
942         {
943                 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
944                 return 0;
945         }
946
947         RadiusDamage_running = 1;
948
949         tfloordmg = autocvar_g_throughfloor_damage;
950         tfloorforce = autocvar_g_throughfloor_force;
951
952         total_damage_to_creatures = 0;
953
954         if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
955                 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA.m_id) // do not send tuba damage (bandwidth hog)
956                 {
957                         force = inflictorvelocity;
958                         if(vlen(force) == 0)
959                                 force = '0 0 -1';
960                         else
961                                 force = normalize(force);
962                         if(forceintensity >= 0)
963                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
964                         else
965                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
966                 }
967
968         stat_damagedone = 0;
969
970         targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
971         while (targ)
972         {
973                 next = targ.chain;
974                 if ((targ != inflictor) || inflictorselfdamage)
975                 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
976                 if (targ.takedamage)
977                 {
978                         vector nearest;
979                         vector diff;
980                         float power;
981
982                         // LordHavoc: measure distance to nearest point on target (not origin)
983                         // (this guarentees 100% damage on a touch impact)
984                         nearest = targ.WarpZone_findradius_nearest;
985                         diff = targ.WarpZone_findradius_dist;
986                         // round up a little on the damage to ensure full damage on impacts
987                         // and turn the distance into a fraction of the radius
988                         power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
989                         //bprint(" ");
990                         //bprint(ftos(power));
991                         //if (targ == attacker)
992                         //      print(ftos(power), "\n");
993                         if (power > 0)
994                         {
995                                 float finaldmg;
996                                 if (power > 1)
997                                         power = 1;
998                                 finaldmg = coredamage * power + edgedamage * (1 - power);
999                                 if (finaldmg > 0)
1000                                 {
1001                                         float a;
1002                                         float c;
1003                                         vector hitloc;
1004                                         vector myblastorigin;
1005                                         vector center;
1006
1007                                         myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
1008
1009                                         // if it's a player, use the view origin as reference
1010                                         center = CENTER_OR_VIEWOFS(targ);
1011
1012                                         force = normalize(center - myblastorigin);
1013                                         force = force * (finaldmg / coredamage) * forceintensity;
1014                                         hitloc = nearest;
1015
1016                                         if(deathtype & WEP_BLASTER.m_id)
1017                                                 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
1018
1019                                         if(targ != directhitentity)
1020                                         {
1021                                                 float hits;
1022                                                 float total;
1023                                                 float hitratio;
1024                                                 float mininv_f, mininv_d;
1025
1026                                                 // test line of sight to multiple positions on box,
1027                                                 // and do damage if any of them hit
1028                                                 hits = 0;
1029
1030                                                 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1031                                                 // so for a given max stddev:
1032                                                 // n = (1 / (2 * max stddev of hitratio))^2
1033
1034                                                 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1035                                                 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1036
1037                                                 if(autocvar_g_throughfloor_debug)
1038                                                         LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1039
1040
1041                                                 total = 0.25 * pow(max(mininv_f, mininv_d), 2);
1042
1043                                                 if(autocvar_g_throughfloor_debug)
1044                                                         LOG_INFOF(" steps=%f", total);
1045
1046
1047                                                 if (IS_PLAYER(targ))
1048                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1049                                                 else
1050                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1051
1052                                                 if(autocvar_g_throughfloor_debug)
1053                                                         LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1054
1055                                                 for(c = 0; c < total; ++c)
1056                                                 {
1057                                                         //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1058                                                         WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1059                                                         if (trace_fraction == 1 || trace_ent == targ)
1060                                                         {
1061                                                                 ++hits;
1062                                                                 if (hits > 1)
1063                                                                         hitloc = hitloc + nearest;
1064                                                                 else
1065                                                                         hitloc = nearest;
1066                                                         }
1067                                                         nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1068                                                         nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1069                                                         nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1070                                                 }
1071
1072                                                 nearest = hitloc * (1 / max(1, hits));
1073                                                 hitratio = (hits / total);
1074                                                 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1075                                                 finaldmg = finaldmg * a;
1076                                                 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1077                                                 force = force * a;
1078
1079                                                 if(autocvar_g_throughfloor_debug)
1080                                                         LOG_INFOF(" D=%f F=%f\n", finaldmg, vlen(force));
1081                                         }
1082
1083                                         //if (targ == attacker)
1084                                         //{
1085                                         //      print("hits ", ftos(hits), " / ", ftos(total));
1086                                         //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1087                                         //      print(" (", ftos(a), ")\n");
1088                                         //}
1089                                         if(finaldmg || vlen(force))
1090                                         {
1091                                                 if(targ.iscreature)
1092                                                 {
1093                                                         total_damage_to_creatures += finaldmg;
1094
1095                                                         if(accuracy_isgooddamage(attacker, targ))
1096                                                                 stat_damagedone += finaldmg;
1097                                                 }
1098
1099                                                 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1100                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
1101                                                 else
1102                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
1103                                         }
1104                                 }
1105                         }
1106                 }
1107                 targ = next;
1108         }
1109
1110         RadiusDamage_running = 0;
1111
1112         if(!DEATH_ISSPECIAL(deathtype))
1113                 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1114
1115         return total_damage_to_creatures;
1116 }
1117
1118 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, entity directhitentity)
1119 {
1120         return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, directhitentity);
1121 }
1122
1123 float Fire_IsBurning(entity e)
1124 {
1125         return (time < e.fire_endtime);
1126 }
1127
1128 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1129 {
1130         float dps;
1131         float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1132
1133         if(IS_PLAYER(e))
1134         {
1135                 if(e.deadflag)
1136                         return -1;
1137         }
1138         else
1139         {
1140                 if(!e.fire_burner)
1141                 {
1142                         // print("adding a fire burner to ", e.classname, "\n");
1143                         e.fire_burner = spawn();
1144                         e.fire_burner.classname = "fireburner";
1145                         e.fire_burner.think = fireburner_think;
1146                         e.fire_burner.nextthink = time;
1147                         e.fire_burner.owner = e;
1148                 }
1149         }
1150
1151         t = max(t, 0.1);
1152         dps = d / t;
1153         if(Fire_IsBurning(e))
1154         {
1155                 mintime = e.fire_endtime - time;
1156                 maxtime = max(mintime, t);
1157
1158                 mindps = e.fire_damagepersec;
1159                 maxdps = max(mindps, dps);
1160
1161                 if(maxtime > mintime || maxdps > mindps)
1162                 {
1163                         // Constraints:
1164
1165                         // damage we have right now
1166                         mindamage = mindps * mintime;
1167
1168                         // damage we want to get
1169                         maxdamage = mindamage + d;
1170
1171                         // but we can't exceed maxtime * maxdps!
1172                         totaldamage = min(maxdamage, maxtime * maxdps);
1173
1174                         // LEMMA:
1175                         // Look at:
1176                         // totaldamage = min(mindamage + d, maxtime * maxdps)
1177                         // We see:
1178                         // totaldamage <= maxtime * maxdps
1179                         // ==> totaldamage / maxdps <= maxtime.
1180                         // We also see:
1181                         // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1182                         //                     >= min(mintime, maxtime)
1183                         // ==> totaldamage / maxdps >= mintime.
1184
1185                         /*
1186                         // how long do we damage then?
1187                         // at least as long as before
1188                         // but, never exceed maxdps
1189                         totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1190                         */
1191
1192                         // alternate:
1193                         // at most as long as maximum allowed
1194                         // but, never below mindps
1195                         totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1196
1197                         // assuming t > mintime, dps > mindps:
1198                         // we get d = t * dps = maxtime * maxdps
1199                         // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1200                         // totaldamage / maxdps = maxtime
1201                         // totaldamage / mindps > totaldamage / maxdps = maxtime
1202                         // FROM THIS:
1203                         // a) totaltime = max(mintime, maxtime) = maxtime
1204                         // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1205
1206                         // assuming t <= mintime:
1207                         // we get maxtime = mintime
1208                         // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1209                         // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1210
1211                         // assuming dps <= mindps:
1212                         // we get mindps = maxdps.
1213                         // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1214                         // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1215                         // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1216
1217                         e.fire_damagepersec = totaldamage / totaltime;
1218                         e.fire_endtime = time + totaltime;
1219                         if(totaldamage > 1.2 * mindamage)
1220                         {
1221                                 e.fire_deathtype = dt;
1222                                 if(e.fire_owner != o)
1223                                 {
1224                                         e.fire_owner = o;
1225                                         e.fire_hitsound = false;
1226                                 }
1227                         }
1228                         if(accuracy_isgooddamage(o, e))
1229                                 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1230                         return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1231                 }
1232                 else
1233                         return 0;
1234         }
1235         else
1236         {
1237                 e.fire_damagepersec = dps;
1238                 e.fire_endtime = time + t;
1239                 e.fire_deathtype = dt;
1240                 e.fire_owner = o;
1241                 e.fire_hitsound = false;
1242                 if(accuracy_isgooddamage(o, e))
1243                         accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1244                 return d;
1245         }
1246 }
1247
1248 void Fire_ApplyDamage(entity e)
1249 {
1250         float t, d, hi, ty;
1251         entity o;
1252
1253         if (!Fire_IsBurning(e))
1254                 return;
1255
1256         for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1257         if(IS_NOT_A_CLIENT(o))
1258                 o = e.fire_owner;
1259
1260         // water and slime stop fire
1261         if(e.waterlevel)
1262         if(e.watertype != CONTENT_LAVA)
1263                 e.fire_endtime = 0;
1264
1265         // ice stops fire
1266         if(e.frozen)
1267                 e.fire_endtime = 0;
1268
1269         t = min(frametime, e.fire_endtime - time);
1270         d = e.fire_damagepersec * t;
1271
1272         hi = e.fire_owner.damage_dealt;
1273         ty = e.fire_owner.typehitsound;
1274         Damage(e, e, e.fire_owner, d, e.fire_deathtype, e.origin, '0 0 0');
1275         if(e.fire_hitsound && e.fire_owner)
1276         {
1277                 e.fire_owner.damage_dealt = hi;
1278                 e.fire_owner.typehitsound = ty;
1279         }
1280         e.fire_hitsound = true;
1281
1282         if (!IS_INDEPENDENT_PLAYER(e))
1283         if(!e.frozen)
1284         FOR_EACH_PLAYER(other) if(e != other)
1285         {
1286                 if(IS_PLAYER(other))
1287                 if(other.deadflag == DEAD_NO)
1288                 if (!IS_INDEPENDENT_PLAYER(other))
1289                 if(boxesoverlap(e.absmin, e.absmax, other.absmin, other.absmax))
1290                 {
1291                         t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1292                         d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1293                         Fire_AddDamage(other, o, d, t, DEATH_FIRE);
1294                 }
1295         }
1296 }
1297
1298 void Fire_ApplyEffect(entity e)
1299 {
1300         if(Fire_IsBurning(e))
1301                 e.effects |= EF_FLAME;
1302         else
1303                 e.effects &= ~EF_FLAME;
1304 }
1305
1306 void fireburner_think()
1307 {SELFPARAM();
1308         // for players, this is done in the regular loop
1309         if(wasfreed(self.owner))
1310         {
1311                 remove(self);
1312                 return;
1313         }
1314         Fire_ApplyEffect(self.owner);
1315         if(!Fire_IsBurning(self.owner))
1316         {
1317                 self.owner.fire_burner = world;
1318                 remove(self);
1319                 return;
1320         }
1321         Fire_ApplyDamage(self.owner);
1322         self.nextthink = time;
1323 }