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