]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_damage.qc
Merge branch 'master' into terencehill/tooltips_cleanup
[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         self.bot_attack = false;
564
565         entity ice, head;
566         ice = spawn();
567         ice.owner = targ;
568         ice.classname = "ice";
569         ice.scale = targ.scale;
570         ice.think = Ice_Think;
571         ice.nextthink = time;
572         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
573         setmodel(ice, MDL_ICE);
574         ice.alpha = 1;
575         ice.colormod = Team_ColorRGB(targ.team);
576         ice.glowmod = ice.colormod;
577         targ.iceblock = ice;
578         targ.revival_time = 0;
579
580         WITH(entity, self, ice, Ice_Think());
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)
596                 return;
597
598         if(targ.frozen && targ.frozen != 3) // only reset health if target was frozen
599                 targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
600
601         entity head;
602         targ.frozen = 0;
603         targ.revive_progress = 0;
604         targ.revival_time = time;
605         self.bot_attack = true;
606
607         WaypointSprite_Kill(targ.waypointsprite_attached);
608
609         FOR_EACH_PLAYER(head)
610         if(head.hook.aiment == targ)
611                 RemoveGrapplingHook(head);
612
613         // remove the ice block
614         if(targ.iceblock)
615                 remove(targ.iceblock);
616         targ.iceblock = world;
617 }
618
619 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
620 {SELFPARAM();
621         float mirrordamage;
622         float mirrorforce;
623         float complainteamdamage = 0;
624         entity attacker_save;
625         mirrordamage = 0;
626         mirrorforce = 0;
627
628         if (gameover || targ.killcount == -666)
629                 return;
630
631         setself(targ);
632         damage_targ = targ;
633         damage_inflictor = inflictor;
634         damage_attacker = attacker;
635                 attacker_save = attacker;
636
637         if(IS_PLAYER(targ))
638                 if(targ.hook)
639                         if(targ.hook.aiment)
640                                 if(targ.hook.aiment == attacker)
641                                         RemoveGrapplingHook(targ); // STOP THAT, you parasite!
642
643         // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
644         if(DEATH_ISWEAPON(deathtype, WEP_HOOK.m_id) || DEATH_ISWEAPON(deathtype, WEP_TUBA.m_id))
645         {
646                 if(IS_PLAYER(targ))
647                         if(SAME_TEAM(targ, attacker))
648                         {
649                                 setself(this);
650                                 return;
651                         }
652         }
653
654         if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
655         {
656                 // exit the vehicle before killing (fixes a crash)
657                 if(IS_PLAYER(targ) && targ.vehicle)
658                         vehicles_exit(VHEF_RELEASE);
659
660                 // These are ALWAYS lethal
661                 // No damage modification here
662                 // Instead, prepare the victim for his death...
663                 targ.armorvalue = 0;
664                 targ.spawnshieldtime = 0;
665                 targ.health = 0.9; // this is < 1
666                 targ.flags -= targ.flags & FL_GODMODE;
667                 damage = 100000;
668         }
669         else if(deathtype == DEATH_MIRRORDAMAGE || deathtype == DEATH_NOAMMO)
670         {
671                 // no processing
672         }
673         else
674         {
675                 // nullify damage if teamplay is on
676                 if(deathtype != DEATH_TELEFRAG)
677                 if(IS_PLAYER(attacker))
678                 {
679                         if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
680                         {
681                                 damage = 0;
682                                 force = '0 0 0';
683                         }
684                         else if(SAME_TEAM(attacker, targ))
685                         {
686                                 if(autocvar_teamplay_mode == 1)
687                                         damage = 0;
688                                 else if(attacker != targ)
689                                 {
690                                         if(autocvar_teamplay_mode == 3)
691                                                 damage = 0;
692                                         else if(autocvar_teamplay_mode == 4)
693                                         {
694                                                 if(IS_PLAYER(targ) && targ.deadflag == DEAD_NO)
695                                                 {
696                                                         attacker.dmg_team = attacker.dmg_team + damage;
697                                                         complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
698                                                         if(complainteamdamage > 0)
699                                                                 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
700                                                         mirrorforce = autocvar_g_mirrordamage * vlen(force);
701                                                         damage = autocvar_g_friendlyfire * damage;
702                                                         // mirrordamage will be used LATER
703
704                                                         if(autocvar_g_mirrordamage_virtual)
705                                                         {
706                                                                 vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
707                                                                 attacker.dmg_take += v.x;
708                                                                 attacker.dmg_save += v.y;
709                                                                 attacker.dmg_inflictor = inflictor;
710                                                                 mirrordamage = v.z;
711                                                                 mirrorforce = 0;
712                                                         }
713
714                                                         if(autocvar_g_friendlyfire_virtual)
715                                                         {
716                                                                 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
717                                                                 targ.dmg_take += v.x;
718                                                                 targ.dmg_save += v.y;
719                                                                 targ.dmg_inflictor = inflictor;
720                                                                 damage = 0;
721                                                                 if(!autocvar_g_friendlyfire_virtual_force)
722                                                                         force = '0 0 0';
723                                                         }
724                                                 }
725                                                 else
726                                                         damage = 0;
727                                         }
728                                 }
729                         }
730                 }
731
732                 if (!DEATH_ISSPECIAL(deathtype))
733                 {
734                         damage *= g_weapondamagefactor;
735                         mirrordamage *= g_weapondamagefactor;
736                         complainteamdamage *= g_weapondamagefactor;
737                         force = force * g_weaponforcefactor;
738                         mirrorforce *= g_weaponforcefactor;
739                 }
740
741                 // should this be changed at all? If so, in what way?
742                 MUTATOR_CALLHOOK(PlayerDamage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force);
743                 damage = frag_damage;
744                 mirrordamage = frag_mirrordamage;
745                 force = frag_force;
746
747                 if(targ.frozen)
748                 if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE)
749                 {
750                         if(autocvar_g_freezetag_revive_falldamage > 0)
751                         if(deathtype == DEATH_FALL)
752                         if(damage >= autocvar_g_freezetag_revive_falldamage)
753                         {
754                                 Unfreeze(targ);
755                                 targ.health = autocvar_g_freezetag_revive_falldamage_health;
756                                 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
757                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
758                                 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
759                         }
760
761                         damage = 0;
762                         force *= autocvar_g_freezetag_frozen_force;
763                 }
764
765                 if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger)
766                 {
767                         Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
768
769                         setself(targ);
770                         entity spot = SelectSpawnPoint (false);
771
772                         if(spot)
773                         {
774                                 damage = 0;
775                                 self.deadflag = DEAD_NO;
776
777                                 self.angles = spot.angles;
778
779                                 self.effects = 0;
780                                 self.effects |= EF_TELEPORT_BIT;
781
782                                 self.angles_z = 0; // never spawn tilted even if the spot says to
783                                 self.fixangle = true; // turn this way immediately
784                                 self.velocity = '0 0 0';
785                                 self.avelocity = '0 0 0';
786                                 self.punchangle = '0 0 0';
787                                 self.punchvector = '0 0 0';
788                                 self.oldvelocity = self.velocity;
789
790                                 self.spawnorigin = spot.origin;
791                                 setorigin (self, spot.origin + '0 0 1' * (1 - self.mins.z - 24));
792                                 // don't reset back to last position, even if new position is stuck in solid
793                                 self.oldorigin = self.origin;
794                                 self.prevorigin = self.origin;
795
796                                 Send_Effect(EFFECT_TELEPORT, self.origin, '0 0 0', 1);
797                         }
798
799                         setself(this);
800                 }
801
802                 if(!g_instagib)
803                 {
804                         // apply strength multiplier
805                         if (attacker.items & ITEM_Strength.m_itemid)
806                         {
807                                 if(targ == attacker)
808                                 {
809                                         damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
810                                         force = force * autocvar_g_balance_powerup_strength_selfforce;
811                                 }
812                                 else
813                                 {
814                                         damage = damage * autocvar_g_balance_powerup_strength_damage;
815                                         force = force * autocvar_g_balance_powerup_strength_force;
816                                 }
817                         }
818
819                         // apply invincibility multiplier
820                         if (targ.items & ITEM_Shield.m_itemid)
821                                 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
822                 }
823
824                 if (targ == attacker)
825                         damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
826
827                 // count the damage
828                 if(attacker)
829                 if(!targ.deadflag)
830                 if(deathtype != DEATH_BUFF)
831                 if(targ.takedamage == DAMAGE_AIM)
832                 if(targ != attacker)
833                 {
834                         entity victim;
835                         if(IS_VEHICLE(targ) && targ.owner)
836                                 victim = targ.owner;
837                         else
838                                 victim = targ;
839
840                         if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim))
841                         {
842                                 if(DIFF_TEAM(victim, attacker) && !victim.frozen)
843                                 {
844                                         if(damage > 0)
845                                         {
846                                                 if(deathtype != DEATH_FIRE)
847                                                 {
848                                                         if(victim.BUTTON_CHAT)
849                                                                 attacker.typehitsound += 1;
850                                                         else
851                                                                 attacker.damage_dealt += damage;
852                                                 }
853
854                                                 damage_goodhits += 1;
855                                                 damage_gooddamage += damage;
856
857                                                 if (!DEATH_ISSPECIAL(deathtype))
858                                                 {
859                                                         if(IS_PLAYER(targ)) // don't do this for vehicles
860                                                         if(IsFlying(victim))
861                                                                 yoda = 1;
862                                                 }
863                                         }
864                                 }
865                                 else
866                                 {
867                                         if(deathtype != DEATH_FIRE)
868                                         {
869                                                 attacker.typehitsound += 1;
870                                         }
871                                         if(complainteamdamage > 0)
872                                                 if(time > attacker.teamkill_complain)
873                                                 {
874                                                         attacker.teamkill_complain = time + 5;
875                                                         attacker.teamkill_soundtime = time + 0.4;
876                                                         attacker.teamkill_soundsource = targ;
877                                                 }
878                                 }
879                         }
880                 }
881         }
882
883         // apply push
884         if (self.damageforcescale)
885         if (vlen(force))
886         if (!IS_PLAYER(self) || time >= self.spawnshieldtime || self == attacker)
887         {
888                 vector farce = damage_explosion_calcpush(self.damageforcescale * force, self.velocity, autocvar_g_balance_damagepush_speedfactor);
889                 if(self.movetype == MOVETYPE_PHYSICS)
890                 {
891                         entity farcent;
892                         farcent = spawn();
893                         farcent.classname = "farce";
894                         farcent.enemy = self;
895                         farcent.movedir = farce * 10;
896                         if(self.mass)
897                                 farcent.movedir = farcent.movedir * self.mass;
898                         farcent.origin = hitloc;
899                         farcent.forcetype = FORCETYPE_FORCEATPOS;
900                         farcent.nextthink = time + 0.1;
901                         farcent.think = SUB_Remove;
902                 }
903                 else
904                 {
905                         self.velocity = self.velocity + farce;
906                         self.move_velocity = self.velocity;
907                 }
908                 self.flags &= ~FL_ONGROUND;
909                 self.move_flags &= ~FL_ONGROUND;
910                 UpdateCSQCProjectile(self);
911         }
912         // apply damage
913         if (damage != 0 || (self.damageforcescale && vlen(force)))
914         if (self.event_damage)
915                 self.event_damage (inflictor, attacker, damage, deathtype, hitloc, force);
916         setself(this);
917
918         // apply mirror damage if any
919         if(mirrordamage > 0 || mirrorforce > 0)
920         {
921                 attacker = attacker_save;
922
923                 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
924                 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE, attacker.origin, force);
925         }
926 }
927
928 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)
929         // Returns total damage applies to creatures
930 {
931         entity  targ;
932         vector  force;
933         float   total_damage_to_creatures;
934         entity  next;
935         float   tfloordmg;
936         float   tfloorforce;
937
938         float stat_damagedone;
939
940         if(RadiusDamage_running)
941         {
942                 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
943                 return 0;
944         }
945
946         RadiusDamage_running = 1;
947
948         tfloordmg = autocvar_g_throughfloor_damage;
949         tfloorforce = autocvar_g_throughfloor_force;
950
951         total_damage_to_creatures = 0;
952
953         if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
954                 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA.m_id) // do not send tuba damage (bandwidth hog)
955                 {
956                         force = inflictorvelocity;
957                         if(vlen(force) == 0)
958                                 force = '0 0 -1';
959                         else
960                                 force = normalize(force);
961                         if(forceintensity >= 0)
962                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
963                         else
964                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
965                 }
966
967         stat_damagedone = 0;
968
969         targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
970         while (targ)
971         {
972                 next = targ.chain;
973                 if ((targ != inflictor) || inflictorselfdamage)
974                 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
975                 if (targ.takedamage)
976                 {
977                         vector nearest;
978                         vector diff;
979                         float power;
980
981                         // LordHavoc: measure distance to nearest point on target (not origin)
982                         // (this guarentees 100% damage on a touch impact)
983                         nearest = targ.WarpZone_findradius_nearest;
984                         diff = targ.WarpZone_findradius_dist;
985                         // round up a little on the damage to ensure full damage on impacts
986                         // and turn the distance into a fraction of the radius
987                         power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
988                         //bprint(" ");
989                         //bprint(ftos(power));
990                         //if (targ == attacker)
991                         //      print(ftos(power), "\n");
992                         if (power > 0)
993                         {
994                                 float finaldmg;
995                                 if (power > 1)
996                                         power = 1;
997                                 finaldmg = coredamage * power + edgedamage * (1 - power);
998                                 if (finaldmg > 0)
999                                 {
1000                                         float a;
1001                                         float c;
1002                                         vector hitloc;
1003                                         vector myblastorigin;
1004                                         vector center;
1005
1006                                         myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
1007
1008                                         // if it's a player, use the view origin as reference
1009                                         center = CENTER_OR_VIEWOFS(targ);
1010
1011                                         force = normalize(center - myblastorigin);
1012                                         force = force * (finaldmg / coredamage) * forceintensity;
1013                                         hitloc = nearest;
1014
1015                                         if(deathtype & WEP_BLASTER.m_id)
1016                                                 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
1017
1018                                         if(targ != directhitentity)
1019                                         {
1020                                                 float hits;
1021                                                 float total;
1022                                                 float hitratio;
1023                                                 float mininv_f, mininv_d;
1024
1025                                                 // test line of sight to multiple positions on box,
1026                                                 // and do damage if any of them hit
1027                                                 hits = 0;
1028
1029                                                 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1030                                                 // so for a given max stddev:
1031                                                 // n = (1 / (2 * max stddev of hitratio))^2
1032
1033                                                 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1034                                                 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1035
1036                                                 if(autocvar_g_throughfloor_debug)
1037                                                         LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1038
1039
1040                                                 total = 0.25 * pow(max(mininv_f, mininv_d), 2);
1041
1042                                                 if(autocvar_g_throughfloor_debug)
1043                                                         LOG_INFOF(" steps=%f", total);
1044
1045
1046                                                 if (IS_PLAYER(targ))
1047                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1048                                                 else
1049                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1050
1051                                                 if(autocvar_g_throughfloor_debug)
1052                                                         LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1053
1054                                                 for(c = 0; c < total; ++c)
1055                                                 {
1056                                                         //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1057                                                         WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1058                                                         if (trace_fraction == 1 || trace_ent == targ)
1059                                                         {
1060                                                                 ++hits;
1061                                                                 if (hits > 1)
1062                                                                         hitloc = hitloc + nearest;
1063                                                                 else
1064                                                                         hitloc = nearest;
1065                                                         }
1066                                                         nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1067                                                         nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1068                                                         nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1069                                                 }
1070
1071                                                 nearest = hitloc * (1 / max(1, hits));
1072                                                 hitratio = (hits / total);
1073                                                 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1074                                                 finaldmg = finaldmg * a;
1075                                                 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1076                                                 force = force * a;
1077
1078                                                 if(autocvar_g_throughfloor_debug)
1079                                                         LOG_INFOF(" D=%f F=%f\n", finaldmg, vlen(force));
1080                                         }
1081
1082                                         //if (targ == attacker)
1083                                         //{
1084                                         //      print("hits ", ftos(hits), " / ", ftos(total));
1085                                         //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1086                                         //      print(" (", ftos(a), ")\n");
1087                                         //}
1088                                         if(finaldmg || vlen(force))
1089                                         {
1090                                                 if(targ.iscreature)
1091                                                 {
1092                                                         total_damage_to_creatures += finaldmg;
1093
1094                                                         if(accuracy_isgooddamage(attacker, targ))
1095                                                                 stat_damagedone += finaldmg;
1096                                                 }
1097
1098                                                 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1099                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
1100                                                 else
1101                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
1102                                         }
1103                                 }
1104                         }
1105                 }
1106                 targ = next;
1107         }
1108
1109         RadiusDamage_running = 0;
1110
1111         if(!DEATH_ISSPECIAL(deathtype))
1112                 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1113
1114         return total_damage_to_creatures;
1115 }
1116
1117 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, entity directhitentity)
1118 {
1119         return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, directhitentity);
1120 }
1121
1122 float Fire_IsBurning(entity e)
1123 {
1124         return (time < e.fire_endtime);
1125 }
1126
1127 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1128 {
1129         float dps;
1130         float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1131
1132         if(IS_PLAYER(e))
1133         {
1134                 if(e.deadflag)
1135                         return -1;
1136         }
1137         else
1138         {
1139                 if(!e.fire_burner)
1140                 {
1141                         // print("adding a fire burner to ", e.classname, "\n");
1142                         e.fire_burner = spawn();
1143                         e.fire_burner.classname = "fireburner";
1144                         e.fire_burner.think = fireburner_think;
1145                         e.fire_burner.nextthink = time;
1146                         e.fire_burner.owner = e;
1147                 }
1148         }
1149
1150         t = max(t, 0.1);
1151         dps = d / t;
1152         if(Fire_IsBurning(e))
1153         {
1154                 mintime = e.fire_endtime - time;
1155                 maxtime = max(mintime, t);
1156
1157                 mindps = e.fire_damagepersec;
1158                 maxdps = max(mindps, dps);
1159
1160                 if(maxtime > mintime || maxdps > mindps)
1161                 {
1162                         // Constraints:
1163
1164                         // damage we have right now
1165                         mindamage = mindps * mintime;
1166
1167                         // damage we want to get
1168                         maxdamage = mindamage + d;
1169
1170                         // but we can't exceed maxtime * maxdps!
1171                         totaldamage = min(maxdamage, maxtime * maxdps);
1172
1173                         // LEMMA:
1174                         // Look at:
1175                         // totaldamage = min(mindamage + d, maxtime * maxdps)
1176                         // We see:
1177                         // totaldamage <= maxtime * maxdps
1178                         // ==> totaldamage / maxdps <= maxtime.
1179                         // We also see:
1180                         // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1181                         //                     >= min(mintime, maxtime)
1182                         // ==> totaldamage / maxdps >= mintime.
1183
1184                         /*
1185                         // how long do we damage then?
1186                         // at least as long as before
1187                         // but, never exceed maxdps
1188                         totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1189                         */
1190
1191                         // alternate:
1192                         // at most as long as maximum allowed
1193                         // but, never below mindps
1194                         totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1195
1196                         // assuming t > mintime, dps > mindps:
1197                         // we get d = t * dps = maxtime * maxdps
1198                         // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1199                         // totaldamage / maxdps = maxtime
1200                         // totaldamage / mindps > totaldamage / maxdps = maxtime
1201                         // FROM THIS:
1202                         // a) totaltime = max(mintime, maxtime) = maxtime
1203                         // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1204
1205                         // assuming t <= mintime:
1206                         // we get maxtime = mintime
1207                         // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1208                         // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1209
1210                         // assuming dps <= mindps:
1211                         // we get mindps = maxdps.
1212                         // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1213                         // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1214                         // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1215
1216                         e.fire_damagepersec = totaldamage / totaltime;
1217                         e.fire_endtime = time + totaltime;
1218                         if(totaldamage > 1.2 * mindamage)
1219                         {
1220                                 e.fire_deathtype = dt;
1221                                 if(e.fire_owner != o)
1222                                 {
1223                                         e.fire_owner = o;
1224                                         e.fire_hitsound = false;
1225                                 }
1226                         }
1227                         if(accuracy_isgooddamage(o, e))
1228                                 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1229                         return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1230                 }
1231                 else
1232                         return 0;
1233         }
1234         else
1235         {
1236                 e.fire_damagepersec = dps;
1237                 e.fire_endtime = time + t;
1238                 e.fire_deathtype = dt;
1239                 e.fire_owner = o;
1240                 e.fire_hitsound = false;
1241                 if(accuracy_isgooddamage(o, e))
1242                         accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1243                 return d;
1244         }
1245 }
1246
1247 void Fire_ApplyDamage(entity e)
1248 {
1249         float t, d, hi, ty;
1250         entity o;
1251
1252         if (!Fire_IsBurning(e))
1253                 return;
1254
1255         for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1256         if(IS_NOT_A_CLIENT(o))
1257                 o = e.fire_owner;
1258
1259         // water and slime stop fire
1260         if(e.waterlevel)
1261         if(e.watertype != CONTENT_LAVA)
1262                 e.fire_endtime = 0;
1263
1264         // ice stops fire
1265         if(e.frozen)
1266                 e.fire_endtime = 0;
1267
1268         t = min(frametime, e.fire_endtime - time);
1269         d = e.fire_damagepersec * t;
1270
1271         hi = e.fire_owner.damage_dealt;
1272         ty = e.fire_owner.typehitsound;
1273         Damage(e, e, e.fire_owner, d, e.fire_deathtype, e.origin, '0 0 0');
1274         if(e.fire_hitsound && e.fire_owner)
1275         {
1276                 e.fire_owner.damage_dealt = hi;
1277                 e.fire_owner.typehitsound = ty;
1278         }
1279         e.fire_hitsound = true;
1280
1281         if (!IS_INDEPENDENT_PLAYER(e))
1282         if(!e.frozen)
1283         FOR_EACH_PLAYER(other) if(e != other)
1284         {
1285                 if(IS_PLAYER(other))
1286                 if(other.deadflag == DEAD_NO)
1287                 if (!IS_INDEPENDENT_PLAYER(other))
1288                 if(boxesoverlap(e.absmin, e.absmax, other.absmin, other.absmax))
1289                 {
1290                         t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1291                         d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1292                         Fire_AddDamage(other, o, d, t, DEATH_FIRE);
1293                 }
1294         }
1295 }
1296
1297 void Fire_ApplyEffect(entity e)
1298 {
1299         if(Fire_IsBurning(e))
1300                 e.effects |= EF_FLAME;
1301         else
1302                 e.effects &= ~EF_FLAME;
1303 }
1304
1305 void fireburner_think()
1306 {SELFPARAM();
1307         // for players, this is done in the regular loop
1308         if(wasfreed(self.owner))
1309         {
1310                 remove(self);
1311                 return;
1312         }
1313         Fire_ApplyEffect(self.owner);
1314         if(!Fire_IsBurning(self.owner))
1315         {
1316                 self.owner.fire_burner = world;
1317                 remove(self);
1318                 return;
1319         }
1320         Fire_ApplyDamage(self.owner);
1321         self.nextthink = time;
1322 }