]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/g_damage.qc
Fix the new rule...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
index 59da8e086f3e148131bf13480ca22cd180230808..fb179a37679b61679d570bb2cfcc285e55cf6ed6 100644 (file)
@@ -47,14 +47,13 @@ float checkrules_firstblood;
 float yoda;
 float damage_goodhits;
 float damage_gooddamage;
-float headshot;
-float damage_headshotbonus; // bonus multiplier for head shots, set to 0 after use
 
 .float dmg_team;
 .float teamkill_complain;
 .float teamkill_soundtime;
 .entity teamkill_soundsource;
 .entity pusher;
+.float istypefrag;
 .float taunt_soundtime;
 
 
@@ -85,15 +84,6 @@ float IsFlying(entity a)
        return 1;
 }
 
-vector GetHeadshotMins(entity targ)
-{
-       return '-0.5 0 0' * PL_HEAD_x + '0 -0.5 0' * PL_HEAD_y + '0 0 1' * (targ.maxs_z - PL_HEAD_z);
-}
-vector GetHeadshotMaxs(entity targ)
-{
-       return '0.5 0 0' * PL_HEAD_x + '0 0.5 0' * PL_HEAD_y + '0 0 1' * targ.maxs_z;
-}
-
 void UpdateFrags(entity player, float f)
 {
        PlayerTeamScore_AddScore(player, f);
@@ -130,17 +120,15 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
 
        PlayerScore_Add(targ, SP_DEATHS, 1);
 
-       if(g_arena || g_ca)
-               if(autocvar_g_arena_roundbased)
-                       return;
-
        if(targ != attacker) // not for suicides
        if(g_weaponarena_random)
        {
                // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
                float culprit;
                culprit = DEATH_WEAPONOF(deathtype);
-               if(!culprit || !WEPSET_CONTAINS_EW(attacker, culprit))
+               if(!culprit)
+                       culprit = attacker.weapon;
+               else if(!WEPSET_CONTAINS_EW(attacker, culprit))
                        culprit = attacker.weapon;
 
                if(g_weaponarena_random_with_laser && culprit == WEP_LASER)
@@ -194,11 +182,7 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
        else
        {
                self = oldself;
-               if(g_runematch)
-               {
-                       f = RunematchHandleFrags(attacker, targ, f);
-               }
-               else if(g_lms)
+               if(g_lms)
                {
                        // remove a life
                        float tl;
@@ -216,11 +200,6 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
                        }
                        f = 0;
                }
-               else if(g_ctf)
-               {
-                       if(g_ctf_ignore_frags)
-                               f = 0;
-               }
        }
 
        attacker.totalfrags += f;
@@ -229,44 +208,6 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
                UpdateFrags(attacker, f);
 }
 
-string Obituary_ExtraFragInfo(entity player) // Extra fragmessage information
-{
-       string health_output;
-       string ping_output;
-       string handicap_output;
-       string output;
-
-       if(autocvar_sv_fraginfo && ((autocvar_sv_fraginfo == 2) || inWarmupStage))
-       {
-               // health/armor of attacker (person who killed you)
-               if(autocvar_sv_fraginfo_stats && (player.health >= 1))
-                       health_output = strcat("^7(Health ^1", ftos(rint(player.health)), "^7 / Armor ^2", ftos(rint(player.armorvalue)), "^7)");
-               
-               // ping display
-               if(autocvar_sv_fraginfo_ping)
-                       ping_output = ((clienttype(player) == CLIENTTYPE_BOT) ? "^2Bot" : strcat("Ping ", ((player.ping >= 150) ? "^1" : "^2"), ftos(rint(player.ping)), "ms"));
-                       
-               // handicap display 
-               if(autocvar_sv_fraginfo_handicap) 
-               {
-                       if(autocvar_sv_fraginfo_handicap == 2)  
-                               handicap_output = strcat(output, strcat("Handicap ^2", ((player.cvar_cl_handicap <= 1) ? "Off" : ftos(rint(player.cvar_cl_handicap)))));
-                       else if(player.cvar_cl_handicap) // with _handicap 1, only show this if there actually is a handicap enabled.   
-                               handicap_output = strcat("Handicap ^2", ftos(rint(player.cvar_cl_handicap)));
-               }
-               
-               // format the string
-               output = strcat(health_output, (health_output ? ((ping_output || handicap_output) ? " ^7(" : "") : ((ping_output || handicap_output) ? "^7(" : "")), 
-                       ping_output, (handicap_output ? "^7 / " : ""), 
-                       handicap_output, ((ping_output || handicap_output) ? "^7)" : ""));
-               
-               // add new line to the beginning if there is a message
-               if(output) { output = strcat("\n", output); }
-       }
-       
-       return output;
-}
-
 string AppendItemcodes(string s, entity player)
 {
        float w;
@@ -286,8 +227,6 @@ string AppendItemcodes(string s, entity player)
                s = strcat(s, "T");
        if(player.kh_next)
                s = strcat(s, "K");
-       if(player.runes)
-               s = strcat(s, "|", ftos(player.runes));
        return s;
 }
 
@@ -299,7 +238,7 @@ void LogDeath(string mode, float deathtype, entity killer, entity killed)
        s = strcat(":kill:", mode);
        s = strcat(s, ":", ftos(killer.playerid));
        s = strcat(s, ":", ftos(killed.playerid));
-       s = strcat(s, ":type=", ftos(deathtype));
+       s = strcat(s, ":type=", Deathtype_Name(deathtype));
        s = strcat(s, ":items=");
        s = AppendItemcodes(s, killer);
        if(killed != killer)
@@ -310,224 +249,317 @@ void LogDeath(string mode, float deathtype, entity killer, entity killed)
        GameLogEcho(s);
 }
 
