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