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