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