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