-void Send_KillNotification (string s1, string s2, string s3, float msg, float type)
+void Obituary_SpecialDeath(
+       entity notif_target,
+       float murder,
+       float deathtype,
+       string s1, string s2, string s3,
+       float f1, float f2, float f3)
 {
-       WriteByte(MSG_ALL, SVC_TEMPENTITY);
-       WriteByte(MSG_ALL, TE_CSQC_KILLNOTIFY);
-       WriteString(MSG_ALL, s1);
-       WriteString(MSG_ALL, s2);
-       WriteString(MSG_ALL, s3);
-       WriteShort(MSG_ALL, msg);
-       WriteByte(MSG_ALL, type);
+       if(DEATH_ISSPECIAL(deathtype))
+       {
+               entity deathent = deathtypes[(deathtype - DT_FIRST) - 1];
+               if not(deathent) { backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); return; }
+
+               if(murder)
+               {
+                       if(deathent.death_msgmurder)
+                       {
+                               Send_Notification_WOVA(
+                                       NOTIF_ONE,
+                                       notif_target,
+                                       MSG_MULTI,
+                                       deathent.death_msgmurder.nent_id,
+                                       s1, s2, s3, "",
+                                       f1, f2, f3, 0
+                               );
+                               Send_Notification_WOVA(
+                                       NOTIF_ALL_EXCEPT,
+                                       notif_target,
+                                       MSG_INFO,
+                                       deathent.death_msgmurder.nent_msginfo.nent_id,
+                                       s1, s2, s3, "",
+                                       f1, f2, f3, 0
+                               );
+                       }
+               }
+               else
+               {
+                       if(deathent.death_msgself)
+                       {
+                               Send_Notification_WOVA(
+                                       NOTIF_ONE,
+                                       notif_target,
+                                       MSG_MULTI,
+                                       deathent.death_msgself.nent_id,
+                                       s1, s2, s3, "",
+                                       f1, f2, f3, 0
+                               );
+                               Send_Notification_WOVA(
+                                       NOTIF_ALL_EXCEPT,
+                                       notif_target,
+                                       MSG_INFO,
+                                       deathent.death_msgself.nent_msginfo.nent_id,
+                                       s1, s2, s3, "",
+                                       f1, f2, f3, 0
+                               );
+                       }
+               }
+       }
+       else { backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); return; }
 }
 
-// Function is used to send a generic centerprint whose content CSQC gets to decide (gentle version or not in the below cases)
-void Send_CSQC_KillCenterprint(entity e, string s1, string s2, float msg, float type)
+float w_deathtype;
+float Obituary_WeaponDeath(
+       entity notif_target,
+       float murder,
+       float deathtype,
+       string s1, string s2, string s3,
+       float f1, float f2)
 {
-       if (clienttype(e) == CLIENTTYPE_REAL)
+       float death_weapon = DEATH_WEAPONOF(deathtype);
+       if(death_weapon)
        {
-               msg_entity = e;
-               WRITESPECTATABLE_MSG_ONE({
-                       WriteByte(MSG_ONE, SVC_TEMPENTITY);
-                       WriteByte(MSG_ONE, TE_CSQC_KILLCENTERPRINT);
-                       WriteString(MSG_ONE, s1);
-                       WriteString(MSG_ONE, s2);
-                       WriteShort(MSG_ONE, msg);
-                       WriteByte(MSG_ONE, type);
-               });
+               w_deathtype = deathtype;
+               float death_message = weapon_action(death_weapon, ((murder) ? WR_KILLMESSAGE : WR_SUICIDEMESSAGE));
+               w_deathtype = FALSE;
+
+               if(death_message)
+               {
+                       Send_Notification_WOVA(
+                               NOTIF_ONE,
+                               notif_target,
+                               MSG_MULTI,
+                               death_message,
+                               s1, s2, s3, "",
+                               f1, f2, 0, 0
+                       );
+                       Send_Notification_WOVA(
+                               NOTIF_ALL_EXCEPT,
+                               notif_target,
+                               MSG_INFO,
+                               msg_multi_notifs[death_message - 1].nent_msginfo.nent_id,
+                               s1, s2, s3, "",
+                               f1, f2, 0, 0
+                       );
+               }
+               else
+               {
+                       dprint(sprintf(
+                               "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
+                               deathtype,
+                               death_weapon
+                       ));
+               }
+
+               return TRUE;
        }
+       return FALSE;
 }
 
