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