]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_damage.qc
Merge branch 'master' into Mario/turrets
[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
569         entity ice, head;
570         ice = spawn();
571         ice.owner = targ;
572         ice.classname = "ice";
573         ice.scale = targ.scale;
574         ice.think = Ice_Think;
575         ice.nextthink = time;
576         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
577         setmodel(ice, "models/ice/ice.md3");
578         ice.alpha = 1;
579         ice.colormod = Team_ColorRGB(targ.team);
580         ice.glowmod = ice.colormod;
581         targ.iceblock = ice;
582         targ.revival_time = 0;
583
584         entity oldself;
585         oldself = self;
586         self = ice;
587         Ice_Think();
588         self = oldself;
589
590         RemoveGrapplingHook(targ);
591
592         FOR_EACH_PLAYER(head)
593         if(head.hook.aiment == targ)
594                 RemoveGrapplingHook(head);
595
596         // add waypoint
597         if(show_waypoint)
598                 WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT, '0.25 0.90 1');
599 }
600
601 void Unfreeze (entity targ)
602 {
603         if(targ.frozen && targ.frozen != 3) // only reset health if target was frozen
604                 targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
605
606         entity head;
607         targ.frozen = 0;
608         targ.revive_progress = 0;
609         targ.revival_time = time;
610
611         WaypointSprite_Kill(targ.waypointsprite_attached);
612
613         FOR_EACH_PLAYER(head)
614         if(head.hook.aiment == targ)
615                 RemoveGrapplingHook(head);
616
617         // remove the ice block
618         if(targ.iceblock)
619                 remove(targ.iceblock);
620         targ.iceblock = world;
621 }
622
623 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
624 {
625         float mirrordamage;
626         float mirrorforce;
627         float complainteamdamage = 0;
628         entity attacker_save;
629         mirrordamage = 0;
630         mirrorforce = 0;
631
632         if (gameover || targ.killcount == -666)
633                 return;
634
635         entity oldself;
636         oldself = self;
637         self = targ;
638         damage_targ = targ;
639         damage_inflictor = inflictor;
640         damage_attacker = attacker;
641                 attacker_save = attacker;
642
643         if(IS_PLAYER(targ))
644                 if(targ.hook)
645                         if(targ.hook.aiment)
646                                 if(targ.hook.aiment == attacker)
647                                         RemoveGrapplingHook(targ); // STOP THAT, you parasite!
648
649         // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
650         if(DEATH_ISWEAPON(deathtype, WEP_HOOK.m_id) || DEATH_ISWEAPON(deathtype, WEP_TUBA.m_id))
651         {
652                 if(IS_PLAYER(targ))
653                         if(SAME_TEAM(targ, attacker))
654                         {
655                                 self = oldself;
656                                 return;
657                         }
658         }
659
660         if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
661         {
662                 // exit the vehicle before killing (fixes a crash)
663                 if(IS_PLAYER(targ) && targ.vehicle)
664                         vehicles_exit(VHEF_RELEASE);
665
666                 // These are ALWAYS lethal
667                 // No damage modification here
668                 // Instead, prepare the victim for his death...
669                 targ.armorvalue = 0;
670                 targ.spawnshieldtime = 0;
671                 targ.health = 0.9; // this is < 1
672                 targ.flags -= targ.flags & FL_GODMODE;
673                 damage = 100000;
674         }
675         else if(deathtype == DEATH_MIRRORDAMAGE || deathtype == DEATH_NOAMMO)
676         {
677                 // no processing
678         }
679         else
680         {
681                 // nullify damage if teamplay is on
682                 if(deathtype != DEATH_TELEFRAG)
683                 if(IS_PLAYER(attacker))
684                 {
685                         if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
686                         {
687                                 damage = 0;
688                                 force = '0 0 0';
689                         }
690                         else if(SAME_TEAM(attacker, targ))
691                         {
692                                 if(autocvar_teamplay_mode == 1)
693                                         damage = 0;
694                                 else if(attacker != targ)
695                                 {
696                                         if(autocvar_teamplay_mode == 3)
697                                                 damage = 0;
698                                         else if(autocvar_teamplay_mode == 4)
699                                         {
700                                                 if(IS_PLAYER(targ) && targ.deadflag == DEAD_NO)
701                                                 {
702                                                         attacker.dmg_team = attacker.dmg_team + damage;
703                                                         complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
704                                                         if(complainteamdamage > 0)
705                                                                 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
706                                                         mirrorforce = autocvar_g_mirrordamage * vlen(force);
707                                                         damage = autocvar_g_friendlyfire * damage;
708                                                         // mirrordamage will be used LATER
709
710                                                         if(autocvar_g_mirrordamage_virtual)
711                                                         {
712                                                                 vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
713                                                                 attacker.dmg_take += v.x;
714                                                                 attacker.dmg_save += v.y;
715                                                                 attacker.dmg_inflictor = inflictor;
716                                                                 mirrordamage = v.z;
717                                                                 mirrorforce = 0;
718                                                         }
719
720                                                         if(autocvar_g_friendlyfire_virtual)
721                                                         {
722                                                                 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
723                                                                 targ.dmg_take += v.x;
724                                                                 targ.dmg_save += v.y;
725                                                                 targ.dmg_inflictor = inflictor;
726                                                                 damage = 0;
727                                                                 if(!autocvar_g_friendlyfire_virtual_force)
728                                                                         force = '0 0 0';
729                                                         }
730                                                 }
731                                                 else
732                                                         damage = 0;
733                                         }
734                                 }
735                         }
736                 }
737
738                 if (!DEATH_ISSPECIAL(deathtype))
739                 {
740                         damage *= g_weapondamagefactor;
741                         mirrordamage *= g_weapondamagefactor;
742                         complainteamdamage *= g_weapondamagefactor;
743                         force = force * g_weaponforcefactor;
744                         mirrorforce *= g_weaponforcefactor;
745                 }
746
747                 // should this be changed at all? If so, in what way?
748                 MUTATOR_CALLHOOK(PlayerDamage_Calculate, attacker, targ, deathtype, damage, mirrordamage, force);
749                 damage = frag_damage;
750                 mirrordamage = frag_mirrordamage;
751                 force = frag_force;
752
753                 if(targ.frozen)
754                 if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE)
755                 {
756                         if(autocvar_g_freezetag_revive_falldamage > 0)
757                         if(deathtype == DEATH_FALL)
758                         if(damage >= autocvar_g_freezetag_revive_falldamage)
759                         {
760                                 Unfreeze(targ);
761                                 targ.health = autocvar_g_freezetag_revive_falldamage_health;
762                                 Send_Effect("iceorglass", targ.origin, '0 0 0', 3);
763                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
764                                 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
765                         }
766
767                         damage = 0;
768                         force *= autocvar_g_freezetag_frozen_force;
769                 }
770
771                 if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger)
772                 {
773                         Send_Effect("teleport", targ.origin, '0 0 0', 1);
774
775                         entity oldself = self;
776                         self = targ;
777                         entity spot = SelectSpawnPoint (false);
778
779                         if(spot)
780                         {
781                                 damage = 0;
782                                 self.deadflag = DEAD_NO;
783
784                                 self.angles = spot.angles;
785
786                                 self.effects = 0;
787                                 self.effects |= EF_TELEPORT_BIT;
788
789                                 self.angles_z = 0; // never spawn tilted even if the spot says to
790                                 self.fixangle = true; // turn this way immediately
791                                 self.velocity = '0 0 0';
792                                 self.avelocity = '0 0 0';
793                                 self.punchangle = '0 0 0';
794                                 self.punchvector = '0 0 0';
795                                 self.oldvelocity = self.velocity;
796
797                                 self.spawnorigin = spot.origin;
798                                 setorigin (self, spot.origin + '0 0 1' * (1 - self.mins.z - 24));
799                                 // don't reset back to last position, even if new position is stuck in solid
800                                 self.oldorigin = self.origin;
801                                 self.prevorigin = self.origin;
802
803                                 Send_Effect("teleport", self.origin, '0 0 0', 1);
804                         }
805
806                         self = oldself;
807                 }
808
809                 if(!g_instagib)
810                 {
811                         // apply strength multiplier
812                         if (attacker.items & ITEM_Strength.m_itemid)
813                         {
814                                 if(targ == attacker)
815                                 {
816                                         damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
817                                         force = force * autocvar_g_balance_powerup_strength_selfforce;
818                                 }
819                                 else
820                                 {
821                                         damage = damage * autocvar_g_balance_powerup_strength_damage;
822                                         force = force * autocvar_g_balance_powerup_strength_force;
823                                 }
824                         }
825
826                         // apply invincibility multiplier
827                         if (targ.items & ITEM_Shield.m_itemid)
828                                 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
829                 }
830
831                 if (targ == attacker)
832                         damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
833
834                 // count the damage
835                 if(attacker)
836                 if(!targ.deadflag)
837                 if(deathtype != DEATH_BUFF)
838                 if(targ.takedamage == DAMAGE_AIM)
839                 if(targ != attacker)
840                 {
841                         entity victim;
842                         if(IS_VEHICLE(targ) && targ.owner)
843                                 victim = targ.owner;
844                         else
845                                 victim = targ;
846
847                         if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim))
848                         {
849                                 if(DIFF_TEAM(victim, attacker) && !victim.frozen)
850                                 {
851                                         if(damage > 0)
852                                         {
853                                                 if(deathtype != DEATH_FIRE)
854                                                 {
855                                                         if(victim.BUTTON_CHAT)
856                                                                 attacker.typehitsound += 1;
857                                                         else
858                                                                 attacker.damage_dealt += damage;
859                                                 }
860
861                                                 damage_goodhits += 1;
862                                                 damage_gooddamage += damage;
863
864                                                 if (!DEATH_ISSPECIAL(deathtype))
865                                                 {
866                                                         if(IS_PLAYER(targ)) // don't do this for vehicles
867                                                         if(IsFlying(victim))
868                                                                 yoda = 1;
869                                                 }
870                                         }
871                                 }
872                                 else
873                                 {
874                                         if(deathtype != DEATH_FIRE)
875                                         {
876                                                 attacker.typehitsound += 1;
877                                         }
878                                         if(complainteamdamage > 0)
879                                                 if(time > attacker.teamkill_complain)
880                                                 {
881                                                         attacker.teamkill_complain = time + 5;
882                                                         attacker.teamkill_soundtime = time + 0.4;
883                                                         attacker.teamkill_soundsource = targ;
884                                                 }
885                                 }
886                         }
887                 }
888         }
889
890         // apply push
891         if (self.damageforcescale)
892         if (vlen(force))
893         if (!IS_PLAYER(self) || time >= self.spawnshieldtime || self == attacker)
894         {
895                 vector farce = damage_explosion_calcpush(self.damageforcescale * force, self.velocity, autocvar_g_balance_damagepush_speedfactor);
896                 if(self.movetype == MOVETYPE_PHYSICS)
897                 {
898                         entity farcent;
899                         farcent = spawn();
900                         farcent.classname = "farce";
901                         farcent.enemy = self;
902                         farcent.movedir = farce * 10;
903                         if(self.mass)
904                                 farcent.movedir = farcent.movedir * self.mass;
905                         farcent.origin = hitloc;
906                         farcent.forcetype = FORCETYPE_FORCEATPOS;
907                         farcent.nextthink = time + 0.1;
908                         farcent.think = SUB_Remove;
909                 }
910                 else
911                 {
912                         self.velocity = self.velocity + farce;
913                         self.move_velocity = self.velocity;
914                 }
915                 self.flags &= ~FL_ONGROUND;
916                 self.move_flags &= ~FL_ONGROUND;
917                 UpdateCSQCProjectile(self);
918         }
919         // apply damage
920         if (damage != 0 || (self.damageforcescale && vlen(force)))
921         if (self.event_damage)
922                 self.event_damage (inflictor, attacker, damage, deathtype, hitloc, force);
923         self = oldself;
924
925         // apply mirror damage if any
926         if(mirrordamage > 0 || mirrorforce > 0)
927         {
928                 attacker = attacker_save;
929
930                 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
931                 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE, attacker.origin, force);
932         }
933 }
934
935 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)
936         // Returns total damage applies to creatures
937 {
938         entity  targ;
939         vector  force;
940         float   total_damage_to_creatures;
941         entity  next;
942         float   tfloordmg;
943         float   tfloorforce;
944
945         float stat_damagedone;
946
947         if(RadiusDamage_running)
948         {
949                 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
950                 return 0;
951         }
952
953         RadiusDamage_running = 1;
954
955         tfloordmg = autocvar_g_throughfloor_damage;
956         tfloorforce = autocvar_g_throughfloor_force;
957
958         total_damage_to_creatures = 0;
959
960         if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
961                 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA.m_id) // do not send tuba damage (bandwidth hog)
962                 {
963                         force = inflictorvelocity;
964                         if(vlen(force) == 0)
965                                 force = '0 0 -1';
966                         else
967                                 force = normalize(force);
968                         if(forceintensity >= 0)
969                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
970                         else
971                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
972                 }
973
974         stat_damagedone = 0;
975
976         targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
977         while (targ)
978         {
979                 next = targ.chain;
980                 if ((targ != inflictor) || inflictorselfdamage)
981                 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
982                 if (targ.takedamage)
983                 {
984                         vector nearest;
985                         vector diff;
986                         float power;
987
988                         // LordHavoc: measure distance to nearest point on target (not origin)
989                         // (this guarentees 100% damage on a touch impact)
990                         nearest = targ.WarpZone_findradius_nearest;
991                         diff = targ.WarpZone_findradius_dist;
992                         // round up a little on the damage to ensure full damage on impacts
993                         // and turn the distance into a fraction of the radius
994                         power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
995                         //bprint(" ");
996                         //bprint(ftos(power));
997                         //if (targ == attacker)
998                         //      print(ftos(power), "\n");
999                         if (power > 0)
1000                         {
1001                                 float finaldmg;
1002                                 if (power > 1)
1003                                         power = 1;
1004                                 finaldmg = coredamage * power + edgedamage * (1 - power);
1005                                 if (finaldmg > 0)
1006                                 {
1007                                         float a;
1008                                         float c;
1009                                         vector hitloc;
1010                                         vector myblastorigin;
1011                                         vector center;
1012
1013                                         myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
1014
1015                                         // if it's a player, use the view origin as reference
1016                                         center = CENTER_OR_VIEWOFS(targ);
1017
1018                                         force = normalize(center - myblastorigin);
1019                                         force = force * (finaldmg / coredamage) * forceintensity;
1020                                         hitloc = nearest;
1021
1022                                         if(deathtype & WEP_BLASTER.m_id)
1023                                                 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
1024
1025                                         if(targ != directhitentity)
1026                                         {
1027                                                 float hits;
1028                                                 float total;
1029                                                 float hitratio;
1030                                                 float mininv_f, mininv_d;
1031
1032                                                 // test line of sight to multiple positions on box,
1033                                                 // and do damage if any of them hit
1034                                                 hits = 0;
1035
1036                                                 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1037                                                 // so for a given max stddev:
1038                                                 // n = (1 / (2 * max stddev of hitratio))^2
1039
1040                                                 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1041                                                 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1042
1043                                                 if(autocvar_g_throughfloor_debug)
1044                                                         printf("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1045
1046
1047                                                 total = 0.25 * pow(max(mininv_f, mininv_d), 2);
1048
1049                                                 if(autocvar_g_throughfloor_debug)
1050                                                         printf(" steps=%f", total);
1051
1052
1053                                                 if (IS_PLAYER(targ))
1054                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1055                                                 else
1056                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1057
1058                                                 if(autocvar_g_throughfloor_debug)
1059                                                         printf(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1060
1061                                                 for(c = 0; c < total; ++c)
1062                                                 {
1063                                                         //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1064                                                         WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1065                                                         if (trace_fraction == 1 || trace_ent == targ)
1066                                                         {
1067                                                                 ++hits;
1068                                                                 if (hits > 1)
1069                                                                         hitloc = hitloc + nearest;
1070                                                                 else
1071                                                                         hitloc = nearest;
1072                                                         }
1073                                                         nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1074                                                         nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1075                                                         nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1076                                                 }
1077
1078                                                 nearest = hitloc * (1 / max(1, hits));
1079                                                 hitratio = (hits / total);
1080                                                 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1081                                                 finaldmg = finaldmg * a;
1082                                                 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1083                                                 force = force * a;
1084
1085                                                 if(autocvar_g_throughfloor_debug)
1086                                                         printf(" D=%f F=%f\n", finaldmg, vlen(force));
1087                                         }
1088
1089                                         //if (targ == attacker)
1090                                         //{
1091                                         //      print("hits ", ftos(hits), " / ", ftos(total));
1092                                         //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1093                                         //      print(" (", ftos(a), ")\n");
1094                                         //}
1095                                         if(finaldmg || vlen(force))
1096                                         {
1097                                                 if(targ.iscreature)
1098                                                 {
1099                                                         total_damage_to_creatures += finaldmg;
1100
1101                                                         if(accuracy_isgooddamage(attacker, targ))
1102                                                                 stat_damagedone += finaldmg;
1103                                                 }
1104
1105                                                 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1106                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
1107                                                 else
1108                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
1109                                         }
1110                                 }
1111                         }
1112                 }
1113                 targ = next;
1114         }
1115
1116         RadiusDamage_running = 0;
1117
1118         if(!DEATH_ISSPECIAL(deathtype))
1119                 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1120
1121         return total_damage_to_creatures;
1122 }
1123
1124 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, entity directhitentity)
1125 {
1126         return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, directhitentity);
1127 }
1128
1129 float Fire_IsBurning(entity e)
1130 {
1131         return (time < e.fire_endtime);
1132 }
1133
1134 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1135 {
1136         float dps;
1137         float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1138
1139         if(IS_PLAYER(e))
1140         {
1141                 if(e.deadflag)
1142                         return -1;
1143         }
1144         else
1145         {
1146                 if(!e.fire_burner)
1147                 {
1148                         // print("adding a fire burner to ", e.classname, "\n");
1149                         e.fire_burner = spawn();
1150                         e.fire_burner.classname = "fireburner";
1151                         e.fire_burner.think = fireburner_think;
1152                         e.fire_burner.nextthink = time;
1153                         e.fire_burner.owner = e;
1154                 }
1155         }
1156
1157         t = max(t, 0.1);
1158         dps = d / t;
1159         if(Fire_IsBurning(e))
1160         {
1161                 mintime = e.fire_endtime - time;
1162                 maxtime = max(mintime, t);
1163
1164                 mindps = e.fire_damagepersec;
1165                 maxdps = max(mindps, dps);
1166
1167                 if(maxtime > mintime || maxdps > mindps)
1168                 {
1169                         // Constraints:
1170
1171                         // damage we have right now
1172                         mindamage = mindps * mintime;
1173
1174                         // damage we want to get
1175                         maxdamage = mindamage + d;
1176
1177                         // but we can't exceed maxtime * maxdps!
1178                         totaldamage = min(maxdamage, maxtime * maxdps);
1179
1180                         // LEMMA:
1181                         // Look at:
1182                         // totaldamage = min(mindamage + d, maxtime * maxdps)
1183                         // We see:
1184                         // totaldamage <= maxtime * maxdps
1185                         // ==> totaldamage / maxdps <= maxtime.
1186                         // We also see:
1187                         // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1188                         //                     >= min(mintime, maxtime)
1189                         // ==> totaldamage / maxdps >= mintime.
1190
1191                         /*
1192                         // how long do we damage then?
1193                         // at least as long as before
1194                         // but, never exceed maxdps
1195                         totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1196                         */
1197
1198                         // alternate:
1199                         // at most as long as maximum allowed
1200                         // but, never below mindps
1201                         totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1202
1203                         // assuming t > mintime, dps > mindps:
1204                         // we get d = t * dps = maxtime * maxdps
1205                         // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1206                         // totaldamage / maxdps = maxtime
1207                         // totaldamage / mindps > totaldamage / maxdps = maxtime
1208                         // FROM THIS:
1209                         // a) totaltime = max(mintime, maxtime) = maxtime
1210                         // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1211
1212                         // assuming t <= mintime:
1213                         // we get maxtime = mintime
1214                         // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1215                         // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1216
1217                         // assuming dps <= mindps:
1218                         // we get mindps = maxdps.
1219                         // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1220                         // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1221                         // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1222
1223                         e.fire_damagepersec = totaldamage / totaltime;
1224                         e.fire_endtime = time + totaltime;
1225                         if(totaldamage > 1.2 * mindamage)
1226                         {
1227                                 e.fire_deathtype = dt;
1228                                 if(e.fire_owner != o)
1229                                 {
1230                                         e.fire_owner = o;
1231                                         e.fire_hitsound = false;
1232                                 }
1233                         }
1234                         if(accuracy_isgooddamage(o, e))
1235                                 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1236                         return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1237                 }
1238                 else
1239                         return 0;
1240         }
1241         else
1242         {
1243                 e.fire_damagepersec = dps;
1244                 e.fire_endtime = time + t;
1245                 e.fire_deathtype = dt;
1246                 e.fire_owner = o;
1247                 e.fire_hitsound = false;
1248                 if(accuracy_isgooddamage(o, e))
1249                         accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1250                 return d;
1251         }
1252 }
1253
1254 void Fire_ApplyDamage(entity e)
1255 {
1256         float t, d, hi, ty;
1257         entity o;
1258
1259         if (!Fire_IsBurning(e))
1260                 return;
1261
1262         for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1263         if(IS_NOT_A_CLIENT(o))
1264                 o = e.fire_owner;
1265
1266         // water and slime stop fire
1267         if(e.waterlevel)
1268         if(e.watertype != CONTENT_LAVA)
1269                 e.fire_endtime = 0;
1270
1271         // ice stops fire
1272         if(e.frozen)
1273                 e.fire_endtime = 0;
1274
1275         t = min(frametime, e.fire_endtime - time);
1276         d = e.fire_damagepersec * t;
1277
1278         hi = e.fire_owner.damage_dealt;
1279         ty = e.fire_owner.typehitsound;
1280         Damage(e, e, e.fire_owner, d, e.fire_deathtype, e.origin, '0 0 0');
1281         if(e.fire_hitsound && e.fire_owner)
1282         {
1283                 e.fire_owner.damage_dealt = hi;
1284                 e.fire_owner.typehitsound = ty;
1285         }
1286         e.fire_hitsound = true;
1287
1288         if (!IS_INDEPENDENT_PLAYER(e))
1289         if(!e.frozen)
1290         FOR_EACH_PLAYER(other) if(e != other)
1291         {
1292                 if(IS_PLAYER(other))
1293                 if(other.deadflag == DEAD_NO)
1294                 if (!IS_INDEPENDENT_PLAYER(other))
1295                 if(boxesoverlap(e.absmin, e.absmax, other.absmin, other.absmax))
1296                 {
1297                         t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1298                         d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1299                         Fire_AddDamage(other, o, d, t, DEATH_FIRE);
1300                 }
1301         }
1302 }
1303
1304 void Fire_ApplyEffect(entity e)
1305 {
1306         if(Fire_IsBurning(e))
1307                 e.effects |= EF_FLAME;
1308         else
1309                 e.effects &= ~EF_FLAME;
1310 }
1311
1312 void fireburner_think()
1313 {
1314         // for players, this is done in the regular loop
1315         if(wasfreed(self.owner))
1316         {
1317                 remove(self);
1318                 return;
1319         }
1320         Fire_ApplyEffect(self.owner);
1321         if(!Fire_IsBurning(self.owner))
1322         {
1323                 self.owner.fire_burner = world;
1324                 remove(self);
1325                 return;
1326         }
1327         Fire_ApplyDamage(self.owner);
1328         self.nextthink = time;
1329 }