...
 
Commits (39)
......@@ -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=fed9f3b94a544058593aaa8addaa9402
- EXPECT=1c17d1c4e3c98de30d9bec68a635f600
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
......
Thu Nov 15 07:24:15 CET 2018
Tue Nov 27 07:24:07 CET 2018
......@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: Xonotic\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-09 00:35+0200\n"
"PO-Revision-Date: 2018-11-08 17:13+0000\n"
"PO-Revision-Date: 2018-11-26 14:15+0000\n"
"Last-Translator: Demiray Muhterem <mdemiray@msn.com>\n"
"Language-Team: Turkish (http://www.transifex.com/team-xonotic/xonotic/"
"language/tr/)\n"
......@@ -881,7 +881,7 @@ msgstr ""
#: qcsrc/menu/xonotic/dialog_multiplayer_join_serverinfo.qc:203
#: qcsrc/menu/xonotic/dialog_multiplayer_join_serverinfo.qc:208
msgid "N/A"
msgstr ""
msgstr "N/A"
#: qcsrc/client/hud/panel/scoreboard.qc:1156
#, c-format
......@@ -3307,31 +3307,31 @@ msgstr ""
#: qcsrc/common/notifications/all.inc:590
#, c-format
msgid "^K3%sYou fragged ^BG%s"
msgstr ""
msgstr "^K3%sParçalandın ^BG%s"
#: qcsrc/common/notifications/all.inc:591
#: qcsrc/common/notifications/all.inc:600
#: qcsrc/common/notifications/all.inc:609
#, c-format
msgid "^K3%sYou scored against ^BG%s"
msgstr ""
msgstr "^K3%sPuan yaptın ^BG%s"
#: qcsrc/common/notifications/all.inc:592
#, c-format
msgid "^K1%sYou were fragged by ^BG%s"
msgstr ""
msgstr "^K1%sŞunun tarafından parçalandın ^BG%s"
#: qcsrc/common/notifications/all.inc:593
#: qcsrc/common/notifications/all.inc:602
#: qcsrc/common/notifications/all.inc:611
#, c-format
msgid "^K1%sYou were scored against by ^BG%s"
msgstr ""
msgstr "^K1%sŞundan puan kaybettin ^BG%s"
#: qcsrc/common/notifications/all.inc:599
#, c-format
msgid "^K3%sYou burned ^BG%s"
msgstr ""
msgstr "^K3%sYandın ^BG%s"
#: qcsrc/common/notifications/all.inc:601
#, c-format
......
......@@ -10,6 +10,7 @@
# Losier Blackheath <losier.cc@gmail.com>, 2018
# sapphireliu <balancedliu@gmail.com>, 2014
# 杜茂森 <dumaosen_main01@outlook.com>, 2018
# 杜茂森 <dumaosen_main01@outlook.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Xonotic\n"
......
......@@ -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;
}
......
......@@ -1357,20 +1357,21 @@ void HUD_Crosshair(entity this)
#define CROSSHAIR_DO_BLUR(M,sz,wcross_name,wcross_alpha) \
MACRO_BEGIN { \
vector scaled_sz = sz * wcross_size; \
if(wcross_blur > 0) \
{ \
for(i = -2; i <= 2; ++i) \
for(j = -2; j <= 2; ++j) \
M(i,j,sz,wcross_name,wcross_alpha*0.04); \
M(i,j,sz,scaled_sz,wcross_name,wcross_alpha*0.04); \
} \
else \
{ \
M(0,0,sz,wcross_name,wcross_alpha); \
M(0,0,sz,scaled_sz,wcross_name,wcross_alpha); \
} \
} MACRO_END
#define CROSSHAIR_DRAW_SINGLE(i,j,sz,wcross_name,wcross_alpha) \
drawpic(wcross_origin - ('0.5 0 0' * (sz * wcross_size.x + i * wcross_blur) + '0 0.5 0' * (sz * wcross_size.y + j * wcross_blur)), wcross_name, sz * wcross_size, wcross_color, wcross_alpha, DRAWFLAG_NORMAL)
#define CROSSHAIR_DRAW_SINGLE(i,j,sz,scaled_sz,wcross_name,wcross_alpha) \
drawpic(wcross_origin - ('0.5 0 0' * (scaled_sz.x + i * wcross_blur) + '0 0.5 0' * (scaled_sz.y + j * wcross_blur)), wcross_name, scaled_sz, wcross_color, wcross_alpha, DRAWFLAG_NORMAL)
#define CROSSHAIR_DRAW(sz,wcross_name,wcross_alpha) \
CROSSHAIR_DO_BLUR(CROSSHAIR_DRAW_SINGLE,sz,wcross_name,wcross_alpha)
......
......@@ -129,6 +129,12 @@ MUTATOR_HOOKFUNCTION(lms, reset_map_players)
});
}
MUTATOR_HOOKFUNCTION(lms, ReadLevelCvars)
{
// incompatible
sv_ready_restart_after_countdown = 0;
}
MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
{
entity player = M_ARGV(0, entity);
......
......@@ -1625,6 +1625,11 @@ bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effe
player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
makevectors(player.angles);
player.fixangle = true;
if (IS_BOT_CLIENT(player))
{
player.v_angle = player.angles;
bot_aim_reset(player);
}
player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
if ( tele_effects )
......
......@@ -113,6 +113,11 @@ void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angle
setorigin(player, to);
player.oldorigin = to; // don't undo the teleport by unsticking
player.angles = to_angles;
if (IS_BOT_CLIENT(player))
{
player.v_angle = player.angles;
bot_aim_reset(player);
}
player.fixangle = true;
player.velocity = to_velocity;
BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
......
......@@ -34,12 +34,8 @@ void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angle
void trigger_teleport_use(entity this, entity actor, entity trigger);
#define TDEATHLOOP(o) \
entity head; \
vector deathmin; \
vector deathmax; \
float deathradius; \
deathmin = (o) + player.mins; \
deathmax = (o) + player.maxs; \
vector deathmin = (o) + player.mins; \
vector deathmax = (o) + player.maxs; \
if(telefragmin != telefragmax) \
{ \
if(deathmin.x > telefragmin.x) deathmin.x = telefragmin.x; \
......@@ -49,11 +45,9 @@ void trigger_teleport_use(entity this, entity actor, entity trigger);
if(deathmax.y < telefragmax.y) deathmax.y = telefragmax.y; \
if(deathmax.z < telefragmax.z) deathmax.z = telefragmax.z; \
} \
deathradius = max(vlen(deathmin), vlen(deathmax)); \
for(head = findradius(o, deathradius); head; head = head.chain) \
if(head != player) \
if(head.takedamage) \
if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax))
float deathradius = max(vlen(deathmin), vlen(deathmax)); \
for (entity head = findradius(o, deathradius); head; head = head.chain) \
if (head != player && head.takedamage && boxesoverlap(deathmin, deathmax, head.absmin, head.absmax))
float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax);
float tdeath_hit;
......
......@@ -680,11 +680,21 @@ MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon)
closest.velocity = my_vel;
closest.angles = my_ang;
if (IS_BOT_CLIENT(closest))
{
closest.v_angle = closest.angles;
bot_aim_reset(closest);
}
closest.fixangle = true;
closest.oldorigin = my_org;
closest.oldvelocity = my_vel;
player.velocity = their_vel;
player.angles = their_ang;
if (IS_BOT_CLIENT(player))
{
player.v_angle = player.angles;
bot_aim_reset(player);
}
player.fixangle = true;
player.oldorigin = their_org;
player.oldvelocity = their_vel;
......
......@@ -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
......
......@@ -10,6 +10,7 @@
#include <common/util.qh>
#include <server/constants.qh>
#include <server/defs.qh>
#include <server/utils.qh>
#endif
#ifdef WARPZONELIB_KEEPDEBUG
......@@ -42,11 +43,17 @@ void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector
player.lastteleporttime = time;
#endif
setorigin(player, to); // NOTE: this also aborts the move, when this is called by touch
player.angles = to_angles;
#ifdef SVQC
player.oldorigin = to; // for DP's unsticking
player.fixangle = true;
if (IS_BOT_CLIENT(player))
{
// FIXME find a way to smooth view's angles change for bots too
player.v_angle = player.angles;
bot_aim_reset(player);
}
#endif
player.angles = to_angles;
player.velocity = to_velocity;
BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
......
......@@ -34,9 +34,7 @@
if (conv <= 0) { \
ret = -1; \
} else { \
bool esc = false; \
if (conv == '=') { \
esc = true; \
conv = STRING_ITERATOR_GET(stringiter); \
conv -= 64; \
} \
......
......@@ -27,14 +27,17 @@ void RegisterSLCategories()
for(i = 0; i < category_ent_count; ++i) \
{ \
s = categories[i].override_string; \
if((s != "") && (s != categories[i].cat_name)) \
if(s != "" && s != categories[i].cat_name) \
{ \
catnum = 0; \
for(x = 0; x < category_ent_count; ++x) \
{ if(categories[x].cat_name == s) { \
catnum = (x+1); \
break; \
} } \
{ \
if(categories[x].cat_name == s) \
{ \
catnum = x + 1; \
break; \
} \
} \
if(catnum) \
{ \
strfree(categories[i].override_string); \
......
......@@ -36,3 +36,4 @@
#include <server/mutators/_mod.inc>
#include <server/pathlib/_mod.inc>
#include <server/weapons/_mod.inc>
#include <server/ip2country.qc>
......@@ -55,6 +55,7 @@ float skill;
.int wpflags;
bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, float applygravity);
void bot_aim_reset(entity this);
void bot_clientconnect(entity this);
void bot_clientdisconnect(entity this);
void bot_cmdhelp(string scmd);
......@@ -123,3 +124,6 @@ void waypoint_spawnforteleporter_wz(entity e, entity tracetest_ent);
void waypoint_spawn_fromeditor(entity pl);
entity waypoint_spawn(vector m1, vector m2, float f);
void waypoint_unreachable(entity pl);
void waypoint_getSymmetricalOrigin_cmd(entity caller, bool save, int arg_idx);
void waypoint_getSymmetricalAxis_cmd(entity caller, bool save, int arg_idx);
......@@ -170,6 +170,22 @@ void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1,
this.bot_canfire = 1;
}
void bot_aim_reset(entity this)
{
this.bot_aimdir_executed = true;
this.bot_badaimtime = 0;
this.bot_aimthinktime = time;
this.bot_prevaimtime = time;
this.bot_mouseaim = this.v_angle;
this.bot_olddesiredang = this.v_angle;
this.bot_1st_order_aimfilter = '0 0 0';
this.bot_2nd_order_aimfilter = '0 0 0';
this.bot_3th_order_aimfilter = '0 0 0';
this.bot_4th_order_aimfilter = '0 0 0';
this.bot_5th_order_aimfilter = '0 0 0';
this.bot_firetimer = 0;
}
void bot_aimdir(entity this, vector v, float maxfiredeviation)
{
float dist, delta_t, blend;
......@@ -309,10 +325,15 @@ void bot_aimdir(entity this, vector v, float maxfiredeviation)
//dprint("e ", vtos(diffang), " < ", ftos(maxfiredeviation), "\n");
// decide whether to fire this time
if (v * shotdir >= cos(maxfiredeviation * DEG2RAD))
if(vdist(trace_endpos-shotorg, <, 500 + 500 * bound(0, skill + this.bot_aggresskill, 10)) || random()*random()>bound(0,(skill+this.bot_aggresskill)*0.05,1))
this.bot_firetimer = time + bound(0.1, 0.5-(skill+this.bot_aggresskill)*0.05, 0.5);
//traceline(shotorg,shotorg+shotdir*1000,false,NULL);
if (maxfiredeviation != 0 && v * shotdir > cos(maxfiredeviation * DEG2RAD))
{
traceline(shotorg, shotorg + shotdir * 1000, false, NULL);
if (vdist(trace_endpos - shotorg, <, 500 + 500 * bound(0, skill + this.bot_aggresskill, 10))
|| random() * random() > bound(0, (skill + this.bot_aggresskill) * 0.05, 1))
{
this.bot_firetimer = time + bound(0.1, 0.5 - (skill + this.bot_aggresskill) * 0.05, 0.5);
}
}
//dprint(ftos(maxfiredeviation),"\n");
//dprint(" diff:", vtos(diffang), "\n");
......@@ -329,20 +350,12 @@ bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeed
{
float r, hf, distanceratio;
vector v;
/*
eprint(this);
dprint("bot_aim(", ftos(shotspeed));
dprint(", ", ftos(shotspeedupward));
dprint(", ", ftos(maxshottime));
dprint(", ", ftos(applygravity));
dprint(");\n");
*/
hf = this.dphitcontentsmask;
this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
shotspeed *= W_WeaponSpeedFactor(this);
shotspeedupward *= W_WeaponSpeedFactor(this);
float speed_factor = W_WeaponSpeedFactor(this);
shotspeed *= speed_factor;
shotspeedupward *= speed_factor;
if (!shotspeed)
{
LOG_TRACE("bot_aim: WARNING: weapon ", this.(weaponentity).m_weapon.m_name, " shotspeed is zero!");
......@@ -390,6 +403,12 @@ bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeed
}
}
if (time > this.bot_firetimer)
{
this.dphitcontentsmask = hf;
return false;
}
//if (r > maxshottime * shotspeed)
// return false;
this.dphitcontentsmask = hf;
......
......@@ -93,6 +93,7 @@ void bot_lagfunc(entity this, float t, float f1, float f2, entity e1, vector v1,
float bot_shouldattack(entity this, entity targ);
void bot_aimdir(entity this, vector v, float maxfiredeviation);
bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity);
void bot_aim_reset(entity this);
float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore);
vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay);
......
......@@ -31,6 +31,11 @@ void havocbot_ai(entity this)
if(this.draggedby)
return;
this.bot_aimdir_executed = false;
// lock aim if teleported or passing through a warpzone
if (this.lastteleporttime && !this.jumppadcount)
this.bot_aimdir_executed = true;
if(bot_execute_commands(this))
return;
......@@ -106,8 +111,6 @@ void havocbot_ai(entity this)
havocbot_aim(this);
lag_update(this);
this.bot_aimdir_executed = false;
if (this.bot_aimtarg)
{
this.aistatus |= AI_STATUS_ATTACKING;
......@@ -137,7 +140,7 @@ void havocbot_ai(entity this)
else
{
if(IS_PLAYER(this.bot_aimtarg))
bot_aimdir(this, this.bot_aimtarg.origin + this.bot_aimtarg.view_ofs - this.origin - this.view_ofs , -1);
bot_aimdir(this, this.bot_aimtarg.origin + this.bot_aimtarg.view_ofs - this.origin - this.view_ofs, 0);
}
}
else if (this.goalcurrent)
......@@ -153,7 +156,7 @@ void havocbot_ai(entity this)
vector dir = get_closer_dest(this.goalcurrent, this.origin);
dir -= this.origin + this.view_ofs;
dir.z = 0;
bot_aimdir(this, dir, -1);
bot_aimdir(this, dir, 0);
}
// if the bot is not attacking, consider reloading weapons
......@@ -577,7 +580,7 @@ void havocbot_movetogoal(entity this)
this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
}
}
else
else //if (this.goalcurrent)
{
if (this.goalcurrent.bot_pickup)
{
......@@ -601,9 +604,9 @@ void havocbot_movetogoal(entity this)
return;
}
}
else
else //if (!(this.aistatus & AI_STATUS_OUT_JUMPPAD))
{
if(time - this.lastteleporttime > 0.2 && this.velocity.z > 0)
if(this.origin.z - this.lastteleport_origin.z > (this.maxs.z - this.mins.z) * 0.5)
{
vector velxy = this.velocity; velxy_z = 0;
if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
......@@ -794,8 +797,26 @@ void havocbot_movetogoal(entity this)
// don't remove if not visible
if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
{
navigation_goalrating_timeout_force(this);
return;
if (IS_DEAD(this.goalcurrent))
{
IL_EACH(g_items, it.enemy == this.goalcurrent && Item_IsLoot(it),
{
if (vdist(it.origin - this.goalcurrent.death_origin, <, 50))
{
navigation_clearroute(this);
navigation_pushroute(this, it);
// loot can't be immediately rated since it isn't on ground yet
// it will be rated after a second when on ground, meanwhile head to it
navigation_goalrating_timeout_expire(this, 1);
return;
}
});
}
if (!Item_IsLoot(this.goalcurrent))
{
navigation_goalrating_timeout_force(this);
return;
}
}
}
else if (!(STAT(FROZEN, this.goalentity)) && this.bot_tracewalk_time < time)
......@@ -881,7 +902,8 @@ void havocbot_movetogoal(entity this)
diff = destorg - this.origin;
if (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10))
if (time < this.bot_stop_moving_timeout
|| (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10)))
{
// stop if the locked goal has been reached
destorg = this.origin;
......@@ -928,12 +950,18 @@ void havocbot_movetogoal(entity this)
}
else
{
dir = flatdir;
if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && destorg.z < this.origin.z) &&
(this.aistatus & AI_STATUS_OUT_WATER))
{
PHYS_INPUT_BUTTON_JUMP(this) = true;
dir = flatdir;
}
else
{
PHYS_INPUT_BUTTON_JUMP(this) = false;
if (destorg.z > this.origin.z)
dir = flatdir;
}
}
}
else
......@@ -1065,6 +1093,12 @@ void havocbot_movetogoal(entity this)
// Check for water/slime/lava and dangerous edges
// (only when the bot is on the ground or jumping intentionally)
if (skill + this.bot_moveskill <= 3 && time > this.bot_stop_moving_timeout
&& current_speed > maxspeed * 0.9 && fabs(deviation.y) > 70)
{
this.bot_stop_moving_timeout = time + 0.4 + random() * 0.2;
}
offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : v_forward * 32);
vector dst_ahead = this.origin + this.view_ofs + offset;
vector dst_down = dst_ahead - '0 0 3000';
......@@ -1168,13 +1202,11 @@ void havocbot_movetogoal(entity this)
}
if (ladder_zdir)
{
if (vdist(flatdir, <, 15))
dir = ladder_zdir * '0 0 1';
if (vdist(vec2(diff), <, 40))
dir.z = ladder_zdir * 4;
else
{
dir.z = ladder_zdir * 1.3;
dir = normalize(dir);
}
dir.z = ladder_zdir * 2;
dir = normalize(dir);
}
}
......@@ -1188,8 +1220,14 @@ void havocbot_movetogoal(entity this)
dir = normalize(dir);
}
// already executed when bot targets an enemy
if (!this.bot_aimdir_executed)
bot_aimdir(this, dir, -1);
{
if (time < this.bot_stop_moving_timeout)
bot_aimdir(this, normalize(this.goalcurrent.origin - this.origin), 0);
else
bot_aimdir(this, dir, 0);
}
if (!ladder_zdir)
{
......@@ -1561,14 +1599,14 @@ float havocbot_moveto(entity this, vector pos)
vector dir = get_closer_dest(this.goalcurrent, this.origin);
dir -= this.origin + this.view_ofs;
dir.z = 0;
bot_aimdir(this, dir, -1);
bot_aimdir(this, dir, 0);
}
if(this.aistatus & AI_STATUS_WAYPOINT_PERSONAL_REACHED)
{
// Step 5: Waypoint reached
LOG_TRACE(this.netname, "'s personal waypoint reached");
delete(this.havocbot_personal_waypoint);
waypoint_remove(this.havocbot_personal_waypoint);
this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_REACHED;
return CMD_STATUS_FINISHED;
}
......
......@@ -23,6 +23,7 @@
.float havocbot_stickenemy;
.float havocbot_role_timeout;
.float bot_stop_moving_timeout;
.float bot_tracewalk_time;
.entity ignoregoal;
.entity bot_lastseengoal;
......
......@@ -742,6 +742,7 @@ bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float e
// completely empty the goal stack, used when deciding where to go
void navigation_clearroute(entity this)
{
this.lastteleporttime = 0;
this.goalcurrent_prev = this.goalcurrent;
this.goalcurrent_distance_2d = FLOAT_MAX;
this.goalcurrent_distance_z = FLOAT_MAX;
......@@ -1611,11 +1612,12 @@ int navigation_poptouchedgoals(entity this)
if (!this.goalcurrent.wpisbox // warpzone
&& vlen2(this.origin - this.goalstack01.origin) < vlen2(this.origin - this.goalcurrent.origin))
{
// immediately remove origin and destination waypoints
navigation_poproute(this);
++removed_goals;
navigation_poproute(this);
++removed_goals;
return removed_goals;
this.lastteleporttime = 0;
}
// make sure jumppad is really hit, don't rely on distance based checks
......@@ -1638,12 +1640,17 @@ int navigation_poptouchedgoals(entity this)
if (time - this.lastteleporttime < random() * max_delay)
return removed_goals;
}
else if (this.goalcurrent.wpisbox) // teleport
{
// immediately remove origin and destination waypoints
navigation_poproute(this);
++removed_goals;
}
navigation_poproute(this);
this.lastteleporttime = 0;
++removed_goals;
}
else
return removed_goals;
return removed_goals;
}
else if (this.lastteleporttime > 0)
{
......@@ -1675,6 +1682,9 @@ int navigation_poptouchedgoals(entity this)
++removed_goals;
return removed_goals;
}
// reset of lastteleporttime can be overriden by a jumppad when it's set
// in more than one frame: make sure it's reset
this.lastteleporttime = 0;
}
// Loose goal touching check when running
......@@ -1796,6 +1806,7 @@ void navigation_goalrating_start(entity this)
navigation_clearroute(this);
navigation_bestgoal = NULL;
navigation_markroutes(this, wp);
this.goalstack31 = wp; // temporarly save the really close waypoint
}
// ends a goal selection session (updates goal stack to the best goal)
......@@ -1804,19 +1815,27 @@ void navigation_goalrating_end(entity this)
if(this.aistatus & AI_STATUS_STUCK)
return;
entity wp = this.goalstack31; // save to wp as this.goalstack31 is set by navigation_routetogoal
this.goalstack31 = NULL;
navigation_routetogoal(this, navigation_bestgoal, this.origin);
LOG_DEBUG("best goal ", this.goalcurrent.classname);
if (wp && this.goalcurrent == wp)
navigation_poproute(this);
// If the bot got stuck then try to reach the farthest waypoint
if (!this.goalentity && autocvar_bot_wander_enable)
if (!this.goalentity)
{
if (!(this.aistatus & AI_STATUS_STUCK))
if (autocvar_bot_wander_enable && !(this.aistatus & AI_STATUS_STUCK))
{
LOG_DEBUG(this.netname, " cannot walk to any goal");
this.aistatus |= AI_STATUS_STUCK;
}
this.goalentity_shouldbefrozen = false;
}
this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
else
this.goalentity_shouldbefrozen = boolean(STAT(FROZEN, this.goalentity));
}
void botframe_updatedangerousobjects(float maxupdate)
......
......@@ -52,8 +52,7 @@ entity navigation_bestgoal;
waypoint_addlink_customcost(to_item, from_wp, waypoint_getlinkcost(from_wp, to_item))
#define TELEPORT_USED(pl, tele_wp) \
(time - pl.lastteleporttime < ((tele_wp.wpflags & WAYPOINTFLAG_PERSONAL) ? 2 : 0.15) \
&& boxesoverlap(tele_wp.absmin, tele_wp.absmax, pl.lastteleport_origin + STAT(PL_MIN, pl), pl.lastteleport_origin + STAT(PL_MAX, pl)))
boxesoverlap(tele_wp.absmin, tele_wp.absmax, pl.lastteleport_origin + STAT(PL_MIN, pl), pl.lastteleport_origin + STAT(PL_MAX, pl))
vector tracewalk_dest;
float tracewalk_dest_height;
......
......@@ -12,6 +12,7 @@
#include "../../antilag.qh"
#include <common/constants.qh>
#include <common/debug.qh>
#include <common/net_linked.qh>
#include <common/physics/player.qh>
......@@ -128,6 +129,108 @@ void waypoint_unreachable(entity pl)
if (j) LOG_INFOF("%d items have no nearest waypoint and cannot be walked to (marked with blue light)\n", j);
}
void waypoint_getSymmetricalAxis_cmd(entity caller, bool save, int arg_idx)
{
vector v1 = stov(argv(arg_idx++));
vector v2 = stov(argv(arg_idx++));
vector mid = (v1 + v2) / 2;
float diffy = (v2.y - v1.y);
float diffx = (v2.x - v1.x);
if (v1.y == v2.y)
diffy = 0.000001;
if (v1.x == v2.x)
diffx = 0.000001;
float m = - diffx / diffy;
float q = - m * mid.x + mid.y;
if (fabs(m) <= 0.000001) m = 0;
if (fabs(q) <= 0.000001) q = 0;
string axis_str = strcat(ftos(m), " ", ftos(q));
if (save)
cvar_set("g_waypointeditor_symmetrical_axis", axis_str);
axis_str = strcat("\"", axis_str, "\"");
sprint(caller, strcat("Axis of symmetry based on input points: ", axis_str, "\n"));
if (save)
sprint(caller, sprintf(" ^3saved to %s\n", "g_waypointeditor_symmetrical_axis"));
if (save)
{
cvar_set("g_waypointeditor_symmetrical", "-2");
sprint(caller, strcat("g_waypointeditor_symmetrical", " has been set to ",
cvar_string("g_waypointeditor_symmetrical"), "\n"));
}
}
void waypoint_getSymmetricalOrigin_cmd(entity caller, bool save, int arg_idx)
{
vector org = '0 0 0';
int ctf_flags = 0;
for (int i = 0; i < 6; i++)
{
if (argv(arg_idx + i) != "")
ctf_flags++;
}
if (ctf_flags < 2)
{
ctf_flags = 0;
org = vec2(havocbot_middlepoint);
if (argv(arg_idx) != "")
sprint(caller, "WARNING: Ignoring single input point\n");
if (havocbot_middlepoint_radius == 0)
{
sprint(caller, "Origin of symmetry can't be automatically determined\n");
return;
}
}
else
{
vector v1, v2, v3, v4, v5, v6;
for (int i = 1; i <= ctf_flags; i++)
{
if (i == 1) { v1 = stov(argv(arg_idx++)); org = v1 / ctf_flags; }
else if (i == 2) { v2 = stov(argv(arg_idx++)); org += v2 / ctf_flags; }
else if (i == 3) { v3 = stov(argv(arg_idx++)); org += v3 / ctf_flags; }
else if (i == 4) { v4 = stov(argv(arg_idx++)); org += v4 / ctf_flags; }
else if (i == 5) { v5 = stov(argv(arg_idx++)); org += v5 / ctf_flags; }
else if (i == 6) { v6 = stov(argv(arg_idx++)); org += v6 / ctf_flags; }
}
}
if (fabs(org.x) <= 0.000001) org.x = 0;
if (fabs(org.y) <= 0.000001) org.y = 0;
string org_str = strcat(ftos(org.x), " ", ftos(org.y));
if (save)
{
cvar_set("g_waypointeditor_symmetrical_origin", org_str);
cvar_set("g_waypointeditor_symmetrical_order", ftos(ctf_flags));
}
org_str = strcat("\"", org_str, "\"");
if (ctf_flags < 2)
sprint(caller, strcat("Origin of symmetry based on flag positions: ", org_str, "\n"));
else
sprint(caller, strcat("Origin of symmetry based on input points: ", org_str, "\n"));
if (save)
sprint(caller, sprintf(" ^3saved to %s\n", "g_waypointeditor_symmetrical_origin"));
if (ctf_flags < 2)
sprint(caller, "Order of symmetry: 0 (autodetected)\n");
else
sprint(caller, strcat("Order of symmetry: ", ftos(ctf_flags), "\n"));
if (save)
sprint(caller, sprintf(" ^3saved to %s\n", "g_waypointeditor_symmetrical_order"));
if (save)
{
if (ctf_flags < 2)
cvar_set("g_waypointeditor_symmetrical", "0");
else
cvar_set("g_waypointeditor_symmetrical", "-1");
sprint(caller, strcat("g_waypointeditor_symmetrical", " has been set to ",
cvar_string("g_waypointeditor_symmetrical"), "\n"));
}
}
vector waypoint_getSymmetricalPoint(vector org, int ctf_flags)
{
vector new_org = org;
......@@ -193,7 +296,7 @@ entity waypoint_spawn(vector m1, vector m2, float f)
// spawn only one destination waypoint for teleports teleporting player to the exact same spot
// otherwise links loaded from file would be applied only to the first destination
// waypoint since link format doesn't specify waypoint entities but just positions
if((f & WAYPOINTFLAG_GENERATED) && !(f & WAYPOINTFLAG_NORELINK) && m1 == m2)
if((f & WAYPOINTFLAG_GENERATED) && !(f & (WAYPOINTFLAG_NORELINK | WAYPOINTFLAG_PERSONAL)) && m1 == m2)
{
IL_EACH(g_waypoints, boxesoverlap(m1, m2, it.absmin, it.absmax),
{
......@@ -1401,6 +1504,25 @@ void waypoint_showlinks_from(entity wp, int display_type)
waypoint_showlink(wp.wp15, wp, display_type); waypoint_showlink(wp.wp31, wp, display_type);
}
void crosshair_trace_waypoints(entity pl)
{
IL_EACH(g_waypoints, true, {
it.solid = SOLID_BSP;
if (!it.wpisbox)
setsize(it, '-16 -16 -16', '16 16 16');
});
crosshair_trace(pl);
IL_EACH(g_waypoints, true, {
it.solid = SOLID_TRIGGER;
if (!it.wpisbox)
setsize(it, '0 0 0', '0 0 0');
});
if (trace_ent.classname != "waypoint")
trace_ent = NULL;
}
void botframe_showwaypointlinks()
{
if (time < botframe_waypointeditorlightningtime)
......@@ -1409,10 +1531,18 @@ void botframe_showwaypointlinks()
FOREACH_CLIENT(IS_PLAYER(it) && !it.isbot,
{
int display_type = 0;
entity head = navigation_findnearestwaypoint(it, false);
if (wasfreed(it.wp_aimed))
it.wp_aimed = NULL;
if (wasfreed(it.wp_locked))
it.wp_locked = NULL;
if (PHYS_INPUT_BUTTON_USE(it))
it.wp_locked = it.wp_aimed;
entity head = it.wp_locked;
if (!head)
head = navigation_findnearestwaypoint(it, false);
it.nearestwaypoint = head; // mainly useful for debug
it.nearestwaypointtimeout = time + 2; // while I'm at it...
if (IS_ONGROUND(it) || it.waterlevel > WATERLEVEL_NONE)
if (IS_ONGROUND(it) || it.waterlevel > WATERLEVEL_NONE || it.wp_locked)
display_type = 1; // default
else if(head && (head.wphardwired))
display_type = 2; // only hardwired
......@@ -1431,6 +1561,29 @@ void botframe_showwaypointlinks()
waypoint_showlinks_from(head, display_type);
}
}
string str;
entity wp = NULL;
if (vdist(vec2(it.velocity), <, autocvar_sv_maxspeed * 1.1))
{
crosshair_trace_waypoints(it);
if (trace_ent)
{
wp = trace_ent;
if (wp != it.wp_aimed)
{
str = sprintf("\necho ^2WP info^7: entity: %d, flags: %d, origin: '%s'\n", etof(wp), wp.wpflags, vtos(wp.origin));
if (wp.wpisbox)
str = strcat(str, sprintf("echo \" absmin: '%s', absmax: '%s'\"\n", vtos(wp.absmin), vtos(wp.absmax)));
stuffcmd(it, str);
str = sprintf("entity: %d\nflags: %d\norigin: \'%s\'", etof(wp), wp.wpflags, vtos(wp.origin));
if (wp.wpisbox)
str = strcat(str, sprintf(" \nabsmin: '%s'\nabsmax: '%s'", vtos(wp.absmin), vtos(wp.absmax)));
debug_text_3d(wp.origin, str, 0, 7, '0 0 0');
}
}
}
if (it.wp_aimed != wp)
it.wp_aimed = wp;
});
}
......
......@@ -28,6 +28,8 @@ float botframe_cachedwaypointlinks;
.float wpfire, wpcost, wpconsidered, wpisbox, wplinked, wphardwired;
.int wpflags;
.entity wp_aimed;
.entity wp_locked;
.vector wpnearestpoint;
......
......@@ -95,8 +95,7 @@ float CheatsAllowed(entity this, float i, int argc, float fr) // the cheat gets
}
#define BEGIN_CHEAT_FUNCTION() \
float cheating, attempting; \
cheating = 0; attempting = 0
float cheating = 0, attempting = 0
#define DID_CHEAT() \
++cheating
#define ADD_CHEATS(e,n) \
......
......@@ -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"
......@@ -622,7 +623,10 @@ void PutPlayerInServer(entity this)
this.angles = spot.angles;
this.angles_z = 0; // never spawn tilted even if the spot says to
if (IS_BOT_CLIENT(this))
{
this.v_angle = this.angles;
bot_aim_reset(this);
}
this.fixangle = true; // turn this way immediately
this.oldvelocity = this.velocity = '0 0 0';
this.avelocity = '0 0 0';
......@@ -801,6 +805,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 +986,7 @@ void ClientPreConnect(entity this)
((IS_REAL_CLIENT(this)) ? this.netaddress : "bot")
));
}
IP2Country_SetPlayer(self);
}
#endif
......@@ -1054,6 +1060,8 @@ string getwelcomemessage(entity this)
return s;
}
bool autocvar_sv_qcphysics = false; // TODO this is for testing - remove when qcphysics work
/**
=============
ClientConnect
......@@ -1126,6 +1134,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))
......@@ -1146,7 +1158,7 @@ void ClientConnect(entity this)
if (IS_REAL_CLIENT(this))
sv_notice_join(this);
this.move_qcphysics = true;
this.move_qcphysics = autocvar_sv_qcphysics;
// update physics stats (players can spawn before physics runs)
Physics_UpdateStats(this);
......
......@@ -188,6 +188,7 @@ void ClientCommand_wpeditor(entity caller, int request, int argc)
sprint(caller, "ERROR: this command works only if you are player\n");
else
waypoint_spawn_fromeditor(caller);
return;
}
else if (argv(1) == "remove")
{
......@@ -195,6 +196,7 @@ void ClientCommand_wpeditor(entity caller, int request, int argc)
sprint(caller, "ERROR: this command works only if you are player\n");
else
waypoint_remove_fromeditor(caller);
return;
}
else if (argv(1) == "unreachable")
{
......@@ -202,13 +204,34 @@ void ClientCommand_wpeditor(entity caller, int request, int argc)
sprint(caller, "ERROR: this command works only if you are player\n");
else
waypoint_unreachable(caller);
return;
}
else if (argv(1) == "saveall")
{
waypoint_saveall();
return;
}
else if (argv(1) == "relinkall")
{
waypoint_schedulerelinkall();
return;
return;
}
else if (argv(1) == "symaxis")
{
if (argv(2) == "set" || argv(2) == "get")
{
waypoint_getSymmetricalAxis_cmd(caller, (argv(2) == "set"), 3);
return;
}
}
else if (argv(1) == "symorigin")
{
if (argv(2) == "set" || argv(2) == "get")
{
waypoint_getSymmetricalOrigin_cmd(caller, (argv(2) == "set"), 3);
return;
}
}
}
}
......@@ -217,7 +240,13 @@ void ClientCommand_wpeditor(entity caller, int request, int argc)
case CMD_REQUEST_USAGE:
{
sprint(caller, "\nUsage:^3 cmd wpeditor action\n");
sprint(caller, " Where 'action' can be: spawn, remove, unreachable, saveall, relinkall\n");
sprint(caller, " Where 'action' can be: spawn, remove, unreachable, saveall, relinkall,\n");
sprint(caller, " symorigin get|set\n");
sprint(caller, " symorigin get|set p1 p2 ... pX\n");
sprint(caller, " symaxis get|set p1 p2\n");
sprint(caller, " where p1 p2 ... pX are positions \"x y z\" (z can be omitted)\n");
sprint(caller, " symorigin and symaxis commands are useful to determine origin/axis of symmetry"
" on maps without ctf flags or where flags aren't perfectly symmetrical\n");
return;
}
}
......
......@@ -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)
......