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