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