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