From: Rudolf Polzer Date: Wed, 24 Nov 2010 18:03:56 +0000 (+0100) Subject: Merge remote branch 'origin/divVerent/accuracy-fixes' X-Git-Tag: xonotic-v0.1.0preview~109 X-Git-Url: http://de.git.xonotic.org/?a=commitdiff_plain;h=026b1e9f8a6d485705aaf949b7e16c08f50ba28f;hp=1daf84d86e5db606fdfe0f815b34333f93cbb16a;p=xonotic%2Fxonotic-data.pk3dir.git Merge remote branch 'origin/divVerent/accuracy-fixes' --- diff --git a/qcsrc/client/Main.qc b/qcsrc/client/Main.qc index ced8061ba..40bed8d10 100644 --- a/qcsrc/client/Main.qc +++ b/qcsrc/client/Main.qc @@ -848,11 +848,6 @@ void Ent_ClientData() if(newspectatee_status != spectatee_status) { float i; - // clear the weapon accuracy stats - for(i = WEP_FIRST; i <= WEP_LAST; ++i) { - weapon_hits[i] = 0; - weapon_fired[i] = 0; - } // clear race stuff race_laptime = 0; @@ -904,6 +899,30 @@ void Ent_RandomSeed() psrandom(s); } +void Ent_ReadAccuracy(void) +{ + float sf, f, w, b; + sf = ReadInt24_t(); + if(sf == 0) + { + for(w = 0; w <= WEP_LAST - WEP_FIRST; ++w) + weapon_accuracy[w] = -1; + return; + } + + for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w, f *= 2) + { + if(sf & f) + { + b = ReadByte(); + if(b == 0) + weapon_accuracy[w] = -1; + else + weapon_accuracy[w] = (b - 1.0) / 254.0; + } + } +} + // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured. // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS. void Ent_RadarLink(); @@ -967,6 +986,7 @@ void(float bIsNewEntity) CSQC_Ent_Update = case ENT_CLIENT_HOOK: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_HOOK); break; case ENT_CLIENT_LGBEAM: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_LGBEAM); break; case ENT_CLIENT_GAUNTLET: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_GAUNTLET); break; + case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break; default: error(strcat("unknown entity type in CSQC_Ent_Update: ", ftos(self.enttype), "\n")); break; diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index f77f1c1c1..01dc01663 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -1692,18 +1692,10 @@ void HUD_Weapons(void) } float show_accuracy; - float weapon_stats, weapon_number; + float weapon_stats; if(cvar("hud_panel_weapons_accuracy") && acc_levels) { show_accuracy = true; - // hits - weapon_stats = getstati(STAT_DAMAGE_HITS); - weapon_number = weapon_stats & 63; - weapon_hits[weapon_number-WEP_FIRST] = floor(weapon_stats / 64); - // fired - weapon_stats = getstati(STAT_DAMAGE_FIRED); - weapon_number = weapon_stats & 63; - weapon_fired[weapon_number-WEP_FIRST] = floor(weapon_stats / 64); if (acc_col_x[0] == -1) for (i = 0; i < acc_levels; ++i) acc_col[i] = stov(cvar_string(strcat("accuracy_color", ftos(i)))); @@ -1726,13 +1718,9 @@ void HUD_Weapons(void) // draw the weapon accuracy if(show_accuracy) { - float weapon_hit, weapon_damage; - weapon_damage = weapon_fired[self.weapon-WEP_FIRST]; - if(weapon_damage) + weapon_stats = weapon_accuracy[self.weapon-WEP_FIRST]; + if(weapon_stats >= 0) { - weapon_hit = weapon_hits[self.weapon-WEP_FIRST]; - weapon_stats = floor(100 * weapon_hit / weapon_damage); - // find the max level lower than weapon_stats float j; j = acc_levels-1; diff --git a/qcsrc/client/hud.qh b/qcsrc/client/hud.qh index b661e30ff..bbb57d91f 100644 --- a/qcsrc/client/hud.qh +++ b/qcsrc/client/hud.qh @@ -15,8 +15,7 @@ float highlightedAction; // 0 = nothing, 1 = move, 2 = resize const float BORDER_MULTIPLIER = 0.25; float scoreboard_bottom; -float weapon_hits[WEP_MAXCOUNT]; -float weapon_fired[WEP_MAXCOUNT]; +float weapon_accuracy[WEP_MAXCOUNT]; #define MAX_ACCURACY_LEVELS 10 float acc_lev[MAX_ACCURACY_LEVELS]; diff --git a/qcsrc/client/scoreboard.qc b/qcsrc/client/scoreboard.qc index ec17dfca7..b1be9dc2a 100644 --- a/qcsrc/client/scoreboard.qc +++ b/qcsrc/client/scoreboard.qc @@ -941,15 +941,7 @@ vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) if(getstati(STAT_SWITCHWEAPON) == WEP_MINSTANEX) g_minstagib = 1; // TODO: real detection for minstagib? - float weapon_hit, weapon_damage, weapon_stats, weapon_number; - // hits - weapon_stats = getstati(STAT_DAMAGE_HITS); - weapon_number = weapon_stats & 63; - weapon_hits[weapon_number-WEP_FIRST] = floor(weapon_stats / 64); - // fired - weapon_stats = getstati(STAT_DAMAGE_FIRED); - weapon_number = weapon_stats & 63; - weapon_fired[weapon_number-WEP_FIRST] = floor(weapon_stats / 64); + float weapon_stats, weapon_number; if (!acc_levels) rgb = '1 1 1'; @@ -964,22 +956,18 @@ vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) continue; if ((i == WEP_NEX && g_minstagib) || i == WEP_PORTO || (i == WEP_MINSTANEX && !g_minstagib) || i == WEP_TUBA) // skip port-o-launch, nex || minstanex and tuba continue; - weapon_hit = weapon_hits[i-WEP_FIRST]; - weapon_damage = weapon_fired[i-WEP_FIRST]; + weapon_stats = weapon_accuracy[i-WEP_FIRST]; float weapon_alpha; - if(weapon_damage) - { - weapon_stats = bound(0, floor(100 * weapon_hit / weapon_damage), 100); + if(weapon_stats >= 0) weapon_alpha = scoreboard_alpha_fg; - } else weapon_alpha = 0.2 * scoreboard_alpha_fg; // weapon icon drawpic_aspect_skin(pos, strcat("weapon", self.netname), '1 0 0' * weapon_width + '0 1 0' * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL); // the accuracy - if(weapon_damage) { + if(weapon_stats >= 0) { weapons_with_stats += 1; average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy @@ -989,13 +977,6 @@ vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size) float padding; padding = (weapon_width - stringwidth(s, FALSE, '1 0 0' * fontsize)) / 2; // center the accuracy value - weapon_damage = weapon_fired[self.weapon-WEP_FIRST]; - if(weapon_damage) - { - weapon_hit = weapon_hits[self.weapon-WEP_FIRST]; - weapon_stats = floor(100 * weapon_hit / weapon_damage); - } - if (acc_levels) { // find the max level lower than weapon_stats @@ -1298,183 +1279,3 @@ void HUD_DrawScoreboard() scoreboard_bottom = pos_y + 2 * hud_fontsize_y; } - -void HUD_DrawAccuracyStats_Description_Hitscan(vector position) -{ - drawstring(position + '0 3 0' * hud_fontsize_y, "Shots fired:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - drawstring(position + '0 5 0' * hud_fontsize_y, "Shots hit:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - drawstring(position + '0 7 0' * hud_fontsize_y, "Accuracy:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - drawstring(position + '0 9 0' * hud_fontsize_y, "Shots missed:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); -} - -void HUD_DrawAccuracyStats_Description_Splash(vector position) -{ - drawstring(position + '0 3 0' * hud_fontsize_y, "Maximum damage:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - drawstring(position + '0 5 0' * hud_fontsize_y, "Actual damage:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - drawstring(position + '0 7 0' * hud_fontsize_y, "Accuracy:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - drawstring(position + '0 9 0' * hud_fontsize_y, "Damage wasted:", hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); -} - -void HUD_DrawAccuracyStats() -{ - float i, count_hitscan, count_splash, row; // count is the number of 'colums' - float weapon_hit, weapon_damage, weapon_stats; - float left_border; // position where the weapons start, the description is in the border - vector fill_colour, fill_size; - vector pos; - vector border_colour; - - float col_margin = 20; // pixels between the columns - float row_margin = 20; // pixels between the rows - - fill_size_x = 5 * hud_fontsize_x; // width of the background - fill_size_y = 10 * hud_fontsize_y; // height of the background - - drawfont = hud_bigfont; - pos_x = 0; - pos_y = SCOREBOARD_OFFSET; - pos_z = 0; - drawstringcenter(pos, "Weapon Accuracy", 2 * hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - left_border = col_margin + 11 * hud_fontsize_x; - - drawfont = hud_font; - - if(warmup_stage) - { - pos_y += 40; - if(mod(time, 1) >= 0.4) - drawstringcenter(pos, "Stats are not tracked during warmup stage", hud_fontsize, '1 1 0', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - return; - } - - if(gametype == GAME_RACE || gametype == GAME_CTS) - { - pos_y += 40; - if(mod(time, 1) >= 0.4) - drawstringcenter(pos, "Stats are not tracked in Race/CTS", hud_fontsize, '1 1 0', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - return; - } - - float top_border_hitscan = SCOREBOARD_OFFSET + 55; // position where the hitscan row starts: pixels down the screen - HUD_DrawAccuracyStats_Description_Hitscan('1 0 0' * col_margin + '0 1 0' * top_border_hitscan); - - float top_border_splash = SCOREBOARD_OFFSET + 175; // position where the splash row starts: pixels down the screen - HUD_DrawAccuracyStats_Description_Splash('1 0 0' * col_margin + '0 1 0' * top_border_splash); - - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - { - self = get_weaponinfo(i); - if not(self.weapons) - continue; - weapon_hit = weapon_hits[i-WEP_FIRST]; - weapon_damage = weapon_fired[i-WEP_FIRST]; - border_colour = (i == activeweapon) ? '1 1 1' : '0 0 0'; // white or black border - - if (weapon_damage) { - if (self.spawnflags & WEP_TYPE_SPLASH) { - weapon_stats = bound(0, floor(100 * weapon_hit / weapon_damage), 100); - - fill_colour_x = 1 - 0.015 * weapon_stats; - fill_colour_y = 1 - 0.015 * (100 - weapon_stats); - - // how the background colour is calculated - // % red green red_2 green_2 - // 0 1 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 10 0.85 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 20 0.70 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 30 0.55 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 40 0.40 0.10 1 - % * 0.015 1 - (100 - %) * 0.015 - // 50 0.25 0.25 1 - % * 0.015 1 - (100 - %) * 0.015 - // 60 0.10 0.40 1 - % * 0.015 1 - (100 - %) * 0.015 - // 70 0 0.55 1 - % * 0.015 1 - (100 - %) * 0.015 - // 80 0 0.70 1 - % * 0.015 1 - (100 - %) * 0.015 - // 90 0 0.85 1 - % * 0.015 1 - (100 - %) * 0.015 - // 100 0 1 1 - % * 0.015 1 - (100 - %) * 0.015 - - if ((left_border + count_splash * (fill_size_x + col_margin) + fill_size_x) >= vid_conwidth) - { - count_splash = 0; - ++row; - HUD_DrawAccuracyStats_Description_Splash('1 0 0' * col_margin + '0 1 0' * (top_border_splash + row * (fill_size_y + row_margin))); - } - - pos_x = left_border + count_splash * (fill_size_x + col_margin); - pos_y = top_border_splash + row * (fill_size_y + row_margin); - - // background - drawpic(pos, "gfx/scoreboard/accuracy_bg", fill_size , fill_colour, scoreboard_alpha_bg, DRAWFLAG_NORMAL); - drawborderlines(autocvar_scoreboard_border_thickness, pos, fill_size, border_colour, scoreboard_alpha_bg, DRAWFLAG_NORMAL); - - // the weapon - drawpic(pos, strcat("gfx/weapons/weapon", self.netname), '1 0.5 0' * fill_size_x , '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the amount of shots fired or max damage - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 3 0' * hud_fontsize_y, ftos(weapon_damage), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the amount of hits or actual damage - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 5 0' * hud_fontsize_y, ftos(weapon_hit), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the accuracy - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 7 0' * hud_fontsize_y, strcat(ftos(weapon_stats),"%"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the amount of shots missed or damage wasted - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 9 0' * hud_fontsize_y, ftos(max(0, weapon_damage - weapon_hit)), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - ++count_splash; - } else if (self.spawnflags & WEP_TYPE_HITSCAN) { - weapon_stats = bound(0, floor(100 * weapon_hit / weapon_damage), 100); - - fill_colour_x = 1 - 0.015 * weapon_stats; - fill_colour_y = 1 - 0.015 * (100 - weapon_stats); - - // how the background colour is calculated - // % red green red_2 green_2 - // 0 1 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 10 0.850 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 20 0.70 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 30 0.55 0 1 - % * 0.015 1 - (100 - %) * 0.015 - // 40 0.40 0.10 1 - % * 0.015 1 - (100 - %) * 0.015 - // 50 0.25 0.25 1 - % * 0.015 1 - (100 - %) * 0.015 - // 60 0.10 0.40 1 - % * 0.015 1 - (100 - %) * 0.015 - // 70 0 0.55 1 - % * 0.015 1 - (100 - %) * 0.015 - // 80 0 0.70 1 - % * 0.015 1 - (100 - %) * 0.015 - // 90 0 0.85 1 - % * 0.015 1 - (100 - %) * 0.015 - // 100 0 1 1 - % * 0.015 1 - (100 - %) * 0.015 - - if ((left_border + count_hitscan * (fill_size_x + col_margin) + fill_size_x + cvar("stats_right_margin")) >= vid_conwidth) - { - count_hitscan = 0; - ++row; - HUD_DrawAccuracyStats_Description_Hitscan('1 0 0' * col_margin + '0 1 0' * (top_border_hitscan + row * (fill_size_y + row_margin))); - } - - pos_x = left_border + count_hitscan * (fill_size_x + col_margin); - pos_y = top_border_hitscan + row * (fill_size_y + row_margin); - - // background - drawpic(pos, "gfx/scoreboard/accuracy_bg", fill_size , fill_colour, scoreboard_alpha_bg, DRAWFLAG_NORMAL); - drawborderlines(autocvar_scoreboard_border_thickness, pos, fill_size, border_colour, scoreboard_alpha_bg, DRAWFLAG_NORMAL); - - // the weapon - drawpic(pos, strcat("gfx/weapons/weapon", self.netname), '1 0.5 0' * fill_size_x , '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the amount of shots fired or max damage - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 3 0' * hud_fontsize_y, ftos(weapon_damage), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the amount of hits or actual damage - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 5 0' * hud_fontsize_y, ftos(weapon_hit), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the accuracy - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 7 0' * hud_fontsize_y, strcat(ftos(weapon_stats),"%"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - // the amount of shots missed or damage wasted - drawstringright(pos + '4.5 0 0' * hud_fontsize_x + '0 9 0' * hud_fontsize_y, ftos(max(0, weapon_damage - weapon_hit)), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL); - - ++count_hitscan; - } - } - } -} diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index 87484cd8f..8cc24634c 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -111,6 +111,7 @@ const float ENT_CLIENT_TRIGGER_MUSIC = 26; const float ENT_CLIENT_HOOK = 27; const float ENT_CLIENT_LGBEAM = 28; const float ENT_CLIENT_GAUNTLET = 29; +const float ENT_CLIENT_ACCURACY = 30; const float ENT_CLIENT_TURRET = 40; @@ -300,8 +301,6 @@ const float STAT_SWITCHWEAPON = 36; const float STAT_GAMESTARTTIME = 37; const float STAT_STRENGTH_FINISHED = 38; const float STAT_INVINCIBLE_FINISHED = 39; -const float STAT_DAMAGE_HITS = 40; // Used by the weapon stats code, represents the total amount of damage done to other players -const float STAT_DAMAGE_FIRED = 41;// Used by the weapon stats code, represents the total amount of potential damage fired const float STAT_PRESSED_KEYS = 42; const float STAT_ALLOW_OLDNEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config const float STAT_FUEL = 44; diff --git a/qcsrc/server/accuracy.qc b/qcsrc/server/accuracy.qc new file mode 100644 index 000000000..c2a68d723 --- /dev/null +++ b/qcsrc/server/accuracy.qc @@ -0,0 +1,122 @@ +.entity accuracy; +.float accuracy_hit[WEP_MAXCOUNT]; +.float accuracy_fired[WEP_MAXCOUNT]; +FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(accuracy_hit); +FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(accuracy_fired); + +float accuracy_byte(float n, float d) +{ + if(d == 0) + return 0; + return 1.0 + rint(n * 254.0 / d); +} + +float accuracy_send(entity to, float sf) +{ + float w, f; + entity a; + WriteByte(MSG_ENTITY, ENT_CLIENT_ACCURACY); + + a = self.owner; + if(a.classname == "spectator") + a = a.enemy; + a = a.accuracy; + + if(to != a.owner) + if not(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share) + sf = 0; + // note: zero sendflags can never be sent... so we can use that to say that we send no accuracy! + WriteInt24_t(MSG_ENTITY, sf); + if(sf == 0) + return TRUE; + // note: we know that client and server agree about SendFlags... + for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w, f *= 2) + { + if(sf & f) + WriteByte(MSG_ENTITY, accuracy_byte(self.(accuracy_hit[w]), self.(accuracy_fired[w]))); + } + return TRUE; +} + +// init/free +void accuracy_init(entity e) +{ + e.accuracy = spawn(); + e.accuracy.owner = e; + e.accuracy.classname = "accuracy"; + e.accuracy.drawonlytoclient = e; + Net_LinkEntity(e.accuracy, FALSE, 0, accuracy_send); +} + +void accuracy_free(entity e) +{ + remove(e.accuracy); +} + +// force a resend of a player's accuracy stats +void accuracy_resend(entity e) +{ + e.accuracy.SendFlags = 0xFFFFFF; +} + +// update accuracy stats +void accuracy_set(entity e, float w, float hit, float fired) +{ + entity a; + float b; + a = e.accuracy; + if(!a) + return; + w -= WEP_FIRST; + b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])); + a.(accuracy_hit[w]) = hit; + a.(accuracy_fired[w]) = fired; + if(b == accuracy_byte(hit, fired)) + return; + w = pow(2, w); + a.SendFlags |= w; + FOR_EACH_CLIENT(a) + if(a.classname == "spectator") + if(a.enemy == e) + a.SendFlags |= w; +} + +void accuracy_add(entity e, float w, float hit, float fired) +{ + entity a; + float b; + a = e.accuracy; + if(!a || !(hit || fired)) + return; + w -= WEP_FIRST; + b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])); + if(hit) + a.(accuracy_hit[w]) += hit; + if(fired) + a.(accuracy_fired[w]) += fired; + if(b == accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w]))) + return; + w = pow(2, w); + a.SendFlags |= w; + FOR_EACH_CLIENT(a) + if(a.classname == "spectator") + if(a.enemy == e) + a.SendFlags |= w; +} + +float accuracy_isgooddamage(entity attacker, entity targ) +{ + if(!inWarmupStage) + if(targ.flags & FL_CLIENT) + if(targ.deadflag == DEAD_NO) + if(IsDifferentTeam(attacker, targ)) + return TRUE; + return FALSE; +} + +float accuracy_canbegooddamage(entity attacker) +{ + if(!inWarmupStage) + return TRUE; + return FALSE; +} diff --git a/qcsrc/server/accuracy.qh b/qcsrc/server/accuracy.qh new file mode 100644 index 000000000..afdc46a5a --- /dev/null +++ b/qcsrc/server/accuracy.qh @@ -0,0 +1,17 @@ +.float cvar_cl_accuracy_data_share; +var float autocvar_sv_accuracy_data_share = 1; + +// init/free +void accuracy_init(entity e); +void accuracy_free(entity e); + +// force a resend of a player's accuracy stats +void accuracy_resend(entity e); + +// update accuracy stats +void accuracy_set(entity e, float w, float hit, float fired); +void accuracy_add(entity e, float w, float hit, float fired); + +// helper +float accuracy_isgooddamage(entity attacker, entity targ); +float accuracy_canbegooddamage(entity attacker); diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 87d567f29..2985b45b4 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -631,6 +631,8 @@ void PutObserverInServer (void) PlayerScore_Clear(self); // clear scores when needed + accuracy_resend(self); + self.spectatortime = time; self.classname = "observer"; @@ -853,6 +855,8 @@ void PutClientInServer (void) entity spot, oldself; float j; + accuracy_resend(self); + if(self.team < 0) JoinBestTeam(self, FALSE, TRUE); @@ -1496,6 +1500,7 @@ void ClientConnect (void) PlayerScore_Attach(self); ClientData_Attach(); + accuracy_init(self); bot_clientconnect(); @@ -1774,6 +1779,7 @@ void ClientDisconnect (void) Spawnqueue_Remove(self); } + accuracy_free(self); ClientData_Detach(); PlayerScore_Detach(self); @@ -2271,33 +2277,13 @@ void GetPressedKeys(void) { self.pressedkeys &~= KEY_CROUCH; } -void update_stats (float number, float hit, float fired) { -// self.stat_hit = number + ((number==0) ? 1 : 64) * hit * sv_accuracy_data_share; -// self.stat_fired = number + ((number==0) ? 1 : 64) * fired * sv_accuracy_data_share; - - if(number) { - self.stat_hit = number + 64 * hit * sv_accuracy_data_share; - self.stat_fired = number + 64 * fired * sv_accuracy_data_share; - } else { - self.stat_hit = hit * sv_accuracy_data_share; - self.stat_fired = fired * sv_accuracy_data_share; - } -} - /* ====================== spectate mode routines ====================== */ -.float weapon_count; void SpectateCopy(entity spectatee) { - if(spectatee.weapon_count < WEP_LAST) { - update_stats (spectatee.weapon_count, spectatee.cvar_cl_accuracy_data_share * floor(spectatee.stats_hit[spectatee.weapon_count - 1]), spectatee.cvar_cl_accuracy_data_share * floor(spectatee.stats_fired[spectatee.weapon_count - 1])); - spectatee.weapon_count ++; - } else - update_stats (0, spectatee.cvar_cl_accuracy_data_share * spectatee.stat_hit, spectatee.cvar_cl_accuracy_data_share * spectatee.stat_fired); - other = spectatee; MUTATOR_CALLHOOK(SpectateCopy); self.armortype = spectatee.armortype; @@ -2365,8 +2351,7 @@ float SpectateNext() { WriteEntity(MSG_ONE, self.enemy); //stuffcmd(self, "set viewsize $tmpviewsize \n"); self.movetype = MOVETYPE_NONE; - - self.enemy.weapon_count = 0; + accuracy_resend(self); if(!SpectateUpdate()) PutObserverInServer(); @@ -2415,8 +2400,6 @@ void LeaveSpectatorMode() if(cvar("g_campaign")) campaign_bots_may_start = 1; - self.stat_count = WEP_LAST; - PutClientInServer(); if(self.classname == "player") @@ -2520,14 +2503,12 @@ void SpectatorThink() self.classname = "spectator"; } else { self.classname = "observer"; - self.stat_count = WEP_LAST; PutClientInServer(); } } else if (self.BUTTON_ATCK2) { self.welcomemessage_time = 0; self.flags &~= FL_JUMPRELEASED; self.classname = "observer"; - self.stat_count = WEP_LAST; PutClientInServer(); } else { if(!SpectateUpdate()) @@ -2965,15 +2946,6 @@ void PlayerPostThink (void) stuffcmd(self, strcat("name ", self.netname, substring(ftos(random()), 2, -1), "\n")); } - // send the clients accuracy stats to the client - if(self.stat_count > 0) - if(frametime) - { - self.stat_hit = self.stat_count + 64 * floor(self.(stats_hit[self.stat_count - 1])); - self.stat_fired = self.stat_count + 64 * floor(self.(stats_fired[self.stat_count - 1])); - self.stat_count -= 1; - } - if(sv_maxidle && frametime) { // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero). diff --git a/qcsrc/server/cl_weaponsystem.qc b/qcsrc/server/cl_weaponsystem.qc index 04666c47f..2770d4314 100644 --- a/qcsrc/server/cl_weaponsystem.qc +++ b/qcsrc/server/cl_weaponsystem.qc @@ -159,14 +159,8 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, vector s_forward, vector m v_up = vu; // track max damage - if not(inWarmupStage) { - entity w; - w = get_weaponinfo(ent.weapon); - if(w.spawnflags & WEP_TYPE_SPLASH) { // splash damage - ent.stats_fired[ent.weapon - 1] += maxdamage; - ent.stat_fired = ent.weapon + 64 * floor(ent.stats_fired[ent.weapon - 1]); - } - } + if(accuracy_canbegooddamage(ent)) + accuracy_add(ent, ent.weapon, maxdamage, 0); W_HitPlotAnalysis(ent, v_forward, v_right, v_up); diff --git a/qcsrc/server/clientcommands.qc b/qcsrc/server/clientcommands.qc index 0cbe3017e..e221911d3 100644 --- a/qcsrc/server/clientcommands.qc +++ b/qcsrc/server/clientcommands.qc @@ -235,7 +235,6 @@ void SV_ParseClientCommand(string s) { self.caplayer = 1; PlayerScore_Clear(self); bprint ("^4", self.netname, "^4 is playing now\n"); - self.stat_count = WEP_LAST; PutClientInServer(); if(cvar("g_campaign")) campaign_bots_may_start = 1; diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index b99f27544..a0718d839 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -598,18 +598,8 @@ string matchid; .float hitplotfh; .string noise4; -.float stat_hit; -.float stat_fired; -.float stat_count; - -.float stats_hit[WEP_MAXCOUNT]; // for hitscan bullets hit -.float stats_fired[WEP_MAXCOUNT]; // for hitscan bullets fired - .float last_pickup; -FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(stats_hit); -FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(stats_fired); - .float stat_leadlimit; float radar_showennemies; diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index 460c608dc..387514be4 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -844,19 +844,6 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float } } -void Damage_RecordDamage(entity attacker, float deathtype, float damage) -{ - float weaponid; - weaponid = DEATH_WEAPONOF(deathtype); - - if not(inWarmupStage) - if (weaponid) - if ((clienttype(attacker) == CLIENTTYPE_REAL) | (clienttype(attacker) == CLIENTTYPE_BOT)) { - attacker.stats_hit[weaponid - 1] += damage; - attacker.stat_hit = weaponid + 64 * floor(attacker.stats_hit[weaponid - 1]); - } -} - 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 @@ -875,7 +862,6 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e float tfloorforce; float stat_damagedone; - float stat_maxdamage; if(RadiusDamage_running) { @@ -913,7 +899,6 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e } stat_damagedone = 0; - stat_maxdamage = 0; targ = WarpZone_FindRadius (blastorigin, rad, FALSE); while (targ) @@ -1031,14 +1016,8 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e { total_damage_to_creatures += finaldmg; - if(targ.flags & FL_CLIENT) - if(targ.deadflag == DEAD_NO) - if(targ != attacker) - if(!teamplay || targ.team != attacker.team) - { + if(accuracy_isgooddamage(attacker, targ)) stat_damagedone += finaldmg; - stat_maxdamage += coredamage; - } } if(targ == directhitentity || DEATH_ISSPECIAL(deathtype)) @@ -1054,7 +1033,8 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e RadiusDamage_running = 0; - Damage_RecordDamage(attacker, deathtype, min(stat_maxdamage, stat_damagedone)); + if(!DEATH_ISSPECIAL(deathtype)) + accuracy_add(attacker, DEATH_WEAPONOFWEAPONDEATH(deathtype), 0, min(coredamage, stat_damagedone)); return total_damage_to_creatures; } @@ -1157,6 +1137,8 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt) e.fire_hitsound = FALSE; } } + if(accuracy_isgooddamage(o, e)) + accuracy_add(o, DEATH_WEAPONOFWEAPONDEATH(dt), 0, max(0, totaldamage - mindamage)); return max(0, totaldamage - mindamage); // can never be negative, but to make sure } else @@ -1169,6 +1151,8 @@ float Fire_AddDamage(entity e, entity o, float d, float t, float dt) e.fire_deathtype = dt; e.fire_owner = o; e.fire_hitsound = FALSE; + if(accuracy_isgooddamage(o, e)) + accuracy_add(o, DEATH_WEAPONOFWEAPONDEATH(dt), 0, d); return d; } } @@ -1203,8 +1187,6 @@ void Fire_ApplyDamage(entity e) } e.fire_hitsound = TRUE; - Damage_RecordDamage(e.fire_owner, e.fire_deathtype, d); - if not(IS_INDEPENDENT_PLAYER(e)) FOR_EACH_PLAYER(other) if(e != other) { diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index ded4518d3..4628aef45 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -801,8 +801,6 @@ void spawnfunc_worldspawn (void) addstat(STAT_INVINCIBLE_FINISHED, AS_FLOAT, invincible_finished); addstat(STAT_PRESSED_KEYS, AS_FLOAT, pressedkeys); addstat(STAT_FUEL, AS_INT, ammo_fuel); - addstat(STAT_DAMAGE_HITS, AS_INT, stat_hit); - addstat(STAT_DAMAGE_FIRED, AS_INT, stat_fired); addstat(STAT_SHOTORG, AS_INT, stat_shotorg); addstat(STAT_LEADLIMIT, AS_FLOAT, stat_leadlimit); addstat(STAT_BULLETS_LOADED, AS_INT, campingrifle_bulletcounter); @@ -1544,8 +1542,6 @@ only called if a time or frag limit has expired */ void NextLevel() { - float i; - gameover = TRUE; intermission_running = 1; @@ -1574,52 +1570,10 @@ void NextLevel() GameLogClose(); -// TO DO - -// save the stats to a text file on the client -// stuffcmd(other, log_stats "stats/file_name"); -// bprint stats -// stuffcmd(other, log_stats ""); -// use a filename similar to the demo name - // string file_name; - // file_name = strcat("\nlog_file \"stats/", strftime(TRUE, "%Y-%m-%d_%H-%M"), "_", mapname, ".txt\""); // open the log file - -// write a stats parser for the menu - - if(cvar("sv_accuracy_data_send")) { - string stats_to_send; - - FOR_EACH_CLIENT(other) { // make the string to send - FixIntermissionClient(other); - - if(other.cvar_cl_accuracy_data_share) { - stats_to_send = strcat(stats_to_send, ":hits:", other.netname); - - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - stats_to_send = strcat(stats_to_send, ":", ftos(other.stats_hit[i-1])); - - stats_to_send = strcat(stats_to_send, "\n:fired:", other.netname); - - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - stats_to_send = strcat(stats_to_send, ":", ftos(other.stats_fired[i-1])); - - stats_to_send = strcat(stats_to_send, "\n"); - } - } - - FOR_EACH_REALCLIENT(other) { // only spam humans - Score_NicePrint(other); // print the score - - if(other.cvar_cl_accuracy_data_receive) // send the stats string to all the willing clients - bprint(stats_to_send); - } - } else { // ye olde message - FOR_EACH_PLAYER(other) { - FixIntermissionClient(other); - - if(other.winning) - bprint(other.netname, " ^7wins.\n"); - } + FOR_EACH_PLAYER(other) { + FixIntermissionClient(other); + if(other.winning) + bprint(other.netname, " ^7wins.\n"); } if(cvar("g_campaign")) diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 5b8ec7127..25222a38f 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -468,28 +468,6 @@ string formatmessage(string msg) replacement = ftos(vlen(self.velocity - self.velocity_z * '0 0 1')); else if (escape == "S") replacement = ftos(vlen(self.velocity)); - else if (escape == "v") { - float weapon_number; - local entity stats; - - if(self.classname == "spectator") - stats = self.enemy; - else - stats = self; - - weapon_number = stats.weapon; - - if (!weapon_number) - weapon_number = stats.switchweapon; - - if (!weapon_number) - weapon_number = stats.cnt; - - if(stats.cvar_cl_accuracy_data_share && stats.stats_fired[weapon_number - 1]) - replacement = ftos(bound(0, floor(100 * stats.stats_hit[weapon_number - 1] / stats.stats_fired[weapon_number - 1]), 100)); - else - replacement = "~"; // or something to indicate NULL, not available - } msg = strcat(substring(msg, 0, p), replacement, substring(msg, p+2, strlen(msg) - (p+2))); p = p + strlen(replacement); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index c2718d4e8..104f58c38 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -35,6 +35,7 @@ campaign.qh ../common/mapinfo.qh ../common/util.qc +accuracy.qh csqcprojectile.qh csqceffects.qc @@ -168,6 +169,7 @@ monsters/fight.qc monsters/ai.qc monsters/m_monsters.qc monsters/monster_zombie.qc +accuracy.qc csqcprojectile.qc playerdemo.qc diff --git a/qcsrc/server/w_common.qc b/qcsrc/server/w_common.qc index 320ffb750..25ca2f5c6 100644 --- a/qcsrc/server/w_common.qc +++ b/qcsrc/server/w_common.qc @@ -28,6 +28,7 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f local vector hitloc, force, endpoint, dir; local entity ent, endent; local float endq3surfaceflags; + float totaldmg; float length; vector beampos; @@ -35,8 +36,6 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f entity pseudoprojectile; float f, ffs; - float hit; - railgun_start = start; railgun_end = end; @@ -47,6 +46,8 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f // go a little bit into the wall because we need to hit this wall later end = end + dir; + totaldmg = 0; + // trace multiple times until we hit a wall, each obstacle will be made // non-solid so we can hit the next, while doing this we spawn effects and // note down which entities were hit so we can damage them later @@ -121,18 +122,12 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f // get the details we need to call the damage function hitloc = ent.railgunhitloc; - //for stats so that team hit will count as a miss - if(ent.flags & FL_CLIENT) - if(ent.deadflag == DEAD_NO) - hit = 1; - - if(teams_matter) - if(ent.team == self.team) - hit = 0; - f = ExponentialFalloff(mindist, maxdist, halflifedist, ent.railgundistance); ffs = ExponentialFalloff(mindist, maxdist, forcehalflifedist, ent.railgundistance); + if(accuracy_isgooddamage(self.owner, ent)) + totaldmg += bdamage * f; + // apply the damage if (ent.takedamage) Damage (ent, self, self, bdamage * f, deathtype, hitloc, force * ffs); @@ -151,16 +146,7 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f } // calculate hits and fired shots for hitscan - if not(inWarmupStage) - { - self.stats_fired[self.weapon - 1] += 1; - self.stat_fired = self.weapon + 64 * floor(self.stats_fired[self.weapon - 1]); - - if(hit) { - self.stats_hit[self.weapon - 1] += 1; - self.stat_hit = self.weapon + 64 * floor(self.stats_hit[self.weapon - 1]); - } - } + accuracy_add(self, self.weapon, 0, min(bdamage, totaldmg)); trace_endpos = endpoint; trace_ent = endent; @@ -170,11 +156,13 @@ void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, f .float dmg_edge; .float dmg_force; .float dmg_radius; +.float dmg_total; void W_BallisticBullet_Hit (void) { - float f; + float f, q; f = pow(bound(0, vlen(self.velocity) / vlen(self.oldvelocity), 1), 2); // energy multiplier + q = 1 + self.dmg_edge / self.dmg; if(other.solid == SOLID_BSP) Damage_DamageInfo(self.origin, self.dmg * f, 0, 0, max(1, self.dmg_force) * normalize(self.velocity) * f, self.projectiledeathtype, self); @@ -195,19 +183,21 @@ void W_BallisticBullet_Hit (void) if(self.dmg_edge != 0) { if(headshot) + { + f *= q; AnnounceTo(self.owner, "headshot"); - if(yoda) - AnnounceTo(self.owner, "awesome"); + } } + if(yoda) + AnnounceTo(self.owner, "awesome"); // calculate hits for ballistic weapons - if (other.flags & FL_CLIENT) // is the player a client - if (other.deadflag == DEAD_NO) // is the victim a corpse - if ((!(teamplay)) | (other.team != self.owner.team)) // not teamplay (ctf, kh, tdm etc) or the victim is in the same team - if not(inWarmupStage) // not in warm up stage + if(accuracy_isgooddamage(self.owner, other)) { - self.owner.stats_hit[self.owner.weapon - 1] += 1; - self.owner.stat_hit = self.owner.weapon + 64 * floor(self.owner.stats_hit[self.owner.weapon - 1]); + // do not exceed 100% + q = min(self.dmg * q, self.dmg_total + f * self.dmg) - self.dmg_total; + self.dmg_total += f * self.dmg; + accuracy_add(self.owner, self.owner.weapon, 0, q); } } @@ -424,13 +414,6 @@ void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, f savetime = frametime; frametime = 0.05; - // update the accuracy stats - increase shots fired by 1 - if not(inWarmupStage) - { - oldself.stats_fired[oldself.weapon - 1] += 1; - oldself.stat_fired = oldself.weapon + 64 * floor(oldself.stats_fired[oldself.weapon - 1]); - } - for(;;) { // DP tracetoss is stupid and always traces in 0.05s @@ -491,13 +474,6 @@ void fireBallisticBullet(vector start, vector dir, float spread, float pSpeed, f return; } - // update the accuracy stats - if not(inWarmupStage) - { - self.stats_fired[self.weapon - 1] += 1; - self.stat_fired = self.weapon + 64 * floor(self.stats_fired[self.weapon - 1]); - } - if(tracereffects & EF_RED) CSQCProjectile(proj, TRUE, PROJECTILE_BULLET_GLOWING_TRACER, TRUE); else if(tracereffects & EF_BLUE) diff --git a/qcsrc/server/w_electro.qc b/qcsrc/server/w_electro.qc index 1600a1c9a..f64e4ca8d 100644 --- a/qcsrc/server/w_electro.qc +++ b/qcsrc/server/w_electro.qc @@ -265,8 +265,9 @@ void lgbeam_think() f = ExponentialFalloff(cvar("g_balance_electro_primary_falloff_mindist"), cvar("g_balance_electro_primary_falloff_maxdist"), cvar("g_balance_electro_primary_falloff_halflifedist"), vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); + if(accuracy_isgooddamage(self.owner, trace_ent)) + accuracy_add(self.owner, WEP_ELECTRO, 0, cvar("g_balance_electro_primary_damage") * dt * f); Damage (trace_ent, self.owner, self.owner, cvar("g_balance_electro_primary_damage") * dt * f, WEP_ELECTRO, trace_endpos, force * dt); - Damage_RecordDamage(self.owner, WEP_ELECTRO, cvar("g_balance_electro_primary_damage") * dt * f); } W_Plasma_TriggerCombo(trace_endpos, cvar("g_balance_electro_primary_comboradius"), self.owner); diff --git a/qcsrc/server/w_fireball.qc b/qcsrc/server/w_fireball.qc index e78bb2bbc..3905ac95f 100644 --- a/qcsrc/server/w_fireball.qc +++ b/qcsrc/server/w_fireball.qc @@ -44,10 +44,12 @@ void W_Fireball_Explode (void) if(points <= 0) continue; dir = normalize(e.origin + e.view_ofs - self.origin); + + if(accuracy_isgooddamage(self.realowner, e)) + accuracy_add(self.realowner, WEP_FIREBALL, 0, cvar("g_balance_fireball_primary_bfgdamage") * points); + Damage(e, self, self.realowner, cvar("g_balance_fireball_primary_bfgdamage") * points, self.projectiledeathtype | HITTYPE_BOUNCE | HITTYPE_SPLASH, e.origin + e.view_ofs, cvar("g_balance_fireball_primary_bfgforce") * dir); pointparticles(particleeffectnum("fireball_bfgdamage"), e.origin, -1 * dir, 1); - - Damage_RecordDamage(self.owner, self.projectiledeathtype, cvar("g_balance_fireball_primary_bfgdamage") * points); } } diff --git a/qcsrc/server/w_laser.qc b/qcsrc/server/w_laser.qc index db11eb395..79d782c95 100644 --- a/qcsrc/server/w_laser.qc +++ b/qcsrc/server/w_laser.qc @@ -147,8 +147,9 @@ void gauntletbeam_think() { vector force; force = w_shotdir * myforce; + if(accuracy_isgooddamage(self.owner, trace_ent)) + accuracy_add(self.owner, WEP_LASER, 0, damage * dt); Damage (trace_ent, self.owner, self.owner, damage * dt, WEP_LASER | HITTYPE_SECONDARY, trace_endpos, force * dt); - Damage_RecordDamage(self.owner, WEP_LASER | HITTYPE_SECONDARY, damage * dt); } // draw effect diff --git a/qcsrc/server/w_minstanex.qc b/qcsrc/server/w_minstanex.qc index 1c9f0bd70..f1d9df9cb 100644 --- a/qcsrc/server/w_minstanex.qc +++ b/qcsrc/server/w_minstanex.qc @@ -9,7 +9,7 @@ void W_MinstaNex_Attack (void) float flying; flying = IsFlying(self); // do this BEFORE to make the trace values from FireRailgunBullet last - W_SetupShot (self, TRUE, 0, "weapons/minstanexfire.wav", 0); + W_SetupShot (self, TRUE, 0, "weapons/minstanexfire.wav", 10000); yoda = 0; damage_goodhits = 0; diff --git a/qcsrc/server/w_shotgun.qc b/qcsrc/server/w_shotgun.qc index c5f1ed12c..3efb0e973 100644 --- a/qcsrc/server/w_shotgun.qc +++ b/qcsrc/server/w_shotgun.qc @@ -69,8 +69,9 @@ void shotgun_meleethink (void) { vector force; force = angle * cvar("g_balance_shotgun_secondary_force"); + if(accuracy_isgooddamage(self.owner, trace_ent)) + accuracy_add(self.owner, WEP_SHOTGUN, 0, cvar("g_balance_shotgun_secondary_damage") * min(1, f + 1)); Damage (trace_ent, self.owner, self.owner, cvar("g_balance_shotgun_secondary_damage") * min(1, f + 1), WEP_SHOTGUN | HITTYPE_SECONDARY , self.owner.origin + self.owner.view_ofs, force); - Damage_RecordDamage(self.owner, WEP_SHOTGUN | HITTYPE_SECONDARY, cvar("g_balance_shotgun_secondary_damage") * min(1, f + 1)); remove(self); } else if(time >= self.cnt + cvar("g_balance_shotgun_secondary_melee_time")) // missed, remove ent