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