...
 
Commits (31)
......@@ -29,7 +29,7 @@ test_sv_game:
- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- make
- EXPECT=78686205fc72d91c106a3022d0d8b083
- EXPECT=1cfe3a32d5e57d1917e14bce27b35b37
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
......
Tue Oct 30 07:24:07 CET 2018
Fri Nov 9 07:24:16 CET 2018
This diff is collapsed.
This diff is collapsed.
......@@ -14,7 +14,7 @@ pt "Portuguese" "Português" 98%
pt_BR "Portuguese (Brazil)" "Português (Brasil)" 99%
ro "Romanian" "Romana" 83%
fi "Finnish" "Suomi" 33%
el "Greek" "Ελληνική" 34%
el "Greek" "Ελληνική" 43%
be "Belarusian" "Беларуская" 61%
bg "Bulgarian" "Български" 68%
ru "Russian" "Русский" 99%
......
{
"files.associations": {
"*.qc": "c",
"*.qh": "c"
},
"C_Cpp.errorSquiggles": "Disabled"
}
......@@ -74,6 +74,8 @@ bool autocvar_hud_panel_scoreboard_spectators_showping = true;
bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
float autocvar_hud_panel_scoreboard_minwidth = 0.4;
AUTOCVAR(_scoreboard_instagib, bool, false, "");
// wrapper to put all possible scores titles through gettext
string TranslateScoresLabel(string l)
{
......@@ -116,6 +118,7 @@ string TranslateScoresLabel(string l)
case "suicides": return CTX(_("SCO^suicides"));
case "takes": return CTX(_("SCO^takes"));
case "ticks": return CTX(_("SCO^ticks"));
case "country": return CTX(_("SCO^country"));
default: return l;
}
}
......@@ -365,7 +368,7 @@ void Cmd_Scoreboard_Help()
// otherwise the previous exclusive rule warns anyway
// e.g. -teams,rc,cts,lms/kills ?+rc/kills
#define SCOREBOARD_DEFAULT_COLUMNS \
"ping pl fps name |" \
"ping pl fps name country |" \
" -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
" -teams,lms/deaths +ft,tdm/deaths" \
" +tdm/sum" \
......@@ -382,17 +385,41 @@ void Cmd_Scoreboard_Help()
" +dom/ticks +dom/takes" \
" -lms,rc,cts,inv,nb/score"
#define SCOREBOARD_DEFAULT_COLUMNS_INSTAGIB \
"ping pl fps name country |" \
" -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
" -teams,lms/deaths +ft,tdm/deaths" \
" +tdm/sum" \
" -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
" -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
" -cts,nb,rc/accuracy" \
" +tdm,ft,dom,ons,as/teamkills"\
" +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
" +lms/lives +lms/rank" \
" +kh/kckills +kh/losses +kh/caps" \
" ?+rc/laps ?+rc/time +rc,cts/fastest" \
" +as/objectives +nb/faults +nb/goals" \
" +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
" +dom/ticks +dom/takes" \
" -lms,rc,cts,inv,nb/score"
void Cmd_Scoreboard_SetFields(int argc)
{
TC(int, argc);
int i, slash;
string str, pattern;
string str, pattern, default_scoreboard;
bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
int missing;
if(!gametype)
return; // do nothing, we don't know gametype and scores yet
if (autocvar__scoreboard_instagib) {
default_scoreboard = SCOREBOARD_DEFAULT_COLUMNS_INSTAGIB;
} else {
default_scoreboard = SCOREBOARD_DEFAULT_COLUMNS;
}
// sbt_fields uses strunzone on the titles!
if(!sbt_field_title[0])
for(i = 0; i < MAX_SBT_FIELDS; ++i)
......@@ -403,16 +430,12 @@ void Cmd_Scoreboard_SetFields(int argc)
argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
if(argc < 3)
argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
argc = tokenizebyseparator(strcat("0 1 ", default_scoreboard), " ");
if(argc == 3)
{
if(argv(2) == "default" || argv(2) == "expand_default")
{
if(argv(2) == "expand_default")
cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
}
if(argv(2) == "default")
argc = tokenizebyseparator(strcat("0 1 ", default_scoreboard), " ");
else if(argv(2) == "all")
{
string s = "ping pl name |"; // scores without a label
......@@ -463,6 +486,7 @@ void Cmd_Scoreboard_SetFields(int argc)
{
case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
case "country": sbt_field[sbt_num_fields] = SP_COUNTRY; break;
case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
......@@ -624,6 +648,10 @@ string Scoreboard_GetField(entity pl, PlayerScoreField field)
sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
return ftos(f);
case SP_COUNTRY:
float cn = pl.(scores(SP_COUNTRY));
return chr2str((cn >> 8) & 0xff, cn & 0xff);
case SP_PL:
if (!pl.gotscores)
return _("N/A");
......@@ -869,7 +897,7 @@ void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, i
if(field == SP_SEPARATOR)
break;
if(is_spec && field != SP_NAME && field != SP_PING) {
if(is_spec && field != SP_NAME && field != SP_PING && field != SP_COUNTRY) {
pos.x += sbt_field_size[i] + hud_fontsize.x;
continue;
}
......
......@@ -427,16 +427,25 @@ CLASS(DebugText3d, Object)
return;
}
int size = 8;
int size = 11;
vector screen_pos = project_3d_to_2d(this.origin) + since_created * this.velocity;
float align = GetResourceAmount(this, RESOURCE_HEALTH);
if (align > 0)
screen_pos.x -= stringwidth(this.message, true, size * '1 1 0') * min(1, align);
if (screen_pos.z < 0) return; // behind camera
screen_pos.z = 0;
float align = GetResourceAmount(this, RESOURCE_HEALTH);
string msg;
vector msg_pos;
vector rgb = '1 1 0';
drawcolorcodedstring2_builtin(screen_pos, this.message, size * '1 1 0', rgb, alpha_, DRAWFLAG_NORMAL);
int n = tokenizebyseparator(this.message, "\n");
for(int k = 0; k < n; ++k)
{
msg = argv(k);
msg_pos = screen_pos + k * 1.25 * size * eY;
if (align > 0)
msg_pos.x -= stringwidth(msg, true, size * '1 1 0') * min(1, align);
drawcolorcodedstring_builtin(msg_pos, msg, size * '1 1 0', alpha_, DRAWFLAG_NORMAL);
}
}
ATTRIB(DebugText3d, draw2d, void(DebugText3d), DebugText3d_draw2d);
ENDCLASS(DebugText3d)
......
......@@ -24,7 +24,7 @@ void multi_trigger(entity this)
return; // allready been triggered
}
if(this.spawnflags & ONLY_PLAYERS && !IS_PLAYER(this.enemy))
if((this.spawnflags & ONLY_PLAYERS) && !IS_PLAYER(this.enemy))
{
return; // only players
}
......
......@@ -21,14 +21,16 @@ AUTOCVAR_SAVE(cl_damagetext_size_max, float, 16, "Damage
AUTOCVAR_SAVE(cl_damagetext_size_max_damage, float, 140, "How much damage is considered large");
AUTOCVAR_SAVE(cl_damagetext_alpha_start, float, 1, "Damage text initial alpha");
AUTOCVAR_SAVE(cl_damagetext_alpha_lifetime, float, 3, "Damage text lifetime in seconds");
AUTOCVAR_SAVE(cl_damagetext_velocity, vector, '0 0 20', "Damage text move direction (world coordinates)");
AUTOCVAR_SAVE(cl_damagetext_offset, vector, '0 -40 0', "Damage text offset (screen coordinates)");
AUTOCVAR_SAVE(cl_damagetext_velocity_screen, vector, '0 0 0', "Damage text move direction (screen coordinates)");
AUTOCVAR_SAVE(cl_damagetext_velocity_world, vector, '0 0 20', "Damage text move direction (world coordinates relative to player's view)");
AUTOCVAR_SAVE(cl_damagetext_offset_screen, vector, '0 -45 0', "Damage text offset (screen coordinates)");
AUTOCVAR_SAVE(cl_damagetext_offset_world, vector, '0 0 0', "Damage text offset (world coordinates relative to player's view)");
AUTOCVAR_SAVE(cl_damagetext_accumulate_range, float, 30, "Damage text spawned within this range is accumulated");
AUTOCVAR_SAVE(cl_damagetext_accumulate_alpha_rel, float, 0.65, "Only update existing damage text when it's above this much percentage (0 to 1) of the starting alpha");
AUTOCVAR_SAVE(cl_damagetext_friendlyfire, bool, true, "Show damage text for friendlyfire too");
AUTOCVAR_SAVE(cl_damagetext_friendlyfire, int, 1, "0: never show for friendly fire, 1: when more than 0 damage, 2: always");
AUTOCVAR_SAVE(cl_damagetext_friendlyfire_color, vector, '1 0 0', "Damage text color for friendlyfire");
AUTOCVAR_SAVE(cl_damagetext_2d, bool, true, "Show damagetext in 2D coordinated if the enemy's location is not known");
AUTOCVAR_SAVE(cl_damagetext_2d, bool, true, "Show damagetext in 2D coordinates if the enemy's location is not known");
AUTOCVAR_SAVE(cl_damagetext_2d_pos, vector, '0.47 0.53 0', "2D damage text initial position (X and Y between 0 and 1)");
AUTOCVAR_SAVE(cl_damagetext_2d_alpha_start, float, 1, "2D damage text initial alpha");
AUTOCVAR_SAVE(cl_damagetext_2d_alpha_lifetime, float, 1.3, "2D damage text lifetime (alpha fading) in seconds");
......@@ -36,7 +38,7 @@ AUTOCVAR_SAVE(cl_damagetext_2d_size_lifetime, float, 3, "2D dama
AUTOCVAR_SAVE(cl_damagetext_2d_velocity, vector, '-25 0 0', "2D damage text move direction (screen coordinates)");
AUTOCVAR_SAVE(cl_damagetext_2d_overlap_offset, vector, '0 -15 0', "Offset 2D damage text by this much to prevent overlapping (screen coordinates)");
AUTOCVAR_SAVE(cl_damagetext_2d_close_range, float, 125, "Always use 2D damagetext for hits closer that this");
AUTOCVAR_SAVE(cl_damagetext_2d_out_of_view, bool, true, "Always use 2D damagetext for hits that occured off-screen");
AUTOCVAR_SAVE(cl_damagetext_2d_out_of_view, bool, true, "Always use 2D damagetext for hits that occurred off-screen");
CLASS(DamageText, Object)
ATTRIB(DamageText, m_color, vector, autocvar_cl_damagetext_color);
......@@ -60,18 +62,25 @@ CLASS(DamageText, Object)
void DamageText_draw2d(DamageText this) {
float since_hit = time - this.hit_time;
// can't use `dt = hit_time - prev_update_time` because shrinking wouldn't be linear
float size = this.m_size - since_hit * this.m_shrink_rate * this.m_size;
float alpha_ = this.alpha - since_hit * this.fade_rate;
if (alpha_ <= 0 || size <= 0) {
delete(this);
return;
}
vector screen_pos;
if (this.m_screen_coords) {
screen_pos = this.origin + since_hit * autocvar_cl_damagetext_2d_velocity;
} else {
screen_pos = project_3d_to_2d(this.origin + since_hit * autocvar_cl_damagetext_velocity) + autocvar_cl_damagetext_offset;
makevectors(view_angles);
vector world_offset = since_hit * autocvar_cl_damagetext_velocity_world + autocvar_cl_damagetext_offset_world;
vector world_pos = this.origin + world_offset.x * v_forward + world_offset.y * v_right + world_offset.z * v_up;
screen_pos = project_3d_to_2d(world_pos) + since_hit * autocvar_cl_damagetext_velocity_screen + autocvar_cl_damagetext_offset_screen;
}
screen_pos.y += size / 2;
if (screen_pos.z >= 0) {
screen_pos.z = 0;
vector rgb;
......@@ -87,6 +96,7 @@ CLASS(DamageText, Object)
vector drawfontscale_save = drawfontscale;
drawfontscale = (size / autocvar_cl_damagetext_size_max) * '1 1 0';
screen_pos.y -= drawfontscale.x * size / 2;
drawcolorcodedstring2_builtin(screen_pos, this.text, autocvar_cl_damagetext_size_max * '1 1 0', rgb, alpha_, DRAWFLAG_NORMAL);
drawfontscale = drawfontscale_save;
}
......@@ -188,6 +198,11 @@ CLASS(DamageText, Object)
}
ENDCLASS(DamageText)
float current_alpha(entity damage_text) {
// alpha doesn't change - actual alpha is always calculated from the initial value
return damage_text.alpha - (time - damage_text.hit_time) * damage_text.fade_rate;
}
NET_HANDLE(damagetext, bool isNew)
{
int server_entity_index = ReadByte();
......@@ -206,8 +221,11 @@ NET_HANDLE(damagetext, bool isNew)
else potential_damage = ReadShort();
return = true;
if (!autocvar_cl_damagetext) return;
if (friendlyfire && !autocvar_cl_damagetext_friendlyfire) return;
if (autocvar_cl_damagetext == 0) return;
if (friendlyfire) {
if (autocvar_cl_damagetext_friendlyfire == 0) return;
if (autocvar_cl_damagetext_friendlyfire == 1 && health == 0 && armor == 0) return;
}
int client_entity_index = server_entity_index - 1;
entity entcs = entcs_receiver(client_entity_index);
......@@ -219,15 +237,14 @@ NET_HANDLE(damagetext, bool isNew)
if (can_use_3d && !prefer_2d) {
// world coords
if (autocvar_cl_damagetext_accumulate_range) {
for (entity e = findradius(entcs.origin, autocvar_cl_damagetext_accumulate_range); e; e = e.chain) {
if (e.instanceOfDamageText
&& !e.m_screen_coords // we're using origin for both world coords and screen coords so avoid mismatches
&& e.m_group == server_entity_index
&& e.alpha > autocvar_cl_damagetext_accumulate_alpha_rel * autocvar_cl_damagetext_alpha_start) {
DamageText_update(e, entcs.origin, e.m_healthdamage + health, e.m_armordamage + armor, e.m_potential_damage + potential_damage, deathtype);
return;
}
// using 1 as minimum because of shotgun (same as menu)
for (entity e = findradius(entcs.origin, max(autocvar_cl_damagetext_accumulate_range, 1)); e; e = e.chain) {
if (e.instanceOfDamageText
&& !e.m_screen_coords // we're using origin for both world coords and screen coords so avoid mismatches
&& e.m_group == server_entity_index
&& current_alpha(e) > autocvar_cl_damagetext_accumulate_alpha_rel * autocvar_cl_damagetext_alpha_start) {
DamageText_update(e, entcs.origin, e.m_healthdamage + health, e.m_armordamage + armor, e.m_potential_damage + potential_damage, deathtype);
return;
}
}
make_impure(NEW(DamageText, server_entity_index, entcs.origin, false, health, armor, potential_damage, deathtype, friendlyfire));
......
......@@ -29,7 +29,10 @@ CLASS(XonoticDamageTextSettings, XonoticTab)
this.TR(this);
this.TD(this, 1, 1, e = makeXonoticTextLabel(0, _("Accumulate range:")));
setDependent(e, "cl_damagetext", 1, 1);
this.TD(this, 1, 2, e = makeXonoticSlider(0, 500, 1, "cl_damagetext_accumulate_range"));
// 1 as min because shotgun sends damagetext per pellet (see https://gitlab.com/xonotic/xonotic-data.pk3dir/issues/1994).
// It's capped to 1 in code too but let's not lie to users by allowing the slider to go to 0, it would still accumulate
// when hitting a stationary player using a weapon with no push.
this.TD(this, 1, 2, e = makeXonoticSlider(1, 500, 1, "cl_damagetext_accumulate_range"));
setDependent(e, "cl_damagetext", 1, 1);
this.TR(this);
this.TD(this, 1, 1, e = makeXonoticTextLabel(0, _("Lifetime:")));
......
......@@ -571,6 +571,12 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, OnEntityPreSpawn)
return true;
}
MUTATOR_HOOKFUNCTION(mutator_instagib, FixClientCvars)
{
entity player = M_ARGV(0, entity);
stuffcmd(player, "cl_cmd settemp _scoreboard_instagib 1\n");
}
MUTATOR_HOOKFUNCTION(mutator_instagib, BuildMutatorsString)
{
M_ARGV(0, string) = strcat(M_ARGV(0, string), ":instagib");
......
......@@ -31,6 +31,8 @@ REGISTER_SP(SCORE);
REGISTER_SP(DMG);
REGISTER_SP(DMGTAKEN);
REGISTER_SP(COUNTRY);
REGISTER_SP(KILLS);
REGISTER_SP(DEATHS);
REGISTER_SP(SUICIDES);
......@@ -84,6 +86,9 @@ REGISTER_SP(NEXBALL_FAULTS);
REGISTER_SP(ONS_TAKES);
REGISTER_SP(ONS_CAPS);
// accuracy
REGISTER_SP(ACC_VAPORIZER);
#endif
......
......@@ -36,3 +36,4 @@
#include <server/mutators/_mod.inc>
#include <server/pathlib/_mod.inc>
#include <server/weapons/_mod.inc>
#include <server/ip2country.qc>
......@@ -41,6 +41,8 @@
#include <lib/warpzone/common.qh>
#include <lib/warpzone/util_server.qh>
STATIC_INIT(bot) { bot_calculate_stepheightvec(); }
// TODO: remove this function! its only purpose is to update these fields since bot_setnameandstuff is called before ClientState
void bot_setclientfields(entity this)
{
......
......@@ -15,6 +15,7 @@
#include "g_damage.qh"
#include "handicap.qh"
#include "g_hook.qh"
#include "ip2country.qh"
#include "command/common.qh"
#include "command/vote.qh"
#include "clientkill.qh"
......@@ -801,6 +802,7 @@ void PutClientInServer(entity this)
} else if (IS_PLAYER(this)) {
PutPlayerInServer(this);
}
SetCountryToScoreboard(this);
}
// TODO do we need all these fields, or should we stop autodetecting runtime
......@@ -981,6 +983,7 @@ void ClientPreConnect(entity this)
((IS_REAL_CLIENT(this)) ? this.netaddress : "bot")
));
}
IP2Country_SetPlayer(self);
}
#endif
......@@ -1126,6 +1129,10 @@ void ClientConnect(entity this)
CS(this).jointime = time;
#ifndef DP_EXT_PRECONNECT
IP2Country_SetPlayer(self);
#endif
if (IS_REAL_CLIENT(this))
{
if (g_weaponarena_weapons == WEPSET(TUBA))
......
......@@ -1648,6 +1648,27 @@ void GameCommand_warp(int request, int argc)
}
}
void GameCommand_ip2country(float request, float argc)
{
switch(request)
{
case CMD_REQUEST_COMMAND:
IP2Country_command(argc);
return;
case CMD_REQUEST_USAGE:
default:
LOG_INFO("\nUsage:^3 sv_cmd ip2country command [arguments]\n");
LOG_INFO("Available commands:\n");
LOG_INFO(" ^2status^7: list requests in process\n");
LOG_INFO(" ^2reset^7: timeout all current requests\n");
LOG_INFO(" ^2lookup ip^7: query country for ip\n");
LOG_INFO(" ^2flushcache^7: remove all ips from cache\n");
LOG_INFO(" ^2players^7: list players with ip and country code\n");
LOG_INFO(" ^2change ID CN^7: change country code by player id\n");
return;
}
}
/* use this when creating a new command, making sure to place it in alphabetical order... also,
** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
void GameCommand_(int request)
......@@ -1707,6 +1728,7 @@ SERVER_COMMAND(stuffto, "Send a command to be executed on a client") { GameComma
SERVER_COMMAND(trace, "Various debugging tools with tracing") { GameCommand_trace(request, arguments); }
SERVER_COMMAND(unlockteams, "Enable the ability for players to switch or enter teams") { GameCommand_unlockteams(request); }
SERVER_COMMAND(warp, "Choose different level in campaign") { GameCommand_warp(request, arguments); }
SERVER_COMMAND(ip2country, "Get country code by ip") { GameCommand_ip2country(request, arguments); }
void GameCommand_macro_help()
{
......
......@@ -18,6 +18,7 @@
#include "scores.qh"
#include "scores_rules.qh"
#include "teamplay.qh"
#include "ip2country.qh"
#include "weapons/weaponstats.qh"
#include "../common/constants.qh"
#include <common/net_linked.qh>
......@@ -882,6 +883,8 @@ spawnfunc(worldspawn)
WeaponStats_Init();
IP2Country_Init();
Nagger_Init();
next_pingtime = time + 5;
......@@ -2211,6 +2214,7 @@ void Shutdown()
db_save(TemporaryDB, "server-temp.db");
}
CheatShutdown(); // must be after cheatcount check
IP2Country_Cleanup();
db_close(ServerProgsDB);
db_close(TemporaryDB);
LOG_TRACE("Saving persistent data... done!");
......
#include "ip2country.qh"
float IP2Country_cachedb;
.entity ip2country_cbent;
.string netaddress;
.ip2country_callback callbackfunction;
.float started_at;
void IP2Country_Init() {
registercvar("sv_ip2country", "1");
registercvar("sv_ip2country_localcn", "XX");
registercvar("sv_ip2country_timeout", "15");
registercvar("sv_ip2country_server", "http://api.vinnica.in/country?type=xonotic&ip=");
LOG_INFO("Loading cached ips\n");
IP2Country_cachedb = db_load(IP2COUNTRY_DBNAME);
}
void IP2Country_Cleanup() {
entity e;
for(e = world; (e = find(e, classname, "ip2country_cbprovider"));)
IP2Country_FreeCallbackEnt(e);
LOG_INFO("Saving cached ips\n");
db_save(IP2Country_cachedb, IP2COUNTRY_DBNAME);
db_close(IP2Country_cachedb);
}
void IP2Country_CacheIP(string ip, string cn) {
LOG_DEBUGF("Cached ip %s %s\n", ip, cn);
db_put(IP2Country_cachedb, ip, cn);
}
string IP2Country_Cache_Get(string ip) {
return db_get(IP2Country_cachedb, ip);
}
void IP2Country_AddCallback(string ip, ip2country_callback callback, float timeout, entity callbackent) {
if(timeout <= 0)
timeout = cvar("sv_ip2country_timeout");
entity cb = spawn();
cb.classname = "ip2country_cbprovider";
cb.netaddress = strzone(ip);
cb.ip2country_cbent = callbackent;
cb.callbackfunction = callback;
cb.started_at = time;
setthink(cb, IP2Country_RequestTimeout);
cb.nextthink = time + timeout;
}
void IP2Country_ExecCallback(string ip, string cn) {
entity e;
for(e = world; (e = find(e, classname, "ip2country_cbprovider")); ) {
if(e.netaddress == ip) {
e.callbackfunction(ip, cn, e.ip2country_cbent);
IP2Country_FreeCallbackEnt(e);
}
}
}
void IP2Country_FreeCallbackEnt(entity e) {
if(e.netaddress)
strunzone(e.netaddress);
remove(e);
}
void IP2Country_RequestTimeout(entity e) {
LOG_INFOF("\nIP to country request for ip : %s timed out after %f", e.netaddress, time - e.started_at);
e.callbackfunction(e.netaddress, "", e.ip2country_cbent);
IP2Country_FreeCallbackEnt(e);
}
int is_local(string IP) {
string ipl;
ipl = strtolower(IP);
if(strncmp(ipl, "local", 5) == 0)
return 1;
else if(strncmp(ipl, "127.", 4) == 0)
return 1;
else if(strncmp(ipl, "10.", 3) == 0)
return 1;
else if(strncmp(ipl, "192.168.", 8) == 0)
return 1;
else if(strncmp(ipl, "169.254.", 8) == 0)
return 1;
else if(strncmp(ipl, "172.16.", 7) == 0)
return 1;
else
return 0;
}
void IP2Country_lookup(string ip, ip2country_callback callback, float timeout, entity cbent) {
string res;
if(!cvar("sv_ip2country"))
return;
if(is_local(ip) == 1) {
callback(ip, cvar_string("sv_ip2country_localcn"), cbent);
return;
}
res = IP2Country_Cache_Get(ip);
if(res != "") {
callback(ip, res, cbent);
return;
}
IP2Country_AddCallback(ip, callback, timeout, cbent);
if (cvar_string("sv_ip2country_server") == "") {
LOG_INFOF("\nCan't lookup ip - %s, because sv_ip2country_server is not set", ip);
return;
}
uri_get(strcat(cvar_string("sv_ip2country_server"), ip), URI_GET_IP2C);
}
void IP2Country_Get_Callback(float id, float status, string data) {
string cn, ip;
if (status) {
LOG_INFOF("\nIP lookup failed, status: %f", status);
return;
}
tokenizebyseparator(data, " ");
cn = substring(argv(0), 0, 2);
ip = argv(1);
if (cn == "--") {
LOG_INFOF("\nServer can't determine country for ip -- %s", ip);
IP2Country_ExecCallback(ip, "");
return;
}
IP2Country_CacheIP(ip, cn);
IP2Country_ExecCallback(ip, cn);
}
void IP2Country_DumpToConsole(string ip, string cn, entity e) {
LOG_INFOF("Country for IP %s: %s\n", ip, (cn == "") ? "Unknown" : cn);
}
void IP2Country_check(string ip) {
IP2Country_lookup(ip, IP2Country_DumpToConsole, 0, world);
}
void IP2Country_status() {
entity e;
LOG_INFO("^3Requests in process:\n");
for(e = world; (e = find(e, classname, "ip2country_cbprovider"));)
LOG_INFOF("%-30s: ^5%4.2f\n", e.netaddress, time - e.started_at);
}
void IP2Country_reset() {
entity e;
for(e = world; (e = find(e, classname, "ip2country_cbprovider"));) {
e.think();
}
}
void IP2Country_flushcache() {
db_close(IP2Country_cachedb);
IP2Country_cachedb = db_create();
db_save(IP2Country_cachedb, IP2COUNTRY_DBNAME);
LOG_INFO("ip2country cache flushed\n");
}
void SetPlayerCountry(entity player, string cn) {
if(player.ip2country_code)
strunzone(player.ip2country_code);
player.ip2country_code = strzone(strtoupper(cn));
}
void SetCountryToScoreboard(entity player) {
string cn;
if(CS(player).scorekeeper && player.ip2country_code) {
cn = player.ip2country_code;
PlayerScore_Set(player, SP_COUNTRY, (str2chr(cn, 0) << 8) + str2chr(cn, 1));
} else {
LOG_INFOF("Player %s has no scorekeeper\n", player.netname);
}
}
void IP2Country_PlayerCallback(string ip, string cn, entity player) {
SetPlayerCountry(player, cn);
}
void IP2Country_SetPlayer(entity player) {
if(IS_REAL_CLIENT(player))
IP2Country_lookup(player.netaddress, IP2Country_PlayerCallback, 0, player);
else
SetPlayerCountry(player, "");
}
void IP2Country_players() {
FOREACH_CLIENT(IS_REAL_CLIENT(it), {
LOG_INFOF("#%-2d %12s^7 %18s [%2s]\n", num_for_edict(it), it.netname, it.netaddress, it.ip2country_code);
});
}
void IP2Country_redraw() {
FOREACH_CLIENT(IS_REAL_CLIENT(it), {
SetCountryToScoreboard(it);
});
}
void IP2Country_ChangeCountry(entity player, string country) {
if(strlen(country) == 2 && IS_REAL_CLIENT(player)) {
SetPlayerCountry(player, country);
SetCountryToScoreboard(player);
}
}
void IP2Country_command(float argc) {
if(argc < 2)
goto wrong_argument;
switch(argv(1)) {
case "status":
IP2Country_status();
if (argc != 2)
goto wrong_argument;
return;
case "reset":
if (argc != 2)
goto wrong_argument;
IP2Country_reset();
return;
case "flushcache":
if (argc != 2)
goto wrong_argument;
IP2Country_flushcache();
return;
case "players":
if (argc != 2)
goto wrong_argument;
IP2Country_players();
return;
case "redraw":
if (argc != 2)
goto wrong_argument;
IP2Country_redraw();
return;
case "lookup":
if (argc != 3)
goto wrong_argument;
IP2Country_check(argv(2));
return;
case "change":
if (argc != 4)
goto wrong_argument;
entity client = GetFilteredEntity(argv(2));
IP2Country_ChangeCountry(client, argv(3));
return;
default:
LOG_INFO("Wrong command\n"
"Available commands: status,reset,flushcache,players,lookup,change\n");
return;
}
:wrong_argument
LOG_INFO("Wrong argument count\n");
}
#ifndef IP2COUNTRY_H
#define IP2COUNTRY_H
#define IP2COUNTRY_DBNAME "ip2country.db"
typedef void(string, string, entity) ip2country_callback;
const int URI_GET_IP2C = 33;
.string ip2country_code;
void IP2Country_Init();
void IP2Country_Cleanup();
void IP2Country_lookup(string, ip2country_callback, float, entity);
void IP2Country_Get_Callback(float, float, string);
void IP2Country_FreeCallbackEnt(entity);
void IP2Country_RequestTimeout(entity);
void IP2Country_check(string);
void IP2Country_command(float);
void IP2Country_SetPlayer(entity);
void IP2Country_redraw();
void SetCountryToScoreboard(entity);
#endif
......@@ -35,6 +35,7 @@
#include "../lib/csqcmodel/sv_model.qh"
#include "../lib/warpzone/anglestransform.qh"
#include "../lib/warpzone/server.qh"
#include "ip2country.qh"
void crosshair_trace(entity pl)
{
......@@ -1138,6 +1139,10 @@ void URI_Get_Callback(float id, float status, string data)
{
// handled
}
else if (id == URI_GET_IP2C)
{
IP2Country_Get_Callback(id, status, data);
}
else if (id == URI_GET_DISCARD)
{
// discard
......
......@@ -271,11 +271,13 @@ float PlayerScore_Clear(entity player)
sk = CS(player).scorekeeper;
FOREACH(Scores, true, {
if(it != SP_COUNTRY) {
if(sk.(scores(it)) != 0)
if(scores_label(it) != "")
sk.SendFlags |= (2 ** (i % 16));
if(i != SP_ELO.m_id)
sk.(scores(it)) = 0;
}
});
return 1;
......@@ -286,6 +288,7 @@ void Score_ClearAll()
entity sk;
float t;
FOREACH_CLIENTSLOT(true, {
if(it != SP_COUNTRY) {
sk = CS(it).scorekeeper;
if (!sk) continue;
FOREACH(Scores, true, {
......@@ -294,6 +297,7 @@ void Score_ClearAll()
sk.SendFlags |= (2 ** (i % 16));
if(i != SP_ELO.m_id)
sk.(scores(it)) = 0;
}
});
});
for(t = 0; t < 16; ++t)
......@@ -309,6 +313,7 @@ void Score_ClearAll()
sk.(teamscores(j)) = 0;
}
}
IP2Country_redraw();
}
void PlayerScore_Attach(entity player)
......
......@@ -51,6 +51,8 @@ void ScoreRules_basics(int teams, float sprio, float stprio, float score_enabled
ScoreInfo_SetLabel_PlayerScore(SP_DMG, "dmg", 0);
ScoreInfo_SetLabel_PlayerScore(SP_DMGTAKEN, "dmgtaken", SFL_LOWER_IS_BETTER);
ScoreInfo_SetLabel_PlayerScore(SP_ELO, "elo", 0);
ScoreInfo_SetLabel_PlayerScore(SP_COUNTRY, "country", 0);
ScoreInfo_SetLabel_PlayerScore(SP_ACC_VAPORIZER, "accuracy", 0);
if(STAT(SHOWFPS))
ScoreInfo_SetLabel_PlayerScore(SP_FPS, "fps", 0);
......
......@@ -63,6 +63,7 @@ void accuracy_resend(entity e)
void accuracy_add(entity this, Weapon w, int fired, int hit)
{
int orig_wep = w.m_id;
if (IS_INDEPENDENT_PLAYER(this)) return;
entity a = CS(this).accuracy;
if (!a) return;
......@@ -84,7 +85,22 @@ void accuracy_add(entity this, Weapon w, int fired, int hit)
a.fired_time = time;
}
if (b == accuracy_byte(a.accuracy_hit[wepid], a.accuracy_fired[wepid])) return; // no change
int new_b = accuracy_byte(a.accuracy_hit[wepid], a.accuracy_fired[wepid]);
if (b == new_b) return; // no change
if (orig_wep == WEP_VAPORIZER.m_id) {
int acc;
if (new_b == 0) {
acc = 0;
} else if (new_b == 255) {
acc = 1.0;
} else {
acc = (new_b - 1) / 100;
}
// hack to make acc look same
PlayerScore_Set(this, SP_ACC_VAPORIZER, acc * 100);
}
int sf = 1 << (wepid % 24);
a.SendFlags |= sf;
FOREACH_CLIENT(IS_SPEC(it) && it.enemy == this, { CS(it).accuracy.SendFlags |= sf; });
......
......@@ -429,13 +429,16 @@ seta cl_damagetext_size_max 16 "Damage text font size for large damage"
seta cl_damagetext_size_max_damage 140 "How much damage is considered large"
seta cl_damagetext_alpha_start "1" "Damage text initial alpha"
seta cl_damagetext_alpha_lifetime "3" "Damage text lifetime in seconds"
seta cl_damagetext_velocity "0 0 20" "Damage text move direction"
seta cl_damagetext_offset "0 -40 0" "Damage text offset"
seta cl_damagetext_velocity_screen "0 0 0" "Damage text move direction (screen coordinates)"
seta cl_damagetext_velocity_world "0 0 20" "Damage text move direction (world coordinates relative to player's view)"
seta cl_damagetext_offset_screen "0 -45 0" "Damage text offset (screen coordinates)"
seta cl_damagetext_offset_world "0 0 0" "Damage text offset (world coordinates relative to player's view)"
seta cl_damagetext_accumulate_range "30" "Damage text spawned within this range is accumulated"
seta cl_damagetext_accumulate_alpha_rel "0.65" "Only update existing damage text when it's above this much percentage (0 to 1) of the starting alpha"
seta cl_damagetext_friendlyfire "1" "Show damage text for friendlyfire too"
seta cl_damagetext_friendlyfire_color "1 0 0" "Damage text color for friendlyfire"
seta cl_damagetext_2d 1 "Show damagetext in 2D coordinates if the enemy's location is not known"
seta cl_damagetext_2d_pos "0.47 0.53 0" "2D damage text initial position (X and Y between 0 and 1)"
seta cl_damagetext_2d_alpha_start 1 "2D damage text initial alpha"
seta cl_damagetext_2d_alpha_lifetime 1.3 "2D damage text lifetime (alpha fading) in seconds"
......@@ -443,7 +446,7 @@ seta cl_damagetext_2d_size_lifetime 3 "2D damage text lifetime (size shrinking)
seta cl_damagetext_2d_velocity "-25 0 0" "2D damage text move direction (screen coordinates)"
seta cl_damagetext_2d_overlap_offset "0 -15 0" "Offset 2D damage text by this much to prevent overlapping (screen coordinates)"
seta cl_damagetext_2d_close_range 125 "Always use 2D damagetext for hits closer that this"
seta cl_damagetext_2d_out_of_view 1 "Always use 2D damagetext for hits that occured off-screen"
seta cl_damagetext_2d_out_of_view 1 "Always use 2D damagetext for hits that occurred off-screen"
seta cl_vehicles_alarm 1 "Play an alarm sound when the vehicle you are driving is heavily damaged"
seta cl_vehicles_hud_tactical 1
......
This diff is collapsed.