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