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