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