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