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