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