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