]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/g_damage.qc
Merge remote-tracking branch 'origin/master' into samual/spawn_weapons
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
index 170793a5524b0621760b283023dbee138a205132..710c7d4e2a88e7e7fa5ca4fdeb019c5e84ab5d96 100644 (file)
@@ -14,10 +14,11 @@ float Damage_DamageInfo_SendEntity(entity to, float sf)
        WriteByte(MSG_ENTITY, bound(0, self.dmg_radius, 255));
        WriteByte(MSG_ENTITY, bound(1, self.dmg_edge, 255));
        WriteShort(MSG_ENTITY, self.oldorigin_x);
+       WriteByte(MSG_ENTITY, self.species);
        return TRUE;
 }
 
-void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, float deathtype, entity dmgowner)
+void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, float deathtype, float bloodtype, entity dmgowner)
 {
        // TODO maybe call this from non-edgedamage too?
        // TODO maybe make the client do the particle effects for the weapons and the impact sounds using this info?
@@ -35,8 +36,8 @@ void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad
        e.dmg_radius = rad;
        e.dmg_force = vlen(force);
        e.velocity = force;
-
        e.oldorigin_x = compressShortVector(e.velocity);
+       e.species = bloodtype;
 
        Net_LinkEntity(e, FALSE, 0.2, Damage_DamageInfo_SendEntity);
 }
@@ -100,10 +101,9 @@ void UpdateFrags(entity player, float f)
 
 // NOTE: f=0 means still count as a (positive) kill, but count no frags for it
 void W_SwitchWeapon_Force(entity e, float w);
+entity GiveFrags_randomweapons;
 void GiveFrags (entity attacker, entity targ, float f, float deathtype)
 {
-       float w;
-
        // TODO route through PlayerScores instead
        if(gameover) return;
 
@@ -140,34 +140,44 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
                // 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 || !(attacker.weapons & W_WeaponBit(culprit)))
+               if(!culprit)
+                       culprit = attacker.weapon;
+               else if(!WEPSET_CONTAINS_EW(attacker, culprit))
                        culprit = attacker.weapon;
 
-               if(g_weaponarena_random_with_laser && culprit == WEPBIT_LASER)
+               if(g_weaponarena_random_with_laser && culprit == WEP_LASER)
                {
                        // no exchange
                }
                else
                {
+                       if(!GiveFrags_randomweapons)
+                       {
+                               GiveFrags_randomweapons = spawn();
+                               GiveFrags_randomweapons.classname = "GiveFrags_randomweapons";
+                       }
+
                        if(inWarmupStage)
-                               w = warmup_start_weapons;
+                               WEPSET_COPY_EA(GiveFrags_randomweapons, warmup_start_weapons);
                        else
-                               w = start_weapons;
+                               WEPSET_COPY_EA(GiveFrags_randomweapons, start_weapons);
 
                        // all others (including the culprit): remove
-                       w &~= attacker.weapons;
+                       WEPSET_ANDNOT_EE(GiveFrags_randomweapons, attacker);
+                       WEPSET_ANDNOT_EW(GiveFrags_randomweapons, culprit);
 
                        // among the remaining ones, choose one by random
-                       w = randombits(w, 1, FALSE);
-                       if(w)
+                       W_RandomWeapons(GiveFrags_randomweapons, 1);
+
+                       if(!WEPSET_EMPTY_E(GiveFrags_randomweapons))
                        {
-                               attacker.weapons |= w;
-                               attacker.weapons &~= W_WeaponBit(culprit);
+                               WEPSET_OR_EE(attacker, GiveFrags_randomweapons);
+                               WEPSET_ANDNOT_EW(attacker, culprit);
                        }
                }
 
                // after a frag, choose another random weapon set
-               if not(attacker.weapons & W_WeaponBit(attacker.weapon))
+               if not(WEPSET_CONTAINS_EW(attacker, attacker.weapon))
                        W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker));
        }
 
