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