-void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
+void Obituary(entity attacker, entity inflictor, entity targ, float deathtype)
 {
-       string  s, a, msg;
-       float w, type;
-
-       if (targ.classname == "player")
+       // Sanity check
+       if not(IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
+
+       // Declarations
+       float notif_firstblood = FALSE;
+       float kill_count_to_attacker, kill_count_to_target;
+
+       // Set final information for the death
+       targ.death_origin = targ.origin;
+       if(targ != attacker) { targ.killer_origin = attacker.origin; }
+       string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
+
+       #ifdef NOTIFICATIONS_DEBUG
+       dprint(
+               sprintf(
+                       "Obituary(%s, %s, %s, %s = %d);\n",
+                       attacker.netname,
+                       inflictor.netname,
+                       targ.netname,
+                       Deathtype_Name(deathtype),
+                       deathtype
+               )
+       );
+       #endif
+       
+       // =======
+       // SUICIDE
+       // =======
+       if(targ == attacker)
        {
-               s = targ.netname;
-               a = attacker.netname;
-
-               if (targ == attacker) // suicides
+               if(DEATH_ISSPECIAL(deathtype))
                {
-                       if (deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
-                               msg = ColoredTeamName(targ.team); // TODO: check if needed?
-            if(!g_cts) // no "killed your own dumb self" message in CTS
-                Send_CSQC_KillCenterprint(targ, msg, "", deathtype, MSG_SUICIDE);
-
-                       if(deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_QUIET)
+                       if(deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
                        {
-                               LogDeath("suicide", deathtype, targ, targ);
-                               GiveFrags(attacker, targ, -1, deathtype);
+                               Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
                        }
-
-                       if (targ.killcount > 2)
-                               msg = ftos(targ.killcount);
-                       if(teamplay && deathtype == DEATH_MIRRORDAMAGE)
+                       else
                        {
-                               if(attacker.team == COLOR_TEAM1)
-                                       deathtype = KILL_TEAM_RED;
-                               else
-                                       deathtype = KILL_TEAM_BLUE;
+                               switch(deathtype)
+                               {
+                                       case DEATH_MIRRORDAMAGE:
+                                       {
+                                               Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
+                                               break;
+                                       }
+                                       
+                                       default:
+                                       {
+                                               Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
+                                               break;
+                                       }
+                               }
                        }
-
-                       Send_KillNotification(s, msg, ftos(w), deathtype, MSG_SUICIDE);
                }
-               else if (attacker.classname == "player")
+               else if not(Obituary_WeaponDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0))
                {
-                       if(!IsDifferentTeam(attacker, targ))
-                       {
-                               if(attacker.team == COLOR_TEAM1)
-                                       type = KILL_TEAM_RED;
-                               else
-                                       type = KILL_TEAM_BLUE;
-
-                               GiveFrags(attacker, targ, -1, deathtype);
+                       backtrace("SUICIDE: what the hell happened here?\n");
+                       return;
+               }
+               LogDeath("suicide", deathtype, targ, targ);
+               GiveFrags(attacker, targ, -1, deathtype);
+       }
 
-                               Send_CSQC_KillCenterprint(attacker, s, "", type, MSG_KILL);
+       // ======
+       // MURDER
+       // ======
+       else if(IS_PLAYER(attacker))
+       {
+               if(!IsDifferentTeam(attacker, targ))
+               {
+                       LogDeath("tk", deathtype, attacker, targ);
+                       GiveFrags(attacker, targ, -1, deathtype);
 
-                               if (targ.killcount > 2) {
-                                       msg = ftos(targ.killcount);
-                               }
+                       attacker.killcount = 0;
+                       
+                       Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
+                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
+                       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(targ.team, INFO_DEATH_TEAMKILL_), targ.netname, attacker.netname, deathlocation, targ.killcount);
 
-                               if (attacker.killcount > 2) {
-                                       msg = ftos(attacker.killcount);
-                                       type = KILL_TEAM_SPREE;
+                       // In this case, the death message will ALWAYS be "foo was betrayed by bar"
+                       // No need for specific death/weapon messages...
+               }
+               else
+               {
+                       LogDeath("frag", deathtype, attacker, targ);
+                       GiveFrags(attacker, targ, 1, deathtype);
+
+                       attacker.taunt_soundtime = time + 1;
+                       attacker.killcount = attacker.killcount + 1;
+
+                       #define SPREE_ITEM(counta,countb,center,normal,gentle) \
+                               case counta: \
+                               { \
+                                       Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
+                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
+                                       break; \
                                }
-                               Send_KillNotification(a, s, msg, type, MSG_KILL);
-
-                               attacker.killcount = 0;
+                       switch(attacker.killcount)
+                       {
+                               KILL_SPREE_LIST
+                               default: break;
+                       }
+                       #undef SPREE_ITEM
 
-                               LogDeath("tk", deathtype, attacker, targ);
+                       if(!checkrules_firstblood)
+                       {
+                               checkrules_firstblood = TRUE;
+                               notif_firstblood = TRUE; // modify the current messages so that they too show firstblood information
+                               PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
+                               PlayerStats_Event(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
+
+                               // tell spree_inf and spree_cen that this is a first-blood and first-victim event
+                               kill_count_to_attacker = -1;
+                               kill_count_to_target = -2;
                        }
                        else
                        {
-                               if (!checkrules_firstblood)
-                               {
-                                       checkrules_firstblood = TRUE;
-                                       Send_KillNotification(a, "", "", KILL_FIRST_BLOOD, MSG_KILL);
-                                       // TODO: make these print a newline if they dont
-                                       Send_CSQC_KillCenterprint(attacker, "", "", KILL_FIRST_BLOOD, MSG_KILL);
-                                       Send_CSQC_KillCenterprint(targ, "", "", KILL_FIRST_VICTIM, MSG_KILL);
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
-                                       PlayerStats_Event(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
-                               }
-
-                               if(targ.BUTTON_CHAT) {
-                                       Send_CSQC_KillCenterprint(attacker, s, Obituary_ExtraFragInfo(targ), KILL_TYPEFRAG, MSG_KILL);
-                                       Send_CSQC_KillCenterprint(targ, a, Obituary_ExtraFragInfo(attacker), KILL_TYPEFRAGGED, MSG_KILL);
-                               } else {
-                                       Send_CSQC_KillCenterprint(attacker, s, Obituary_ExtraFragInfo(targ), KILL_FRAG, MSG_KILL);
-                                       Send_CSQC_KillCenterprint(targ, a, Obituary_ExtraFragInfo(attacker), KILL_FRAGGED, MSG_KILL);
-                               }
-
-                               attacker.taunt_soundtime = time + 1;
+                               kill_count_to_attacker = attacker.killcount;
+                               kill_count_to_target = 0;
+                       }
 
-                               // TODO: fix this?
-                               if (deathtype == DEATH_CUSTOM)
-                                       msg = deathmessage;
+                       float verbose_allowed = (autocvar_notification_server_allows_frag_verbose && ((autocvar_notification_server_allows_frag_verbose == 2) || inWarmupStage));
+                       if(targ.istypefrag)
+                       {
+                               if(attacker.FRAG_VERBOSE && verbose_allowed)
+                                       Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_MURDER_TYPEFRAG_VERBOSE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? NO_MSG : targ.ping));
                                else
-                                       msg = inflictor.message2;
-
-                               if(strstrofs(msg, "%", 0) < 0)
-                                       msg = strcat("%s ", msg, " by %s");
+                                       Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_MURDER_TYPEFRAG, targ.netname, kill_count_to_attacker);
 
-                               Send_KillNotification(a, s, msg, deathtype, MSG_KILL);
+                               if(targ.FRAG_VERBOSE && verbose_allowed)
+                                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_MURDER_TYPEFRAGGED_VERBOSE, attacker.netname, kill_count_to_target, attacker.health, attacker.armorvalue, (IS_BOT_CLIENT(attacker) ? NO_MSG : attacker.ping));
+                               else
+                                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_MURDER_TYPEFRAGGED, attacker.netname, kill_count_to_target);
+                       }
+                       else
+                       {
+                               if(attacker.FRAG_VERBOSE && verbose_allowed)
+                                       Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_MURDER_FRAG_VERBOSE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? NO_MSG : targ.ping));
+                               else
+                                       Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_MURDER_FRAG, targ.netname, kill_count_to_attacker);
 
-                               if(g_ctf && targ.flagcarried)
-                               {
-                                       UpdateFrags(attacker, ctf_score_value("score_kill"));
-                                       PlayerScore_Add(attacker, SP_CTF_FCKILLS, 1);
-                                       GiveFrags(attacker, targ, 0, deathtype); // for logging
-                               }
+                               if(targ.FRAG_VERBOSE && verbose_allowed)
+                                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_MURDER_FRAGGED_VERBOSE, attacker.netname, kill_count_to_target, attacker.health, attacker.armorvalue, (IS_BOT_CLIENT(attacker) ? NO_MSG : attacker.ping));
                                else
