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