@@ -223,37 +233,39 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
 
 string Obituary_ExtraFragInfo(entity player) // Extra fragmessage information
 {
-       string health_output;
-       string ping_output;
-       string handicap_output;
-       string output;
-
-       // health/armor of attacker (person who killed you)
-       if(autocvar_sv_fraginfo_stats && (player.health >= 1))
-               if((autocvar_sv_fraginfo_stats == 2) || !inWarmupStage)
+       string health_output = string_null;
+       string ping_output = string_null;
+       string handicap_output = string_null;
+       string output = string_null;
+
+       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(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(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(player.cvar_cl_handicap));
+               // 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); }
        }
        
-       // format the string
-       output = strcat(health_output, (health_output ? " ^7(" : ((ping_output || handicap_output) ? "^7(" : "")), 
-               ping_output, ((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;
 }
 
@@ -342,6 +354,8 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
                {
                        if (deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
                                msg = ColoredTeamName(targ.team); // TODO: check if needed?
+                       else
+                               msg = "";
             if(!g_cts) // no "killed your own dumb self" message in CTS
                 Send_CSQC_KillCenterprint(targ, msg, "", deathtype, MSG_SUICIDE);
 
@@ -353,6 +367,8 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
 
                        if (targ.killcount > 2)
                                msg = ftos(targ.killcount);
+                       else
+                               msg = "";
                        if(teamplay && deathtype == DEATH_MIRRORDAMAGE)
                        {
                                if(attacker.team == COLOR_TEAM1)
@@ -376,9 +392,10 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
 
                                Send_CSQC_KillCenterprint(attacker, s, "", type, MSG_KILL);
 
-                               if (targ.killcount > 2) {
+                               if (targ.killcount > 2)
                                        msg = ftos(targ.killcount);
-                               }
+                               else
+                                       msg = "";
 
                                if (attacker.killcount > 2) {
                                        msg = ftos(attacker.killcount);
@@ -403,7 +420,7 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
                                        PlayerStats_Event(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
                                }
 
-                               if((autocvar_sv_fraginfo_typefrag) && (targ.BUTTON_CHAT)) {
+                               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 {
@@ -413,11 +430,12 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
 
                                attacker.taunt_soundtime = time + 1;
 
-                               // TODO: fix this?
-                               if (deathtype == DEATH_CUSTOM)
+                               if (deathtype == DEATH_HURTTRIGGER && inflictor.message2 != "")
+                                       msg = inflictor.message2;
+                               else if (deathtype == DEATH_CUSTOM)
                                        msg = deathmessage;
                                else
-                                       msg = inflictor.message2;
+                                       msg = "";
 
                                if(strstrofs(msg, "%", 0) < 0)
                                        msg = strcat("%s ", msg, " by %s");
@@ -494,6 +512,8 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
                                msg = inflictor.message;
                        else if (deathtype == DEATH_CUSTOM)
                                msg = deathmessage;
+                       else
+                               msg = "";
                        if(strstrofs(msg, "%", 0) < 0)
                                msg = strcat("%s ", msg);
 
@@ -627,9 +647,7 @@ 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;
@@ -639,9 +657,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
 
                                                        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;
@@ -796,7 +812,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                if(targ.takedamage == DAMAGE_AIM)
                if(targ != attacker)
                {
-                       if(damage_headshotbonus > 0)
+                       if(damage_headshotbonus)
                        {
                                if(targ.classname == "player")
                                {
@@ -817,7 +833,8 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                                        deathtype |= HITTYPE_HEADSHOT;
                                }
                                if(deathtype & HITTYPE_HEADSHOT)
-                                       damage *= 1 + damage_headshotbonus;
+                                       if(damage_headshotbonus > 0)
+                                               damage *= 1 + damage_headshotbonus;
                        }
 
                        entity victim;
@@ -884,7 +901,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);
        }
@@ -953,17 +986,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
+float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float inflictorselfdamage, float forceintensity, float deathtype, entity directhitentity)
+       // 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;
@@ -982,80 +1009,114 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
        tfloordmg = autocvar_g_throughfloor_damage;
        tfloorforce = autocvar_g_throughfloor_force;
 
-       blastorigin = (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5);
        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, attacker);
-               else
-                       Damage_DamageInfo(blastorigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, attacker);
-       }
+               if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
+               {
+                       force = inflictorvelocity;
+                       if(vlen(force) == 0)
+                               force = '0 0 -1';
+                       else
+                               force = normalize(force);
+                       if(forceintensity >= 0)
+                               Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
+                       else
+                               Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
+               }
 
        stat_damagedone = 0;
 
-       targ = WarpZone_FindRadius (blastorigin, rad, FALSE);
+       targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, FALSE);
        while (targ)
        {
                next = targ.chain;
-               if (targ != inflictor)
-                       if (ignore != targ) if(targ.takedamage)
+               if ((targ != inflictor) || inflictorselfdamage)
+               if (((cantbe != targ) && !mustbe) || (mustbe == 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;
+                       diff = targ.WarpZone_findradius_dist;
+                       // round up a little on the damage to ensure full damage on impacts
+                       // and turn the distance into a fraction of the radius
+                       power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
+                       //bprint(" ");
+                       //bprint(ftos(power));
+                       //if (targ == attacker)
+                       //      print(ftos(power), "\n");
+                       if (power > 0)
                        {
-                               // LordHavoc: measure distance to nearest point on target (not origin)
-                               // (this guarentees 100% damage on a touch impact)
-                               nearest = targ.WarpZone_findradius_nearest;
-                               diff = targ.WarpZone_findradius_dist;
-                               // round up a little on the damage to ensure full damage on impacts
-                               // and turn the distance into a fraction of the radius
-                               power = 1 - ((vlen (diff) - 2) / rad);
-                               //bprint(" ");
-                               //bprint(ftos(power));
-                               //if (targ == attacker)
-                               //      print(ftos(power), "\n");
-                               if (power > 0)
+                               float finaldmg;
+                               if (power > 1)
+                                       power = 1;
+                               finaldmg = coredamage * power + edgedamage * (1 - power);
+                               if (finaldmg > 0)
                                {
-                                       if (power > 1)
-                                               power = 1;
-                                       finaldmg = coredamage * power + edgedamage * (1 - power);
-                                       if (finaldmg > 0)
+                                       float a;
+                                       float c;
+                                       vector hitloc;
+                                       vector myblastorigin;
+                                       vector center;
+
+                                       myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
+
+                                       // if it's a player, use the view origin as reference
+                                       if (targ.classname == "player")
+                                               center = targ.origin + targ.view_ofs;
+                                       else
+                                               center = targ.origin + (targ.mins + targ.maxs) * 0.5;
+
+                                       force = normalize(center - myblastorigin);
+                                       force = force * (finaldmg / coredamage) * forceintensity;
+                                       hitloc = nearest;
+
+                                       if(targ != directhitentity)
                                        {
-                                               float a;
-                                               float c;
                                                float hits;
                                                float total;
                                                float hitratio;
-                                               vector hitloc;
-                                               vector myblastorigin;
-                                               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;
-                                               force = normalize(center - myblastorigin);
-                                               force = force * (finaldmg / coredamage) * forceintensity;
+                                               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(1, finaldmg, 50));
+                                                       total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
                                                else
-                                                       total = ceil(bound(1, finaldmg/10, 5));
-                                               hitloc = nearest;
-                                               c = 0;
-                                               while (c < total)
+                                                       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)
                                                {
                                                        //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
-                                                       WarpZone_TraceLine(blastorigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
+                                                       WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
                                                        if (trace_fraction == 1 || trace_ent == targ)
                                                        {
-                                                               hits = hits + 1;
+                                                               ++hits;
                                                                if (hits > 1)
                                                                        hitloc = hitloc + nearest;
                                                                else
@@ -1064,8 +1125,8 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
                                                        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);
@@ -1073,76 +1134,80 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
                                                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(autocvar_g_throughfloor_debug)
+                                                       print(sprintf(" D=%f F=%f\n", finaldmg, vlen(force)));
+                                       }
 
-                                               //if (targ == attacker)
-                                               //{
-                                               //      print("hits ", ftos(hits), " / ", ftos(total));
-                                               //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
-                                               //      print(" (", ftos(a), ")\n");
-                                               //}
-                                               if(hits || tfloordmg || tfloorforce)
+                                       // laser force adjustments :P
+                                       if(DEATH_WEAPONOF(deathtype) == WEP_LASER)
+                                       {
+                                               if (targ == attacker)
                                                {
-                                                       if(targ.iscreature)
-                                                       {
-                                                               total_damage_to_creatures += finaldmg;
+                                                       vector vel;
 
-                                                               if(accuracy_isgooddamage(attacker, targ))
-                                                                       stat_damagedone += finaldmg;
+                                                       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;
                                                        }
 
-                                                       if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
-                                                               Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
+                                                       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
-                                                               Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
+                                                       {
+                                                               force *= autocvar_g_balance_laser_primary_force_other_scale;
+                                                       }
+                                               }
+                                       }
+
+                                       //if (targ == attacker)
+                                       //{
+                                       //      print("hits ", ftos(hits), " / ", ftos(total));
+                                       //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
+                                       //      print(" (", ftos(a), ")\n");
+                                       //}
+                                       if(finaldmg || vlen(force))
+                                       {
+                                               if(targ.iscreature)
+                                               {
+                                                       total_damage_to_creatures += finaldmg;
+
+                                                       if(accuracy_isgooddamage(attacker, targ))
+                                                               stat_damagedone += finaldmg;
                                                }
+
+                                               if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
+                                                       Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
+                                               else
+                                                       Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
                                        }
                                }
                        }
+               }
                targ = next;
        }
 
@@ -1154,6 +1219,11 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e
        return total_damage_to_creatures;
 }
 
+float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, float deathtype, entity directhitentity)
+{
+       return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, FALSE, forceintensity, deathtype, directhitentity);
+}
+
 .float fire_damagepersec;
 .float fire_endtime;
 .float fire_deathtype;
@@ -1203,43 +1273,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;