-                                       GiveFrags(attacker, targ, 1, deathtype);
+                                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_MURDER_FRAGGED, attacker.netname, kill_count_to_target);
+                       }
 
-                               if (targ.killcount > 2) {
-                                       Send_KillNotification(s, ftos(targ.killcount), a, KILL_END_SPREE, MSG_SPREE);
-                               }
+                       if not(Obituary_WeaponDeath(targ, TRUE, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker))
+                               Obituary_SpecialDeath(targ, TRUE, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker, 0);
+               }
+       }
 
-                               attacker.killcount = attacker.killcount + 1;
+       // =============
+       // ACCIDENT/TRAP
+       // =============
+       else
+       {
+               switch(deathtype)
+               {
+                       // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
+                       // Later on you will only be able to make custom messages using DEATH_CUSTOM,
+                       // and there will be a REAL DEATH_VOID implementation which mappers will use.
+                       /*case DEATH_HURTTRIGGER:
+                       {
+                               s1 = targ.netname;
+                               s2 = inflictor.message;
+                               if(strstrofs(s2, "%", 0) < 0) { s2 = strcat("%s ", s2); }
+                               break;
+                       }*/
 
-                               if (attacker.killcount > 2) {
-                                       Send_KillNotification(a, ftos(attacker.killcount), "", KILL_SPREE, MSG_SPREE);
-                               }
-                               else if (attacker.killcount == 3)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_3, MSG_SPREE);
-                                       AnnounceTo(attacker, "03kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3, 1);
-                               }
-                               else if (attacker.killcount == 5)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_5, MSG_SPREE);
-                                       AnnounceTo(attacker, "05kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5, 1);
-                               }
-                               else if (attacker.killcount == 10)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_10, MSG_SPREE);
-                                       AnnounceTo(attacker, "10kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10, 1);
-                               }
-                               else if (attacker.killcount == 15)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_15, MSG_SPREE);
-                                       AnnounceTo(attacker, "15kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15, 1);
-                               }
-                               else if (attacker.killcount == 20)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_20, MSG_SPREE);
-                                       AnnounceTo(attacker, "20kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20, 1);
-                               }
-                               else if (attacker.killcount == 25)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_25, MSG_SPREE);
-                                       AnnounceTo(attacker, "25kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25, 1);
-                               }
-                               else if (attacker.killcount == 30)
-                               {
-                                       Send_KillNotification(a, "", "", KILL_SPREE_30, MSG_SPREE);
-                                       AnnounceTo(attacker, "30kills");
-                                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30, 1);
-                               }
-                               LogDeath("frag", deathtype, attacker, targ);
+                       case DEATH_CUSTOM:
+                       {
+                               Obituary_SpecialDeath(targ, FALSE, deathtype,
+                                       targ.netname,
+                                       ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
+                                       deathlocation,
+                                       targ.killcount,
+                                       0,
+                                       0);
+                               break;
                        }
-               }
-               else
-               {
-                       Send_CSQC_KillCenterprint(targ, "", "", deathtype, MSG_KILL_ACTION);
-                       if (deathtype == DEATH_HURTTRIGGER && inflictor.message != "")
-                               msg = inflictor.message;
-                       else if (deathtype == DEATH_CUSTOM)
-                               msg = deathmessage;
-                       if(strstrofs(msg, "%", 0) < 0)
-                               msg = strcat("%s ", msg);
-
-                       GiveFrags(targ, targ, -1, deathtype);
-                       if(PlayerScore_Add(targ, SP_SCORE, 0) == -5) {
-                               AnnounceTo(targ, "botlike");
-                               PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+                       
+                       default:
+                       {
+                               Obituary_SpecialDeath(targ, FALSE, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
+                               break;
                        }
-                       Send_KillNotification(s, msg, "", deathtype, MSG_KILL_ACTION);
-
-                       if (targ.killcount > 2)
-                               Send_KillNotification(s, ftos(targ.killcount), "", 0, MSG_KILL_ACTION_SPREE);
-
-                       LogDeath("accident", deathtype, targ, targ);
                }
 
-               targ.death_origin = targ.origin;
-               if(targ != attacker)
-                       targ.killer_origin = attacker.origin;
+               LogDeath("accident", deathtype, targ, targ);
+               GiveFrags(targ, targ, -1, deathtype);
 
-               // FIXME: this should go in PutClientInServer
-               if (targ.killcount)
-                       targ.killcount = 0;
+               if(PlayerScore_Add(targ, SP_SCORE, 0) == -5)
+               {
+                       Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
+                       PlayerStats_Event(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
+               }
        }
+
+       // reset target kill count
+       if(targ.killcount) { targ.killcount = 0; }
 }
 
 // these are updated by each Damage call for use in button triggering and such
@@ -539,7 +571,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
 {
        float mirrordamage;
        float mirrorforce;
-       float teamdamage0;
+       float complainteamdamage = 0; 
        entity attacker_save;
        mirrordamage = 0;
        mirrorforce = 0;
@@ -572,7 +604,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        }
        }
 
