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