-       if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE || deathtype == DEATH_QUIET)
+       if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
        {
                // These are ALWAYS lethal
                // No damage modification here
@@ -619,10 +651,10 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                                        {
                                                if(targ.classname == "player" && targ.deadflag == DEAD_NO)
                                                {
-                                                       teamdamage0 = max(attacker.dmg_team, autocvar_g_teamdamage_threshold);
                                                        attacker.dmg_team = attacker.dmg_team + damage;
-                                                       if(attacker.dmg_team > teamdamage0 && !g_ca)
-                                                               mirrordamage = autocvar_g_mirrordamage * (attacker.dmg_team - teamdamage0);
+                                                       complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
+                                                       if(complainteamdamage > 0 && !g_ca) // FIXME why is g_ca ruled out here? Why not just g_mirrordamage 0 on CA servers?
+                                                               mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
                                                        mirrorforce = autocvar_g_mirrordamage * vlen(force);
                                                        if(g_minstagib)
                                                        {
@@ -637,21 +669,17 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
 
                                                        if(autocvar_g_mirrordamage_virtual)
                                                        {
-                                                               vector v;
-                                                               v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, mirrordamage);
-                                                               v_z = 0; // fteqcc sucks
+                                                               vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, mirrordamage);
                                                                attacker.dmg_take += v_x;
                                                                attacker.dmg_save += v_y;
                                                                attacker.dmg_inflictor = inflictor;
-                                                               mirrordamage = 0;
+                                                               mirrordamage = v_z; // = 0, to make fteqcc stfu
                                                                mirrorforce = 0;
                                                        }
 
                                                        if(autocvar_g_friendlyfire_virtual)
                                                        {
-                                                               vector v;
-                                                               v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
-                                                               v_z = 0; // fteqcc sucks
+                                                               vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
                                                                targ.dmg_take += v_x;
                                                                targ.dmg_save += v_y;
                                                                targ.dmg_inflictor = inflictor;
@@ -701,6 +729,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        {
                                damage = 0;
                                mirrordamage = 0;
+                               complainteamdamage = 0;
                                if (targ != attacker)
                                {
                                        if ((targ.health >= 1) && (targ.classname == "player"))
@@ -716,6 +745,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                {
                        damage *= g_weapondamagefactor;
                        mirrordamage *= g_weapondamagefactor;
+                       complainteamdamage *= g_weapondamagefactor;
                        force = force * g_weaponforcefactor;
                        mirrorforce *= g_weaponforcefactor;
                }
@@ -757,79 +787,12 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                                damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
                }
 
-               // CTF: reduce damage/force
-               if(g_ctf)
-               if(targ == attacker)
-               if(targ.flagcarried)
-               {
-                       damage = damage * autocvar_g_ctf_flagcarrier_selfdamage;
-                       force = force * autocvar_g_ctf_flagcarrier_selfforce;
-               }
-
-               if(g_runematch)
-               {
-                       // apply strength rune
-                       if (attacker.runes & RUNE_STRENGTH)
-                       {
-                               if(attacker.runes & CURSE_WEAK) // have both curse & rune
-                               {
-                                       damage = damage * autocvar_g_balance_rune_strength_combo_damage;
-                                       force = force * autocvar_g_balance_rune_strength_combo_force;
-                               }
-                               else
-                               {
-                                       damage = damage * autocvar_g_balance_rune_strength_damage;
-                                       force = force * autocvar_g_balance_rune_strength_force;
-                               }
-                       }
-                       else if (attacker.runes & CURSE_WEAK)
-                       {
-                               damage = damage * autocvar_g_balance_curse_weak_damage;
-                               force = force * autocvar_g_balance_curse_weak_force;
-                       }
-
-                       // apply defense rune
-                       if (targ.runes & RUNE_DEFENSE)
-                       {
-                               if (targ.runes & CURSE_VULNER) // have both curse & rune
-                                       damage = damage * autocvar_g_balance_rune_defense_combo_takedamage;
-                               else
-                                       damage = damage * autocvar_g_balance_rune_defense_takedamage;
-                       }
-                       else if (targ.runes & CURSE_VULNER)
-                               damage = damage * autocvar_g_balance_curse_vulner_takedamage;
-               }
-
                // count the damage
                if(attacker)
                if(!targ.deadflag)
                if(targ.takedamage == DAMAGE_AIM)
                if(targ != attacker)
                {
-                       if(damage_headshotbonus > 0)
-                       {
-                               if(targ.classname == "player")
-                               {
-                                       // HEAD SHOT:
-                                       // find height of hit on player axis
-                                       // if above view_ofs and below maxs, and also in the middle half of the bbox, it is head shot
-                                       vector headmins, headmaxs, org;
-                                       org = antilag_takebackorigin(targ, time - ANTILAG_LATENCY(attacker));
-                                       headmins = org + GetHeadshotMins(targ);
-                                       headmaxs = org + GetHeadshotMaxs(targ);
-                                       if(trace_hits_box(railgun_start, railgun_end, headmins, headmaxs))
-                                       {
-                                               deathtype |= HITTYPE_HEADSHOT;
-                                       }
-                               }
-                               else if(targ.classname == "turret_head")
-                               {
-                                       deathtype |= HITTYPE_HEADSHOT;
-                               }
-                               if(deathtype & HITTYPE_HEADSHOT)
-                                       damage *= 1 + damage_headshotbonus;
-                       }
-
                        entity victim;
                        if((targ.vehicle_flags & VHF_ISVEHICLE) && targ.owner)
                                victim = targ.owner;
@@ -863,12 +826,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                                                        if(g_minstagib)
                                                        if(victim.items & IT_STRENGTH)
                                                                yoda = 1;
-
-                                                       if(deathtype & HITTYPE_HEADSHOT)
-                                                               headshot = 1;
                                                }
-                                               if(g_ca)
-                                                       PlayerScore_Add(attacker, SP_SCORE, damage * autocvar_g_ca_damage2score_multiplier);
                                        }
                                }
                                else
@@ -877,7 +835,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                                        {
                                                attacker.typehitsound += 1;
                                        }
-                                       if(mirrordamage > 0)
+                                       if(complainteamdamage > 0)
                                                if(time > attacker.teamkill_complain)
                                                {
                                                        attacker.teamkill_complain = time + 5;
@@ -894,7 +852,23 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
        if (vlen(force))
        if (self.classname != "player" || time >= self.spawnshieldtime || g_midair)
        {
-               self.velocity = self.velocity + damage_explosion_calcpush(self.damageforcescale * force, self.velocity, autocvar_g_balance_damagepush_speedfactor);
+               vector farce = damage_explosion_calcpush(self.damageforcescale * force, self.velocity, autocvar_g_balance_damagepush_speedfactor);
+               if(self.movetype == MOVETYPE_PHYSICS)
+               {
+                       entity farcent;
+                       farcent = spawn();
+                       farcent.classname = "farce";
+                       farcent.enemy = self;
+                       farcent.movedir = farce * 10;
+                       if(self.mass)
+                               farcent.movedir = farcent.movedir * self.mass;
+                       farcent.origin = hitloc;
+                       farcent.forcetype = FORCETYPE_FORCEATPOS;
+                       farcent.nextthink = time + 0.1;
+                       farcent.think = SUB_Remove;
+               }
+               else
+                       self.velocity = self.velocity + farce;
                self.flags &~= FL_ONGROUND;
                UpdateCSQCProjectile(self);
        }
@@ -904,42 +878,6 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                self.event_damage (inflictor, attacker, damage, deathtype, hitloc, force);
        self = oldself;
 
-       if(targ.classname == "player" && attacker.classname == "player" && attacker != targ && attacker.health > 2)
-       {
-               if(g_runematch)
-               {
-                       if (attacker.runes & RUNE_VAMPIRE)
-                       {
-                       // apply vampire rune
-                               if (attacker.runes & CURSE_EMPATHY) // have the curse too
-                               {
-                                       //attacker.health = attacker.health + damage * autocvar_g_balance_rune_vampire_combo_absorb;
-                                       attacker.health = bound(
-                                               autocvar_g_balance_curse_empathy_minhealth, // LA: was 3, now 40
-                                               attacker.health + damage * autocvar_g_balance_rune_vampire_combo_absorb,
-                                               autocvar_g_balance_rune_vampire_maxhealth);     // LA: was 1000, now 500
-                               }
-                               else
-                               {
-                                       //attacker.health = attacker.health + damage * autocvar_g_balance_rune_vampire_absorb;
-                                       attacker.health = bound(
-                                               attacker.health,        // LA: was 3, but changed so that you can't lose health
-                                                                                       // empathy won't let you gain health in the same way...
-                                               attacker.health + damage * autocvar_g_balance_rune_vampire_absorb,
-                                               autocvar_g_balance_rune_vampire_maxhealth);     // LA: was 1000, now 500
-                                       }
-                       }
-                       // apply empathy curse
-                       else if (attacker.runes & CURSE_EMPATHY)
-                       {
-                               attacker.health = bound(
-                                       autocvar_g_balance_curse_empathy_minhealth, // LA: was 3, now 20
-                                       attacker.health + damage * autocvar_g_balance_curse_empathy_takedamage,
-                                       attacker.health);
-                       }
-               }
-       }
-
        // apply mirror damage if any
        if(mirrordamage > 0 || mirrorforce > 0)
        {
@@ -964,16 +902,11 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
 
 float RadiusDamage_running;
 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity ignore, float forceintensity, float deathtype, entity directhitentity)
-// Returns total damage applies to creatures
+       // Returns total damage applies to creatures
 {
        entity  targ;
-       float   finaldmg;
-       float   power;
        vector  blastorigin;
        vector  force;
-       vector  diff;
-       vector  center;
-       vector  nearest;
        float   total_damage_to_creatures;
        entity  next;
        float   tfloordmg;
@@ -996,18 +929,18 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
        total_damage_to_creatures = 0;
 
        if(deathtype != (WEP_HOOK | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
-       if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
-       {
-               force = inflictor.velocity;
-               if(vlen(force) == 0)
-                       force = '0 0 -1';
-               else
-                       force = normalize(force);
-               if(forceintensity >= 0)
-                       Damage_DamageInfo(blastorigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
-               else
-                       Damage_DamageInfo(blastorigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
-       }
+               if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
+               {
+                       force = inflictor.velocity;
+                       if(vlen(force) == 0)
+                               force = '0 0 -1';
+                       else
+                               force = normalize(force);
+                       if(forceintensity >= 0)
+                               Damage_DamageInfo(blastorigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
+                       else
+                               Damage_DamageInfo(blastorigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
+               }
 
        stat_damagedone = 0;
 
@@ -1018,6 +951,10 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
                if (targ != inflictor)
                        if (ignore != targ) if(targ.takedamage)
                        {
+                               vector nearest;
+                               vector diff;
+                               float power;
+
                                // LordHavoc: measure distance to nearest point on target (not origin)
                                // (this guarentees 100% damage on a touch impact)
                                nearest = targ.WarpZone_findradius_nearest;
@@ -1031,6 +968,7 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
                                //      print(ftos(power), "\n");
                                if (power > 0)
                                {
+                                       float finaldmg;
                                        if (power > 1)
                                                power = 1;
                                        finaldmg = coredamage * power + edgedamage * (1 - power);
@@ -1038,95 +976,125 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
                                        {
                                                float a;
                                                float c;
-                                               float hits;
-                                               float total;
-                                               float hitratio;
                                                vector hitloc;
                                                vector myblastorigin;
+                                               vector center;
+
                                                myblastorigin = WarpZone_TransformOrigin(targ, blastorigin);
-                                               center = targ.origin + (targ.mins + targ.maxs) * 0.5;
+
                                                // if it's a player, use the view origin as reference
-                                               if (targ.classname == "player")
-                                                       center = targ.origin + targ.view_ofs;
+                                               center = CENTER_OR_VIEWOFS(targ);
+
                                                force = normalize(center - myblastorigin);
                                                force = force * (finaldmg / coredamage) * forceintensity;
-                                               // test line of sight to multiple positions on box,
-                                               // and do damage if any of them hit
-                                               hits = 0;
-                                               if (targ.classname == "player")
-                                                       total = ceil(bound(1, finaldmg, 50));
-                                               else
-                                                       total = ceil(bound(1, finaldmg/10, 5));
                                                hitloc = nearest;
-                                               c = 0;
-                                               while (c < total)
+
+                                               if(targ != directhitentity)
                                                {
-                                                       //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
-                                                       WarpZone_TraceLine(blastorigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
-                                                       if (trace_fraction == 1 || trace_ent == targ)
+                                                       float hits;
+                                                       float total;
+                                                       float hitratio;
+                                                       float mininv_f, mininv_d;
+
+                                                       // test line of sight to multiple positions on box,
+                                                       // and do damage if any of them hit
+                                                       hits = 0;
+
+                                                       // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
+                                                       // so for a given max stddev:
+                                                       // n = (1 / (2 * max stddev of hitratio))^2
+
+                                                       mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
+                                                       mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
+
+                                                       if(autocvar_g_throughfloor_debug)
+                                                               print(sprintf("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f));
+
+                                                       total = 0.25 * pow(max(mininv_f, mininv_d), 2);
+
+                                                       if(autocvar_g_throughfloor_debug)
+                                                               print(sprintf(" steps=%f", total));
+
+                                                       if (targ.classname == "player")
+                                                               total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
+                                                       else
+                                                               total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
+
+                                                       if(autocvar_g_throughfloor_debug)
+                                                               print(sprintf(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total))));
+
+                                                       for(c = 0; c < total; ++c)
                                                        {
-                                                               hits = hits + 1;
-                                                               if (hits > 1)
-                                                                       hitloc = hitloc + nearest;
-                                                               else
-                                                                       hitloc = nearest;
+                                                               //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
+                                                               WarpZone_TraceLine(blastorigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
+                                                               if (trace_fraction == 1 || trace_ent == targ)
+                                                               {
+                                                                       ++hits;
+                                                                       if (hits > 1)
+                                                                               hitloc = hitloc + nearest;
+                                                                       else
+                                                                               hitloc = nearest;
+                                                               }
+                                                               nearest_x = targ.origin_x + targ.mins_x + random() * targ.size_x;
+                                                               nearest_y = targ.origin_y + targ.mins_y + random() * targ.size_y;
+                                                               nearest_z = targ.origin_z + targ.mins_z + random() * targ.size_z;
                                                        }
-                                                       nearest_x = targ.origin_x + targ.mins_x + random() * targ.size_x;
-                                                       nearest_y = targ.origin_y + targ.mins_y + random() * targ.size_y;
-                                                       nearest_z = targ.origin_z + targ.mins_z + random() * targ.size_z;
-                                                       c = c + 1;
+
+                                                       nearest = hitloc * (1 / max(1, hits));
+                                                       hitratio = (hits / total);
+                                                       a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
+                                                       finaldmg = finaldmg * a;
+                                                       a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
+                                                       force = force * a;
+
+                                                       if(autocvar_g_throughfloor_debug)
+                                                               print(sprintf(" D=%f F=%f\n", finaldmg, vlen(force)));
                                                }
-                                               nearest = hitloc * (1 / max(1, hits));
-                                               hitratio = (hits / total);
-                                               a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
-                                               finaldmg = finaldmg * a;
-                                               a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
-                                               force = force * a;
 
                                                // laser force adjustments :P
                                                if(DEATH_WEAPONOF(deathtype) == WEP_LASER)
                                                {
-                            if (targ == attacker)
-                            {
-                                vector vel;
-
-                                float force_zscale;
-                                float force_velocitybiasramp;
-                                float force_velocitybias;
-
-                                force_velocitybiasramp = autocvar_sv_maxspeed;
-                                if(deathtype & HITTYPE_SECONDARY)
-                                {
-                                    force_zscale = autocvar_g_balance_laser_secondary_force_zscale;
-                                    force_velocitybias = autocvar_g_balance_laser_secondary_force_velocitybias;
-                                }
-                                else
-                                {
-                                    force_zscale = autocvar_g_balance_laser_primary_force_zscale;
-                                    force_velocitybias = autocvar_g_balance_laser_primary_force_velocitybias;
-                                }
-
-                                vel = targ.velocity;
-                                vel_z = 0;
-                                vel = normalize(vel) * bound(0, vlen(vel) / force_velocitybiasramp, 1) * force_velocitybias;
-                                force =
-                                    vlen(force)
-                                    *
-                                    normalize(normalize(force) + vel);
-
-                                force_z *= force_zscale;
-                            }
-                            else
-                            {
-                                if(deathtype & HITTYPE_SECONDARY)
-                                {
-                                    force *= autocvar_g_balance_laser_secondary_force_other_scale;
-                                }
-                                else
-                                {
-                                    force *= autocvar_g_balance_laser_primary_force_other_scale;
-                                }
-                            }
+                                                       if (targ == attacker)
+                                                       {
+                                                               vector vel;
+
+                                                               float force_zscale;
+                                                               float force_velocitybiasramp;
+                                                               float force_velocitybias;
+
+                                                               force_velocitybiasramp = autocvar_sv_maxspeed;
+                                                               if(deathtype & HITTYPE_SECONDARY)
+                                                               {
+                                                                       force_zscale = autocvar_g_balance_laser_secondary_force_zscale;
+                                                                       force_velocitybias = autocvar_g_balance_laser_secondary_force_velocitybias;
+                                                               }
+                                                               else
+                                                               {
+                                                                       force_zscale = autocvar_g_balance_laser_primary_force_zscale;
+                                                                       force_velocitybias = autocvar_g_balance_laser_primary_force_velocitybias;
+                                                               }
+
+                                                               vel = targ.velocity;
+                                                               vel_z = 0;
+                                                               vel = normalize(vel) * bound(0, vlen(vel) / force_velocitybiasramp, 1) * force_velocitybias;
+                                                               force =
+                                                                       vlen(force)
+                                                                       *
+                                                                       normalize(normalize(force) + vel);
+
+                                                               force_z *= force_zscale;
+                                                       }
+                                                       else
+                                                       {
+                                                               if(deathtype & HITTYPE_SECONDARY)
+                                                               {
+                                                                       force *= autocvar_g_balance_laser_secondary_force_other_scale;
+                                                               }
+                                                               else
+                                                               {
+                                                                       force *= autocvar_g_balance_laser_primary_force_other_scale;
+                                                               }
+                                                       }
                                                }
 
                                                //if (targ == attacker)
@@ -1135,7 +1103,7 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
                                                //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
                                                //      print(" (", ftos(a), ")\n");
                                                //}
-                                               if(hits || tfloordmg || tfloorforce)
+                                               if(finaldmg || vlen(force))
                                                {
                                                        if(targ.iscreature)
                                                        {
@@ -1213,43 +1181,59 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
 
                if(maxtime > mintime || maxdps > mindps)
                {
+                       // Constraints:
+                       
+                       // damage we have right now
                        mindamage = mindps * mintime;
-                       maxdamage = mindamage + d;
-
-                       // interval [mintime, maxtime] * [mindps, maxdps]
-                       // intersected with
-                       // [mindamage, maxdamage]
-                       // maximum of this!
 
-                       if(maxdamage >= maxtime * maxdps)
-                       {
-                               totaltime = maxtime;
-                               totaldamage = maxtime * maxdps;
-
-                               // this branch increases totaldamage if either t > mintime, or dps > mindps
-                       }
-                       else
-                       {
-                               // maxdamage is inside the interval!
-                               // first, try to use mindps; only if this fails, increase dps as needed
-                               totaltime = min(maxdamage / mindps, maxtime); // maxdamage / mindps >= mindamage / mindps = mintime
-                               totaldamage = maxdamage;
-                               // can totaldamage / totaltime be >= maxdps?
-                               // max(mindps, maxdamage / maxtime) >= maxdps?
-                               // we know maxdamage < maxtime * maxdps
-                               // so it cannot be
-
-                               // this branch ALWAYS increases totaldamage, but requires maxdamage < maxtime * maxdps
-                       }
+                       // damage we want to get
+                       maxdamage = mindamage + d;
 
-                       // total conditions for increasing:
-                       //     maxtime > mintime OR maxdps > mindps OR maxtime * maxdps > maxdamage
-                       // however:
-                       //     if maxtime = mintime, maxdps = mindps
-                       // then:
-                       //     maxdamage = mindamage + d
-                       //     mindamage = mindps * mintime = maxdps * maxtime < maxdamage!
-                       // so the last condition is not needed
+                       // but we can't exceed maxtime * maxdps!
+                       totaldamage = min(maxdamage, maxtime * maxdps);
+
+                       // LEMMA:
+                       // Look at:
+                       // totaldamage = min(mindamage + d, maxtime * maxdps)
+                       // We see:
+                       // totaldamage <= maxtime * maxdps
+                       // ==> totaldamage / maxdps <= maxtime.
+                       // We also see:
+                       // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
+                       //                     >= min(mintime, maxtime)
+                       // ==> totaldamage / maxdps >= mintime.
+
+                       /*
+                       // how long do we damage then?
+                       // at least as long as before
+                       // but, never exceed maxdps
+                       totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
+                       */
+
+                       // alternate:
+                       // at most as long as maximum allowed
+                       // but, never below mindps
+                       totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
+
+                       // assuming t > mintime, dps > mindps:
+                       // we get d = t * dps = maxtime * maxdps
+                       // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
+                       // totaldamage / maxdps = maxtime
+                       // totaldamage / mindps > totaldamage / maxdps = maxtime
+                       // FROM THIS:
+                       // a) totaltime = max(mintime, maxtime) = maxtime
+                       // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
+
+                       // assuming t <= mintime:
+                       // we get maxtime = mintime
+                       // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
+                       // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
+
+                       // assuming dps <= mindps:
+                       // we get mindps = maxdps.
+                       // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
+                       // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
+                       // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
 
                        e.fire_damagepersec = totaldamage / totaltime;
                        e.fire_endtime = time + totaltime;