Been a while -- unstable 0.75

-added mask command
-added baninfo command
-added alias command and removed redundant output from `find`
-added rcon command
-added webfront (http://127.0.0.1:1624)
-true skill is officially implemented
-find now shows last connect time
-noise on pm (if gsc_enabled)
-force 8 line chat height (if gsc_enabled)
-tell admins the number of reports on join
-enhanced ban tracking
-ip wait timeout added
-remove report on ban
-can't report yourself
-remove reported players when banned
-fixed rare crash with toadmins backend
-fixed crash when finding player stats that don't exist
-fixed a bug that caused owner command to reactivate only `creator` rank player existed
-fixed a bug that caused certain notifications to be sent to all players
This commit is contained in:
RaidMax 2015-04-09 23:02:12 -05:00
parent ed4883d675
commit c3bf5bf33a
24 changed files with 1585 additions and 612 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v2.0.50727"/>
</startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>
</configuration>

View File

@ -37,7 +37,12 @@ namespace IW4MAdmin
public String getWhen()
{
return When.ToString("yyyy-MM-dd HH:mm:ss"); ;
return When.ToString("MM/dd/yy HH:mm:ss"); ;
}
public DateTime getTime()
{
return When;
}
private String Reason;

View File

@ -154,7 +154,6 @@ namespace IW4MAdmin
E.Target.tempBan(Message);
else
E.Origin.Tell("You cannot temp ban " + E.Target.getName());
}
}
@ -171,7 +170,7 @@ namespace IW4MAdmin
if (E.Owner.Website == null)
Message = "^1Player Banned: ^5" + E.Target.LastOffense;
else
Message = "^1Player Banned: ^5" + E.Target.LastOffense + "^7 (appeal at " + E.Owner.Website + ")";
Message = "^1Player Banned: ^5" + E.Target.LastOffense + "^7 (appeal at nbsclan.org)";
if (E.Origin.getLevel() > E.Target.getLevel())
{
E.Target.Ban(Message, E.Origin);
@ -203,7 +202,7 @@ namespace IW4MAdmin
public override void Execute(Event E)
{
String You = String.Format("{0} [^3{1}^7] {{2}} {{3}} [{4}^7] IP: {5}", E.Origin.getName(), E.Origin.getClientNum(), E.Origin.getID(), Utilities.levelToColor(E.Origin.getLevel()), E.Origin.getDBID(), E.Origin.getIP());
String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.getName(), E.Origin.getClientNum(), E.Origin.getID(), E.Origin.getDBID(), Utilities.levelToColor(E.Origin.getLevel()), E.Origin.getIP());
E.Origin.Tell(You);
}
@ -260,7 +259,7 @@ namespace IW4MAdmin
if (E.Origin.getLevel() >= C.getNeededPerm())
{
_commands = _commands + " [^3" + C.getName() + "^7] ";
if (count >= 3)
if (count >= 4)
{
E.Origin.Tell(_commands);
_commands = String.Empty;
@ -359,7 +358,7 @@ namespace IW4MAdmin
{
foreach (Player P in E.Owner.getPlayers())
{
if (P != null && P.getLevel() > Player.Permission.User)
if (P != null && P.getLevel() > Player.Permission.User && !P.Masked)
{
E.Origin.Tell(String.Format("[^3{0}^7] {1}", Utilities.levelToColor(P.getLevel()), P.getName()));
}
@ -421,46 +420,10 @@ namespace IW4MAdmin
foreach (Player P in db_players)
{
String mesg;
P.Alias = E.Owner.aliasDB.getPlayer(P.getDBID());
if (P.getLevel() == Player.Permission.Banned)
mesg = String.Format("[^3{0}^7] [^3@{1}^7] - {2} [{3}^7] - {4}", P.getName(), P.getDBID(), P.getID(), Utilities.levelToColor(P.getLevel()), P.getLastO());
else
mesg = String.Format("[^3{0}^7] [^3@{1}^7] - {2} [{3}^7]", P.getName(), P.getDBID(), P.getID(), Utilities.levelToColor(P.getLevel()));
String mesg = String.Format("[^3{0}^7] [^3@{1}^7] - [{2}^7] - {3} | last seen {4} ago", P.getName(), P.getDBID(), Utilities.levelToColor(P.getLevel()), P.getIP(), P.getLastConnection());
E.Origin.Tell(mesg);
if (P.Alias == null)
continue;
if (P.Alias.getNames() != null)
{
mesg = "Aliases: ";
foreach (String S in P.Alias.getNames())
{
if (S != String.Empty)
mesg += S + " | ";
}
E.Origin.Tell(mesg);
}
if (P.Alias.getIPS() != null)
{
mesg = "IPs: ";
foreach (String IP in P.Alias.getIPS())
{
if (IP.Split('.').Length > 3 && IP != String.Empty)
mesg += IP + " | ";
}
E.Origin.Tell(mesg);
}
}
}
}
class Rules : Command
@ -477,7 +440,6 @@ namespace IW4MAdmin
E.Origin.Tell("- " + r);
}
}
}
class PrivateMessage : Command
@ -487,6 +449,7 @@ namespace IW4MAdmin
public override void Execute(Event E)
{
E.Data = Utilities.removeWords(E.Data, 1);
E.Target.Alert();
E.Target.Tell("^1" + E.Origin.getName() + " ^3[PM]^7 - " + E.Data);
E.Origin.Tell(String.Format("To ^3{0} ^7-> {1}", E.Target.getName(), E.Data));
}
@ -499,12 +462,20 @@ namespace IW4MAdmin
public override void Execute(Event E)
{
if (E.Target == null)
E.Origin.Tell(String.Format("^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL", E.Origin.stats.Kills, E.Origin.stats.Deaths, E.Origin.stats.KDR, E.Origin.stats.Skill));
else
{
if (E.Target.stats == null)
E.Target.stats = E.Owner.statDB.getStats(E.Target.getDBID());
E.Origin.Tell(String.Format("[^3{4}^7] ^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL", E.Target.stats.Kills, E.Target.stats.Deaths, E.Target.stats.KDR, E.Target.stats.Skill, E.Target.getName()));
E.Origin.Tell("You do not have any stats!");
else
E.Origin.Tell(String.Format("^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL", E.Origin.stats.Kills, E.Origin.stats.Deaths, E.Origin.stats.KDR, E.Origin.stats.Skill));
}
else
{
E.Target.stats = E.Owner.statDB.getStats(E.Target.getDBID());
if (E.Target.stats == null)
E.Origin.Tell("That person does not have any stats at this time!");
else
E.Origin.Tell(String.Format("[^3{4}^7] ^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL", E.Target.stats.Kills, E.Target.stats.Deaths, E.Target.stats.KDR, E.Target.stats.Skill, E.Target.getName()));
}
}
}
@ -527,6 +498,7 @@ namespace IW4MAdmin
TopP.Add(P);
}
}
if (TopP.Count > 0)
{
E.Origin.Tell("^1TOP PLAYERS");
@ -536,6 +508,7 @@ namespace IW4MAdmin
E.Origin.Tell(String.Format("^3{0}^7 - ^5{1} ^7KDR | ^5{2} ^7SKILL", P.getName(), P.stats.KDR, P.stats.Skill));
}
}
else
E.Origin.Tell("There are no top players yet!");
}
@ -608,6 +581,12 @@ namespace IW4MAdmin
public override void Execute(Event E)
{
if (E.Target == E.Origin)
{
E.Origin.Tell("You cannot report yourself, silly.");
return;
}
if (E.Target.getLevel() > E.Origin.getLevel())
{
E.Origin.Tell("You cannot report " + E.Target.getName());
@ -655,5 +634,121 @@ namespace IW4MAdmin
E.Owner.RCON.addRCON(String.Format("admin_lastevent tell;{0};{1};{2}", E.Origin.getID(), E.Target.getID(), E.Data));
}
}
class Mask : Command
{
public Mask(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { }
public override void Execute(Event E)
{
if (E.Origin.Masked)
{
E.Origin.Masked = false;
E.Origin.Tell("You are now unmasked");
}
else
{
E.Origin.Masked = true;
E.Origin.Tell("You are now masked");
}
}
}
class BanInfo : Command
{
public BanInfo(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { }
public override void Execute(Event E)
{
if (E.Target == null)
{
E.Origin.Tell("No bans for that player.");
return;
}
Ban B = E.Owner.Bans.Find(b => b.getID().Equals(E.Target.getID()));
if (B == null)
{
E.Origin.Tell("No active ban was found for that player.");
return;
}
Player Banner = E.Owner.clientDB.getPlayer(B.getBanner(), -1);
if (Banner == null)
{
E.Origin.Tell("Ban was found for the player, but origin of the ban is unavailable.");
return;
}
E.Origin.Tell(String.Format("^1{0} ^7was banned by ^5{1} ^7for: {2}", E.Target.getName(), Banner.getName(), B.getReason()));
}
}
class Alias : Command
{
public Alias(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { }
public override void Execute(Event E)
{
E.Target.Alias = E.Owner.aliasDB.getPlayer(E.Target.getDBID());
if (E.Target.Alias == null)
{
E.Target.Tell("Could not find alias info for that player.");
return;
}
E.Target.Tell("[^3" + E.Target.getName() + "^7]");
StringBuilder message = new StringBuilder();
List<Player> playerAliases = new List<Player>();
E.Owner.getAliases(playerAliases, E.Target);
// if (E.Target.Alias.getNames() != null)
{
message.Append("Aliases: ");
foreach (Player P in playerAliases)
{
foreach (String S in P.Alias.getNames())
{
if (S != String.Empty && S != E.Target.getName())
message.Append(S + " | ");
}
}
E.Origin.Tell(message.ToString());
}
if (E.Target.Alias.getIPS() != null)
{
message.Append("IPS: ");
foreach (Player P2 in playerAliases)
{
foreach (String IP in P2.Alias.getIPS())
{
if (IP.Split('.').Length > 3 && IP != String.Empty && !message.ToString().Contains(IP))
message.Append (IP + " | ");
}
}
E.Origin.Tell(message.ToString());
}
}
}
class _RCON : Command
{
public _RCON(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { }
public override void Execute(Event E)
{
String[] Response = E.Owner.RCON.addRCON(E.Data.Trim());
if (Response.Length > 0)
E.Origin.Tell("Successfuly sent RCON command!");
}
}
}

View File

@ -79,11 +79,17 @@ namespace IW4MAdmin
protected int ExecuteNonQuery(String Request)
{
waitForClose();
Con.Open();
SQLiteCommand CMD = new SQLiteCommand(Con);
CMD.CommandText = Request;
int rowsUpdated = CMD.ExecuteNonQuery();
Con.Close();
int rowsUpdated = 0;
lock (Con)
{
Con.Open();
SQLiteCommand CMD = new SQLiteCommand(Con);
CMD.CommandText = Request;
rowsUpdated = CMD.ExecuteNonQuery();
Con.Close();
}
return rowsUpdated;
}
@ -93,13 +99,17 @@ namespace IW4MAdmin
try
{
waitForClose();
Con.Open();
SQLiteCommand mycommand = new SQLiteCommand(Con);
mycommand.CommandText = sql;
SQLiteDataReader reader = mycommand.ExecuteReader();
dt.Load(reader);
reader.Close();
Con.Close();
lock (Con)
{
Con.Open();
SQLiteCommand mycommand = new SQLiteCommand(Con);
mycommand.CommandText = sql;
SQLiteDataReader reader = mycommand.ExecuteReader();
dt.Load(reader);
reader.Close();
Con.Close();
}
}
catch (Exception e)
{
@ -134,7 +144,7 @@ namespace IW4MAdmin
{
if(!File.Exists(FileName))
{
String Create = "CREATE TABLE [CLIENTS] ( [Name] TEXT NULL, [npID] TEXT NULL, [Number] INTEGER PRIMARY KEY AUTOINCREMENT, [Level] INT DEFAULT 0 NULL, [LastOffense] TEXT NULL, [Connections] INT DEFAULT 1 NULL, [IP] TEXT NULL);";
String Create = "CREATE TABLE [CLIENTS] ( [Name] TEXT NULL, [npID] TEXT NULL, [Number] INTEGER PRIMARY KEY AUTOINCREMENT, [Level] INT DEFAULT 0 NULL, [LastOffense] TEXT NULL, [Connections] INT DEFAULT 1 NULL, [IP] TEXT NULL, [LastConnection] TEXT NULL);";
ExecuteNonQuery(Create);
Create = "CREATE TABLE [BANS] ( [Reason] TEXT NULL, [npID] TEXT NULL, [bannedByID] TEXT NULL, [IP] TEXT NULL, [TIME] TEXT NULL);";
ExecuteNonQuery(Create);
@ -150,12 +160,18 @@ namespace IW4MAdmin
if (Result != null && Result.Rows.Count > 0)
{
DataRow ResponseRow = Result.Rows[0];
// if (ResponseRow["IP"].ToString().Length < 2)
// ResponseRow["IP"] = DateTime.Now.ToString(); // because aliases and backwards compatibility
DateTime LC;
return new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), cNum, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString());
try
{
LC = DateTime.Parse(ResponseRow["LastConnection"].ToString());
}
catch (Exception)
{
LC = DateTime.Now;
}
return new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), cNum, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), (int)ResponseRow["Connections"], ResponseRow["IP"].ToString(), LC);
}
else
@ -171,11 +187,17 @@ namespace IW4MAdmin
if (Result != null && Result.Rows.Count > 0)
{
DataRow p = Result.Rows[0];
DateTime LC;
try
{
LC = DateTime.Parse(p["LastConnection"].ToString());
}
catch (Exception)
{
LC = DateTime.Now;
}
// if (p["IP"].ToString().Length < 2)
// p["IP"] = DateTime.Now.ToString(); // because aliases and backwards compatibility
return new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString());
return new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString(), LC);
}
else
@ -194,7 +216,17 @@ namespace IW4MAdmin
{
foreach (DataRow p in Result.Rows)
{
Players.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString()));
DateTime LC;
try
{
LC = DateTime.Parse(p["LastConnection"].ToString());
}
catch (Exception)
{
LC = DateTime.Now;
}
Players.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), Convert.ToInt32(p["Connections"]), p["IP"].ToString(), LC));
}
return Players;
}
@ -206,7 +238,7 @@ namespace IW4MAdmin
//Returns any player with level 4 permissions, null if no owner found
public Player getOwner()
{
String Query = String.Format("SELECT * FROM CLIENTS WHERE Level = '{0}'", 4);
String Query = String.Format("SELECT * FROM CLIENTS WHERE Level >= '{0}'", 4);
DataTable Result = GetDataTable(Query);
if (Result != null && Result.Rows.Count > 0)
@ -225,14 +257,12 @@ namespace IW4MAdmin
public List<Ban> getBans()
{
List<Ban> Bans = new List<Ban>();
DataTable Result = GetDataTable("SELECT * FROM BANS");
DataTable Result = GetDataTable("SELECT * FROM BANS ORDER BY TIME DESC");
foreach (DataRow Row in Result.Rows)
{
if (Row["TIME"].ToString().Length < 2) //compatibility with my old database
Row["TIME"] = DateTime.Now.ToString();
// if (Row["IP"].ToString().Length < 2)
// Row["IP"] = DateTime.Now.ToString(); //because we don't have old ip's and don't want a messy alias
Bans.Add(new Ban(Row["Reason"].ToString(), Row["npID"].ToString(), Row["bannedByID"].ToString(), DateTime.Parse(Row["TIME"].ToString()), Row["IP"].ToString()));
}
@ -261,6 +291,7 @@ namespace IW4MAdmin
newPlayer.Add("LastOffense", "");
newPlayer.Add("Connections", 1);
newPlayer.Add("IP", P.getIP());
newPlayer.Add("LastConnection", Utilities.DateTimeSQLite(DateTime.Now));
Insert("CLIENTS", newPlayer);
}
@ -276,6 +307,7 @@ namespace IW4MAdmin
updatedPlayer.Add("LastOffense", P.getLastO());
updatedPlayer.Add("Connections", P.getConnections());
updatedPlayer.Add("IP", P.getIP());
updatedPlayer.Add("LastConnection", Utilities.DateTimeSQLite(DateTime.Now));
Update("CLIENTS", updatedPlayer, String.Format("npID = '{0}'", P.getID()));
}
@ -314,7 +346,7 @@ namespace IW4MAdmin
{
if (!File.Exists(FileName))
{
String Create = "CREATE TABLE [STATS] ( [Number] INTEGER, [KILLS] INTEGER DEFAULT 0, [DEATHS] INTEGER DEFAULT 0, [KDR] REAL DEFAULT 0, [SKILL] REAL DEFAULT 0 );";
String Create = "CREATE TABLE [STATS] ( [Number] INTEGER, [KILLS] INTEGER DEFAULT 0, [DEATHS] INTEGER DEFAULT 0, [KDR] REAL DEFAULT 0, [SKILL] REAL DEFAULT 0, [MEAN] REAL DEFAULT 0, [DEV] REAL DEFAULT 0 );";
ExecuteNonQuery(Create);
}
}
@ -328,11 +360,18 @@ namespace IW4MAdmin
if (Result != null && Result.Rows.Count > 0)
{
DataRow ResponseRow = Result.Rows[0];
return new Stats(Convert.ToInt32(ResponseRow["KILLS"]), Convert.ToInt32(ResponseRow["DEATHS"]), Convert.ToDouble(ResponseRow["KDR"]), Convert.ToDouble(ResponseRow["SKILL"]));
if (ResponseRow["MEAN"] == DBNull.Value)
ResponseRow["MEAN"] = 25;
if (ResponseRow["DEV"] == DBNull.Value)
ResponseRow["DEV"] = 8;
if (ResponseRow["SKILL"] == DBNull.Value)
ResponseRow["SKILL"] = 0;
return new Stats(Convert.ToInt32(ResponseRow["Number"]), Convert.ToInt32(ResponseRow["KILLS"]), Convert.ToInt32(ResponseRow["DEATHS"]), Convert.ToDouble(ResponseRow["KDR"]), Convert.ToDouble(ResponseRow["SKILL"]), Convert.ToDouble(ResponseRow["MEAN"]), Convert.ToDouble(ResponseRow["DEV"]));
}
else
return null;
return new Stats(0, 0, 0, 0, 0, 25, 8.3333);
}
public void addPlayer(Player P)
@ -343,7 +382,9 @@ namespace IW4MAdmin
newPlayer.Add("KILLS", 0);
newPlayer.Add("DEATHS", 0);
newPlayer.Add("KDR", 0);
newPlayer.Add("SKILL", 1);
newPlayer.Add("SKILL", Moserware.Skills.GameInfo.DefaultGameInfo.DefaultRating.ConservativeRating);
newPlayer.Add("MEAN", Moserware.Skills.GameInfo.DefaultGameInfo.DefaultRating.Mean);
newPlayer.Add("DEV", Moserware.Skills.GameInfo.DefaultGameInfo.DefaultRating.StandardDeviation);
Insert("STATS", newPlayer);
}
@ -357,6 +398,8 @@ namespace IW4MAdmin
updatedPlayer.Add("DEATHS", P.stats.Deaths);
updatedPlayer.Add("KDR", Math.Round(P.stats.KDR, 2));
updatedPlayer.Add("SKILL", P.stats.Skill);
updatedPlayer.Add("MEAN", P.stats.Rating.Mean);
updatedPlayer.Add("DEV", P.stats.Rating.StandardDeviation);
Update("STATS", updatedPlayer, String.Format("Number = '{0}'", P.getDBID()));
}
@ -364,7 +407,7 @@ namespace IW4MAdmin
//Returns top 8 players (we filter through them later)
public List<Stats> topStats()
{
String Query = String.Format("SELECT * FROM STATS WHERE SKILL > '{0}' ORDER BY SKILL DESC LIMIT 8", 10);
String Query = String.Format("SELECT * FROM STATS WHERE SKILL > '{0}' ORDER BY SKILL DESC LIMIT 5", 230);
DataTable Result = GetDataTable(Query);
List<Stats> Top = new List<Stats>();
@ -373,8 +416,16 @@ namespace IW4MAdmin
{
foreach (DataRow D in Result.Rows)
{
Stats S = new Stats(Convert.ToInt32(D["Number"]), Convert.ToInt32(D["DEATHS"]), Convert.ToDouble(D["KDR"]), Convert.ToDouble(D["SKILL"]));
if (S.Skill > 10)
if (D["MEAN"] == DBNull.Value)
continue;
if (D["DEV"] == DBNull.Value)
continue;
if (D["SKILL"] == DBNull.Value)
D["SKILL"] = 0;
Stats S = new Stats(Convert.ToInt32(D["Number"]), Convert.ToInt32(D["KILLS"]), Convert.ToInt32(D["DEATHS"]), Convert.ToDouble(D["KDR"]), Convert.ToDouble(D["SKILL"]), Convert.ToDouble(D["MEAN"]), Convert.ToDouble(D["DEV"]));
if (S.Skill > 230)
Top.Add(S);
}
}
@ -382,6 +433,42 @@ namespace IW4MAdmin
return Top;
}
public List<Stats> getMultipleStats(int start, int length)
{
String Query = String.Format("SELECT * FROM STATS ORDER BY SKILL DESC LIMIT '{0}' OFFSET '{1}'", length, start);
DataTable Result = GetDataTable(Query);
List<Stats> Stats = new List<Stats>();
if (Result != null && Result.Rows.Count > 0)
{
foreach (DataRow D in Result.Rows)
{
if (D["MEAN"] == DBNull.Value)
continue;
if (D["DEV"] == DBNull.Value)
continue;
if (D["SKILL"] == DBNull.Value)
D["SKILL"] = 0;
Stats S = new Stats(Convert.ToInt32(D["Number"]), Convert.ToInt32(D["KILLS"]), Convert.ToInt32(D["DEATHS"]), Convert.ToDouble(D["KDR"]), Convert.ToDouble(D["SKILL"]), Convert.ToDouble(D["MEAN"]), Convert.ToDouble(D["DEV"]));
Stats.Add(S);
}
}
return Stats;
}
public int totalStats()
{
DataTable Result = GetDataTable("SELECT * from STATS ORDER BY Number DESC LIMIT 1");
if (Result.Rows.Count > 0)
return Convert.ToInt32(Result.Rows[0]["Number"]);
else
return 0;
}
public void clearSkill()
{
String Query = "SELECT * FROM STATS";
@ -423,6 +510,21 @@ namespace IW4MAdmin
return null;
}
public List<Aliases> getPlayer(String IP)
{
String Query = String.Format("SELECT * FROM ALIASES WHERE IPS LIKE '%{0}%'", IP);
DataTable Result = GetDataTable(Query);
List<Aliases> players = new List<Aliases>();
if (Result != null && Result.Rows.Count > 0)
{
foreach (DataRow p in Result.Rows)
players.Add(new Aliases(Convert.ToInt32(p["Number"]), p["NAMES"].ToString(), p["IPS"].ToString()));
}
return players;
}
public void addPlayer(Aliases Alias)
{
Dictionary<String, object> newPlayer = new Dictionary<String, object>();

View File

@ -66,24 +66,19 @@ namespace IW4MAdmin
eventType = eventType.Trim();
if (eventType == "J")
return new Event(GType.Connect, null, SV.clientFromLine(line, 3, true), null, SV);
return new Event(GType.Connect, null, SV.clientFromEventLine(line, 2), null, SV);
if (eventType == "Q")
return new Event(GType.Disconnect, null, SV.getPlayers()[Convert.ToInt16(line[2])], null, null);
return new Event(GType.Disconnect, null, SV.clientFromEventLine(line, 2), null, SV);
if (eventType == "K")
return new Event(GType.Kill, line[9], SV.clientFromLineArr(line, true), SV.clientFromLineArr(line, false), null);
return new Event(GType.Kill, line[9], SV.clientFromEventLine(line, 6), SV.clientFromEventLine(line, 2), SV);
if (line[0].Substring(line[0].Length - 3).Trim() == "say")
{
if (line.Length < 4)
{
Console.WriteLine("SAY FUCKED UP BIG-TIME");
return null;
}
Regex rgx = new Regex("[^a-zA-Z0-9 -! -_]");
string message = rgx.Replace(line[4], "");
return new Event(GType.Say, Utilities.removeNastyChars(message), SV.clientFromLine(line, 3, false), null, null);
return new Event(GType.Say, Utilities.removeNastyChars(message), SV.clientFromEventLine(line, 2), null, SV);
}
if (eventType == ":")

View File

@ -112,11 +112,17 @@ namespace IW4MAdmin
return encoding.GetString(buffer);
}
}
public String[] readAll()
{
return Handle.ReadToEnd().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
public String getLines()
{
return Handle.ReadToEnd();
}
public String[] end(int neededLines)
{
var lines = new List<String>();

View File

@ -14,7 +14,7 @@ namespace IW4MAdmin
public void Send()
{
String URI = String.Format("http://raidmax.org/IW4M/Admin/heartbeat.php?address={0}&name={1}&map={2}&players={3}&version={4}", Instance.getPort().ToString(), Instance.getName(), Instance.getMap(), Instance.getClientNum().ToString() + '/' + Instance.getMaxClients().ToString(), IW4MAdmin.Program.Version.ToString());
String URI = String.Format("http://raidmax.org/IW4M/Admin/heartbeat.php?address={0}&name={1}&map={2}&players={3}&version={4}", Instance.getPort().ToString(), Instance.getName(), Instance.getMap(), Instance.statusPlayers.Count.ToString() + '/' + Instance.getMaxClients().ToString(), IW4MAdmin.Program.Version.ToString());
Handle.Request(URI);
}

View File

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>IW4MAdmin</RootNamespace>
<AssemblyName>IW4MAdmin</AssemblyName>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<IsWebBootstrapper>true</IsWebBootstrapper>
@ -78,11 +78,18 @@
<StartupObject>IW4MAdmin.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="Kayak">
<HintPath>..\packages\Kayak.0.7.2\lib\Kayak.dll</HintPath>
</Reference>
<Reference Include="Moserware.Skills, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>bin\Debug\Moserware.Skills.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Data.SQLite, Version=1.0.66.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
<Reference Include="System.Data.SQLite, Version=1.0.96.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>bin\Release\System.Data.SQLite.dll</HintPath>
<HintPath>libs\System.Data.SQLite.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
@ -110,9 +117,31 @@
<Compile Include="Server.cs" />
<Compile Include="TrueSkill.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="WebFront.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="version.txt" />
<Content Include="libs\Moserware.Skills.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="libs\SQLite.Interop.dll" />
<Content Include="libs\System.Data.SQLite.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="version.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="webfront\bans.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="webfront\header.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="webfront\main.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="webfront\stats.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="App.config" />
<Content Include="config\maps.cfg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -127,6 +156,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="IW4M ADMIN_TemporaryKey.pfx" />
<None Include="packages.config" />
<None Include="Properties\app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@ -151,7 +181,8 @@
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy $(TargetDir)$(TargetFileName) $(SolutionDir)OFFICIAL\NBS\$(TargetFileName)</PostBuildEvent>
<PostBuildEvent>copy $(TargetDir)$(TargetFileName) $(SolutionDir)OFFICIAL\Release\$(TargetFileName)
copy $(SolutionDir)Admin\version.txt $(SolutionDir)OFFICIAL\Release\version.txt</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -10,8 +10,9 @@ namespace IW4MAdmin
static String IP;
static int Port;
static String RCON;
static public double Version = 0.6;
static public double Version = 0.7;
static public double latestVersion;
static public List<Server> Servers;
static void Main(string[] args)
{
@ -24,9 +25,9 @@ namespace IW4MAdmin
else
Console.WriteLine(" Version " + Version + " (unable to retrieve latest)");
Console.WriteLine("=====================================================");
foreach (Server IW4M in checkConfig())
Servers = checkConfig();
foreach (Server IW4M in Servers)
{
//Threading seems best here
Server SV = IW4M;
@ -36,6 +37,9 @@ namespace IW4MAdmin
Console.WriteLine("Now monitoring " + SV.getName());
}
IW4MAdmin_Web.WebFront test = new IW4MAdmin_Web.WebFront();
test.Init();
Utilities.Wait(5); //Give them time to read an error before exiting
}

View File

@ -6,34 +6,33 @@ namespace IW4MAdmin
{
class Stats
{
public Stats(int K, int D, double kdr, double skill)
public Stats(int n, int K, int D, double kdr, double skill, double mean, double dev)
{
statIndex = n;
Kills = K;
Deaths = D;
KDR = Math.Round(kdr,2);
Skill = Math.Round(skill,2);
lastSigma = lastMew/3;
lastMew = 25;
Rating = new Moserware.Skills.Rating(mean, dev);
Skill = Math.Round(Rating.ConservativeRating, 3)*10;
}
public void updateKDR()
{
KDR = Math.Round((double)((double)Kills / (double)Deaths), 2);
}
int tempDeaths = Deaths; // cuz we don't want undefined!
if (Deaths == 0)
tempDeaths = 1;
public void updateSkill()
{
Skill = TrueSkill.Gaussian(lastMew, lastSigma);
KDR = Math.Round((double)((double)Kills / (double)tempDeaths), 2);
}
public int Kills;
public int Deaths;
public double KDR;
public double Skill;
public double lastSigma;
public double lastMew;
public int statIndex;
public Moserware.Skills.Rating Rating;
}
class Aliases
@ -73,7 +72,7 @@ namespace IW4MAdmin
public void addName(String Name)
{
if (Name.Trim() != String.Empty && Name != null)
Names += ';' + Names;
Names += ';' + Name;
}
public void addIP(String IP)
@ -112,7 +111,18 @@ namespace IW4MAdmin
IP = "";
Warnings = 0;
Alias = new Aliases(0, "", "");
stats = new Stats(0, 0, 0, 1);
stats = new Stats(0, 0, 0, 0, Moserware.Skills.GameInfo.DefaultGameInfo.DefaultRating.ConservativeRating, Moserware.Skills.GameInfo.DefaultGameInfo.DefaultRating.Mean, Moserware.Skills.GameInfo.DefaultGameInfo.DefaultRating.StandardDeviation);
LastConnection = DateTime.Now;
}
public Player(string n, string id, int num, String I)
{
Name = n;
npID = id;
Number = num;
IP = I;
LastConnection = DateTime.Now;
}
public Player(string n, string id, int num, Player.Permission l, int cind, String lo, int con, String IP2)
@ -129,6 +139,26 @@ namespace IW4MAdmin
Connections = con;
IP = IP2;
Warnings = 0;
Masked = false;
LastConnection = DateTime.Now;
}
public Player(string n, string id, int num, Player.Permission l, int cind, String lo, int con, String IP2, DateTime LC)
{
Name = n;
npID = id;
Number = num;
Level = l;
dbID = cind;
if (lo == null)
LastOffense = String.Empty;
else
LastOffense = lo;
Connections = con;
IP = IP2;
Warnings = 0;
Masked = false;
LastConnection = LC;
}
public String getName()
@ -171,9 +201,24 @@ namespace IW4MAdmin
return IP;
}
public String getLastConnection()
{
TimeSpan Elapsed = DateTime.Now - LastConnection;
if (Elapsed.Minutes < 60)
return Elapsed.Minutes + " minutes";
if (Elapsed.Hours <= 24)
return Elapsed.Hours + " hours";
if (Elapsed.Days <= 365)
return Elapsed.Days + " days";
else
return "a very long time";
}
public void updateName(String n)
{
Name = n;
if (n.Trim() != String.Empty)
Name = n;
}
public void updateIP(String I)
@ -209,7 +254,12 @@ namespace IW4MAdmin
public void Ban(String Message, Player Sender)
{
lastEvent.Owner.Ban(Message, this, Sender);
lastEvent.Owner.Ban(Message, this, Sender);
}
public void Alert()
{
lastEvent.Owner.Alert(this);
}
//should be moved to utils
@ -233,11 +283,13 @@ namespace IW4MAdmin
private int dbID;
public int Connections;
private String IP;
private DateTime LastConnection;
public Event lastEvent;
public String LastOffense;
public int Warnings;
public Stats stats;
public Aliases Alias;
public bool Masked;
}
}

View File

@ -1,234 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
namespace IW4MAdmin
{
public class SQLiteDatabase
{
String dbConnection;
/// <summary>
/// Default Constructor for SQLiteDatabase Class.
/// </summary>
public SQLiteDatabase(String file)
{
dbConnection = "Data Source=" + file;
}
/// <summary>
/// Single Param Constructor for specifying the DB file.
/// </summary>
/// <param name="inputFile">The File containing the DB</param>
public SQLiteDatabase(String inputFile)
{
dbConnection = String.Format("Data Source={0}", inputFile);
}
/// <summary>
/// Single Param Constructor for specifying advanced connection options.
/// </summary>
/// <param name="connectionOpts">A dictionary containing all desired options and their values</param>
public SQLiteDatabase(Dictionary<String, String> connectionOpts)
{
String str = "";
foreach (KeyValuePair<String, String> row in connectionOpts)
{
str += String.Format("{0}={1}; ", row.Key, row.Value);
}
str = str.Trim().Substring(0, str.Length - 1);
dbConnection = str;
}
/// <summary>
/// Allows the programmer to run a query against the Database.
/// </summary>
/// <param name="sql">The SQL to run</param>
/// <returns>A DataTable containing the result set.</returns>
public DataTable GetDataTable(string sql)
{
DataTable dt = new DataTable();
try
{
SQLiteConnection cnn = new SQLiteConnection(dbConnection);
cnn.Open();
SQLiteCommand mycommand = new SQLiteCommand(cnn);
mycommand.CommandText = sql;
SQLiteDataReader reader = mycommand.ExecuteReader();
dt.Load(reader);
reader.Close();
cnn.Close();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw new Exception(e.Message);
}
return dt;
}
/// <summary>
/// Allows the programmer to interact with the database for purposes other than a query.
/// </summary>
/// <param name="sql">The SQL to be run.</param>
/// <returns>An Integer containing the number of rows updated.</returns>
public int ExecuteNonQuery(string sql)
{
SQLiteConnection cnn = new SQLiteConnection(dbConnection);
cnn.Open();
SQLiteCommand mycommand = new SQLiteCommand(cnn);
mycommand.CommandText = sql;
int rowsUpdated = mycommand.ExecuteNonQuery();
cnn.Close();
return rowsUpdated;
}
/// <summary>
/// Allows the programmer to retrieve single items from the DB.
/// </summary>
/// <param name="sql">The query to run.</param>
/// <returns>A string.</returns>
public string ExecuteScalar(string sql)
{
SQLiteConnection cnn = new SQLiteConnection(dbConnection);
cnn.Open();
SQLiteCommand mycommand = new SQLiteCommand(cnn);
mycommand.CommandText = sql;
object value = mycommand.ExecuteScalar();
cnn.Close();
if (value != null)
{
return value.ToString();
}
return "";
}
/// <summary>
/// Allows the programmer to easily update rows in the DB.
/// </summary>
/// <param name="tableName">The table to update.</param>
/// <param name="data">A dictionary containing Column names and their new values.</param>
/// <param name="where">The where clause for the update statement.</param>
/// <returns>A boolean true or false to signify success or failure.</returns>
public bool Update(String tableName, Dictionary<String, String> data, String where)
{
String vals = "";
Boolean returnCode = true;
if (data.Count >= 1)
{
foreach (KeyValuePair<String, String> val in data)
{
vals += String.Format(" {0} = '{1}',", val.Key.ToString(), val.Value.ToString());
}
vals = vals.Substring(0, vals.Length - 1);
}
try
{
this.ExecuteNonQuery(String.Format("update {0} set {1} where {2};", tableName, vals, where));
}
catch( Exception fail)
{
Console.WriteLine(fail.Message);
returnCode = false;
}
return returnCode;
}
/// <summary>
/// Allows the programmer to easily delete rows from the DB.
/// </summary>
/// <param name="tableName">The table from which to delete.</param>
/// <param name="where">The where clause for the delete.</param>
/// <returns>A boolean true or false to signify success or failure.</returns>
public bool Delete(String tableName, String where)
{
Boolean returnCode = true;
try
{
this.ExecuteNonQuery(String.Format("delete from {0} where {1};", tableName, where));
}
catch (Exception fail)
{
Console.WriteLine(fail.Message);
returnCode = false;
}
return returnCode;
}
/// <summary>
/// Allows the programmer to easily insert into the DB
/// </summary>
/// <param name="tableName">The table into which we insert the data.</param>
/// <param name="data">A dictionary containing the column names and data for the insert.</param>
/// <returns>A boolean true or false to signify success or failure.</returns>
public bool Insert(String tableName, Dictionary<String, String> data)
{
String columns = "";
String values = "";
Boolean returnCode = true;
foreach (KeyValuePair<String, String> val in data)
{
columns += String.Format(" {0},", val.Key.ToString());
values += String.Format(" '{0}',", val.Value);
}
columns = columns.Substring(0, columns.Length - 1);
values = values.Substring(0, values.Length - 1);
try
{
this.ExecuteNonQuery(String.Format("insert into {0}({1}) values({2});", tableName, columns, values));
}
catch (Exception fail)
{
Console.WriteLine(fail.Message);
returnCode = false;
}
return returnCode;
}
/// <summary>
/// Allows the programmer to easily delete all data from the DB.
/// </summary>
/// <returns>A boolean true or false to signify success or failure.</returns>
public bool ClearDB()
{
DataTable tables;
try
{
tables = this.GetDataTable("select NAME from SQLITE_MASTER where type='table' order by NAME;");
foreach (DataRow table in tables.Rows)
{
this.ClearTable(table["NAME"].ToString());
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Allows the user to easily clear all data from a specific table.
/// </summary>
/// <param name="table">The name of the table to clear.</param>
/// <returns>A boolean true or false to signify success or failure.</returns>
public bool ClearTable(String table)
{
try
{
this.ExecuteNonQuery(String.Format("delete from {0};", table));
return true;
}
catch
{
return false;
}
}
}
}

View File

@ -36,6 +36,8 @@ namespace IW4MAdmin
HB = new Heartbeat(this);
Macros = new Dictionary<String, Object>();
Reports = new List<Report>();
Skills = new Moserware.TrueSkill();
statusPlayers = new Dictionary<string, Player>();
nextMessage = 0;
initCommands();
initMacros();
@ -55,6 +57,11 @@ namespace IW4MAdmin
return mapname;
}
public String getGametype()
{
return Gametype;
}
//Returns current server IP set by `net_ip` -- *STRING*
public String getIP()
{
@ -101,111 +108,178 @@ namespace IW4MAdmin
return Bans;
}
public void getAliases(List<Player> returnPlayers, Player Origin)
{
if (Origin == null)
return;
List<Aliases> aliasAliases = new List<Aliases>();
Aliases currentAliases = aliasDB.getPlayer(Origin.getDBID());
if (currentAliases == null)
Log.Write("No aliases found for " + Origin.getName(), Log.Level.Debug);
foreach (String IP in currentAliases.getIPS())
{
List<Aliases> tmp = aliasDB.getPlayer(IP);
if (tmp != null)
aliasAliases = tmp;
foreach (Aliases a in aliasAliases)
{
if (a == null)
continue;
Player aliasPlayer = clientDB.getPlayer(a.getNumber());
if (aliasPlayer != null)
{
aliasPlayer.Alias = a;
if (returnPlayers.Exists(p => p.getDBID() == aliasPlayer.getDBID()) == false)
{
returnPlayers.Add(aliasPlayer);
getAliases(returnPlayers, aliasPlayer);
}
}
}
}
}
//Add player object p to `players` list
public bool addPlayer(Player P)
{
if (P.getClientNum() < 0 || P.getClientNum() > (players.Count-1)) // invalid index
return false;
if (players[P.getClientNum()] != null && players[P.getClientNum()].getID() == P.getID()) // if someone has left and a new person has taken their spot between polls
return true;
Log.Write("Client slot #" + P.getClientNum() + " now reserved", Log.Level.Debug);
#if DEBUG == false
try
#endif
{
if (clientDB.getPlayer(P.getID(), P.getClientNum()) == null)
{
clientDB.addPlayer(P);
Player New = clientDB.getPlayer(P.getID(), P.getClientNum());
statDB.addPlayer(New);
}
//messy way to prevent loss of last event
Player NewPlayer = clientDB.getPlayer(P.getID(), P.getClientNum());
NewPlayer.stats = statDB.getStats(NewPlayer.getDBID());
if (NewPlayer.stats == null) //For safety
if (NewPlayer == null) // first time connecting
{
Log.Write("Client slot #" + P.getClientNum() + " first time connecting", Log.Level.All);
clientDB.addPlayer(P);
NewPlayer = clientDB.getPlayer(P.getID(), P.getClientNum());
aliasDB.addPlayer(new Aliases(NewPlayer.getDBID(), NewPlayer.getName(), NewPlayer.getIP()));
statDB.addPlayer(NewPlayer);
NewPlayer.stats = statDB.getStats(NewPlayer.getDBID());
}
if (P.lastEvent == null)
NewPlayer.updateName(P.getName().Trim());
NewPlayer.stats = statDB.getStats(NewPlayer.getDBID());
NewPlayer.Alias = aliasDB.getPlayer(NewPlayer.getDBID());
if (NewPlayer.Alias == null)
{
aliasDB.addPlayer(new Aliases(NewPlayer.getDBID(), NewPlayer.getName(), NewPlayer.getIP()));
NewPlayer.Alias = aliasDB.getPlayer(NewPlayer.getDBID());
}
// try not to crash if no stats!
if (P.lastEvent == null || P.lastEvent.Owner == null)
NewPlayer.lastEvent = new Event(Event.GType.Say, null, NewPlayer, null, this); // this is messy but its throwing an error when they've started it too late
else
NewPlayer.lastEvent = P.lastEvent;
if (players[NewPlayer.getClientNum()] == null)
{
bool updated = false;
while (!updated) //Sometimes we get a issue when a player disconnects and it doesn't register
{
try
{
P.updateIP(IPS[P.getID()].Trim());
NewPlayer.updateIP(P.getIP());
Log.Write("Sucessfully updated " + NewPlayer.getName() + "'s IP to " + P.getIP(), Log.Level.Debug);
updated = true;
}
catch
{
Log.Write("Waiting for " + P.getName() + "'s IP...", Log.Level.Debug);
Utilities.Wait(2);
}
}
if (aliasDB.getPlayer(NewPlayer.getDBID()) == null)
aliasDB.addPlayer(new Aliases(NewPlayer.getDBID(), P.getName(), P.getIP()));
NewPlayer.Alias = aliasDB.getPlayer(NewPlayer.getDBID());
if ((NewPlayer.Alias.getNames().Find(m => m.Equals(P.getName()))) == null || NewPlayer.getName() == null || NewPlayer.getName() == String.Empty)
{
Log.Write("Connecting player has new alias -- " + P.getName() + " previous was: " + NewPlayer.getName(), Log.Level.Debug);
NewPlayer.updateName(P.getName());
NewPlayer.Alias.addName(P.getName());
aliasDB.updatePlayer(NewPlayer.Alias);
}
// lets check aliases
if ((NewPlayer.Alias.getNames().Find(m => m.Equals(P.getName()))) == null || NewPlayer.getName() == null || NewPlayer.getName() == String.Empty)
{
NewPlayer.updateName(P.getName().Trim());
NewPlayer.Alias.addName(NewPlayer.getName());
}
// and ips
if (NewPlayer.Alias.getIPS().Find(i => i.Equals(P.getIP())) == null || P.getIP() == null || P.getIP() == String.Empty)
{
NewPlayer.updateIP(P.getIP());
NewPlayer.Alias.addIP(P.getIP());
}
if (NewPlayer.Alias.getIPS().Find(i => i.Equals(P.getIP())) == null || P.getIP() == null || P.getIP() == String.Empty)
{
Log.Write("Connecting player has new IP - " + P.getIP(), Log.Level.Debug);
NewPlayer.updateIP(P.getIP());
NewPlayer.Alias.addIP(P.getIP());
aliasDB.updatePlayer(NewPlayer.Alias);
}
aliasDB.updatePlayer(NewPlayer.Alias);
clientDB.updatePlayer(NewPlayer);
clientDB.updatePlayer(NewPlayer);
NewPlayer.lastEvent.Owner = this; // cuz crashes
List<Player> newPlayerAliases = new List<Player>();
getAliases(newPlayerAliases, NewPlayer);
Ban B = isBanned(NewPlayer);
if (B != null || NewPlayer.getLevel() == Player.Permission.Banned)
foreach (Player aP in newPlayerAliases)
{
if (aP == null)
continue;
String Reason = String.Empty;
String Message = String.Empty;
if (NewPlayer.getLevel() == Player.Permission.Banned)
{
Log.Write("Banned client " + P.getName() + " trying to connect...", Log.Level.Debug);
string Reason = String.Empty;
if (B != null)
Reason = B.getReason();
if (aP.getLastO() != null)
Message = "^7Player Kicked: Previously banned for ^5" + aP.getLastO() + " ^7(appeal at " + Website+ ")";
else
Reason = P.LastOffense;
Message = "Player Kicked: Previous Ban";
String Message = "^1Player Kicked: ^7Previously Banned for ^5" + Reason;
NewPlayer.Kick(Message);
if (players[P.getClientNum()] != null)
players[P.getClientNum()] = null;
if (players[NewPlayer.getClientNum()] != null)
{
lock (players)
{
players[NewPlayer.getClientNum()] = null;
}
}
return true;
}
players[NewPlayer.getClientNum()] = null;
Ban B = isBanned(aP);
if (B != null && aP != null)
{
Log.Write("Banned alias " + aP.getName() + " trying to connect...", Log.Level.Debug);
aP.lastEvent = NewPlayer.lastEvent;
aP.LastOffense = "Evading";
if (B.getReason() != null)
NewPlayer.Ban("^7Previously Banned: ^5" + B.getReason() + " ^7(appeal at " + Website + ")", NewPlayer);
else
NewPlayer.Ban("^7Previous Ban", NewPlayer);
lock (players)
{
if (players[NewPlayer.getClientNum()] != null)
players[NewPlayer.getClientNum()] = null;
}
return true;
}
}
lock (players)
{
players[NewPlayer.getClientNum()] = null; // just in case we have shit in the way
players[NewPlayer.getClientNum()] = NewPlayer;
}
#if DEBUG == FALSE
NewPlayer.Tell("Welcome ^5" + NewPlayer.getName() + " ^7this is your ^5" + Utilities.timesConnected(NewPlayer.getConnections()) + " ^7time connecting!");
#endif
Log.Write("Client " + NewPlayer.getName() + " connecting...", Log.Level.Debug);
clientnum++;
Log.Write("Client " + NewPlayer.getName() + " connecting...", Log.Level.Debug);
if (NewPlayer.getLevel() == Player.Permission.Flagged)
ToAdmins("^1NOTICE: ^7Flagged player ^5" + NewPlayer.getName() + "^7 has joined!");
}
if (NewPlayer.getLevel() == Player.Permission.Flagged)
ToAdmins("^1NOTICE: ^7Flagged player ^5" + NewPlayer.getName() + "^7 has joined!");
if (NewPlayer.getLevel() > Player.Permission.Moderator)
NewPlayer.Tell("There are ^5" + Reports.Count + " ^7recent reports!");
return true;
}
@ -221,115 +295,78 @@ namespace IW4MAdmin
//Remove player by CLIENT NUMBER
public bool removePlayer(int cNum)
{
if (cNum >= 0 && cNum < 18)
if (cNum >= 0 && cNum < players.Count)
{
Log.Write("Updating stats for " + players[cNum].getName(), Log.Level.Debug);
statDB.updatePlayer(players[cNum]);
Player Leaving = players[cNum];
Leaving.Connections++;
clientDB.updatePlayer(Leaving);
statDB.updatePlayer(Leaving);
Log.Write("Client at " + cNum + " disconnecting...", Log.Level.Debug);
players[cNum] = null;
clientnum--;
lock (players)
{
players[cNum] = null;
}
clientnum = statusPlayers.Count;
return true;
}
else
{
Log.Write("Client disconnecting has an invalid client index!", Log.Level.Debug);
clientnum = statusPlayers.Count;
return false;
}
}
//Get a client from players list by by log line. If create = true, it will return a new player object
public Player clientFromLine(String[] line, int name_pos, bool create)
{
string Name = line[name_pos].ToString().Trim();
if (create)
{
Player C = new Player(Name, line[1].ToString(), Convert.ToInt16(line[2]), 0);
return C;
}
else
{
foreach (Player P in players)
{
if (P == null)
continue;
if (line[1].Trim() == P.getID())
return P;
}
Log.Write("Could not find player but player is in server. Lets try to manually add (looks like you didn't start me on an empty server)", Log.Level.All);
players[Convert.ToInt16(line[2])] = null;
addPlayer(new Player(Name, line[1].ToString().Trim(), Convert.ToInt16(line[2]), 0));
return players[Convert.ToInt16(line[2])];
}
}
//Should be client from Name ( returns client in players list by name )
public Player clientFromLine(String Name)
{
foreach (Player P in players)
{
if (P == null)
continue;
if (P.getName().ToLower().Contains(Name.ToLower()))
return P;
}
return null;
}
//Another version of client from line, written for the line created by a kill or death event
public Player clientFromLineArr(String[] L, bool kill)
public Player clientFromEventLine(String[] L, int cIDPos)
{
if (L.Length < 7)
if (L.Length < cIDPos)
{
Log.Write("Line sent for client creation is not long enough!", Log.Level.Debug);
return null;
}
if (kill)
int pID = -1;
int.TryParse(L[cIDPos].Trim(), out pID);
if (pID < 0 || pID > 17)
{
foreach (Player P in players)
{
if (P == null)
continue;
if (P.getName().ToLower().Contains(L[8].Trim()))
return P;
}
String killerName = L[8].Trim();
String killerGUID = L[5].Trim();
int killerID = -1;
int.TryParse(L[6], out killerID);
Player newPlayer = new Player(killerName, killerGUID, killerID, 0);
addPlayer(newPlayer);
return players[newPlayer.getClientNum()];
Log.Write("Error event player index " + pID + " is out of bounds!", Log.Level.Debug);
Log.Write(String.Join(";", L), Log.Level.Debug);
return null;
}
else
{
foreach (Player P in players)
Player P = null;
try
{
P = players[pID];
return P;
}
catch (Exception)
{
Log.Write("Client index is invalid - " + pID, Log.Level.Debug);
Log.Write(L.ToString(), Log.Level.Debug);
return null;
}
}
}
public Player clientFromName(String pName)
{
lock (players)
{
foreach (var P in players)
{
if (P == null)
continue;
if (P.getName().ToLower().Contains(L[4].Trim()))
if (P != null && P.getName().ToLower().Contains(pName.ToLower()))
return P;
}
String victimName = L[4].Trim();
String victimGUID = L[1].Trim();
int victimID = -1;
int.TryParse(L[2].Trim(), out victimID);
Player newPlayer = new Player(victimName, victimGUID, victimID, 0);
addPlayer(newPlayer);
return players[newPlayer.getClientNum()];
}
return null;
}
//Check ban list for every banned player and return ban if match is found
@ -349,9 +386,6 @@ namespace IW4MAdmin
if (B.getIP() == null || C.getIP() == null)
continue;
if (C.Alias.getIPS().Find(f => f.Equals(B.getIP())) != null)
return B;
if (C.getIP() == B.getIP())
return B;
}
@ -409,7 +443,7 @@ namespace IW4MAdmin
}
else
E.Target = clientFromLine(Args[0]);
E.Target = clientFromName(Args[0]);
if (E.Target == null)
{
@ -528,7 +562,7 @@ namespace IW4MAdmin
if (lines[count] == oldLines[oldLines.Length - 1])
continue;
if (lines[count].Length < 10) //Not a needed line
if (lines[count].Length < 10) // its not a needed line
continue;
else
@ -574,8 +608,55 @@ namespace IW4MAdmin
int timesFailed = 0;
while (isRunning)
{
IPS = Utilities.IPFromStatus(RCON.addRCON("status"));
while (IPS == null)
lock (statusPlayers)
{
String[] Response = RCON.addRCON("status");
if (Response != null)
statusPlayers = Utilities.playersFromStatus(Response);
}
if (statusPlayers != null)
{
lastPoll = DateTime.Now;
timesFailed = 0;
if (statusPlayers.Count > clientnum)
{
lock (statusPlayers)
{
foreach (var P in statusPlayers.Values)
{
if (P == null)
continue;
if (!addPlayer(P))
Log.Write("Error adding " + P.getName() + " at client slot #" + P.getClientNum(), Log.Level.Debug);
}
}
}
else if (statusPlayers.Count < clientnum)
{
List<Player> toRemove = new List<Player>();
foreach (Player P in players)
{
if (P == null)
continue;
Player Matching;
statusPlayers.TryGetValue(P.getID(), out Matching);
if (Matching == null) // they are no longer with us
toRemove.Add(P);
}
foreach (Player Removing in toRemove) // cuz cant modify collections
removePlayer(Removing.getClientNum());
}
clientnum = statusPlayers.Count;
}
else
{
timesFailed++;
Log.Write("Server appears to be offline - " + timesFailed, Log.Level.Debug);
@ -584,32 +665,10 @@ namespace IW4MAdmin
{
Log.Write("Max offline attempts reached. Reinitializing RCON connection.", Log.Level.Debug);
RCON.Reset();
timesFailed = 0;
}
Utilities.Wait(10); // cut the time in half to make sure it isn't changing a map
IPS = Utilities.IPFromStatus(RCON.addRCON("status"));
}
Log.Write("Server responded to status query!", Log.Level.Debug);
timesFailed = 0;
foreach(Player P in players) //Ensures uniformity between server and admin players
{
if (P == null)
continue;
String IP = String.Empty;
IPS.TryGetValue(P.getID(), out IP);
if (IP == String.Empty)
{
Log.Write("Invalid player detected, quit event must have been skipped!", Log.Level.All);
removePlayer(P.getClientNum());
}
}
lastPoll = DateTime.Now;
Utilities.Wait(20); // don't want to overload the server
Utilities.Wait(15);
}
}
@ -654,7 +713,7 @@ namespace IW4MAdmin
try
{
Website = infoResponseDict["_Website"];
Website = infoResponseDict["_website"];
}
catch (Exception E)
{
@ -747,7 +806,7 @@ namespace IW4MAdmin
logPath = Basepath + '\\' + "m2demo" + '\\' + log;
else
logPath = Basepath + '\\' + Mod + '\\' + log;
//#if DEBUG == FALSE && System.Environment.GetEnvironmentVariable("COMPUTERNAME") != "michael-surface"
if (!File.Exists(logPath))
{
Log.Write("Gamelog does not exist!", Log.Level.All);
@ -755,6 +814,7 @@ namespace IW4MAdmin
}
logFile = new file(logPath);
//#endif
Log.Write("Log file is " + logPath, Log.Level.Debug);
//get players ip's
@ -765,7 +825,6 @@ namespace IW4MAdmin
return false;
}
IPS = Utilities.IPFromStatus(p);
lastPoll = DateTime.Now;
#if DEBUG
@ -774,6 +833,7 @@ namespace IW4MAdmin
System.IO.Stream ftpStream = tmp.GetResponse().GetResponseStream();
String ftpLog = new StreamReader(ftpStream).ReadToEnd();*/
//logPath = "games_old.log";
//logFile = new file("C:\\Users\\Michael\\text.txt");
#endif
return true;
}
@ -787,14 +847,13 @@ namespace IW4MAdmin
//Process any server event
public bool processEvent(Event E)
{
//
if (E.Type == Event.GType.Connect)
/*if (E.Type == Event.GType.Connect)
{
if (E.Origin == null)
Log.Write("Connect event triggered, but no client is detected!", Log.Level.Debug);
addPlayer(E.Origin);
return true;
}
}*/
if (E.Type == Event.GType.Disconnect)
{
@ -804,10 +863,7 @@ namespace IW4MAdmin
return false;
}
E.Origin.Connections++;
clientDB.updatePlayer(E.Origin);
removePlayer(E.Origin.getClientNum());
removePlayer(E.Origin.getClientNum());
return true;
}
@ -837,30 +893,27 @@ namespace IW4MAdmin
E.Target.stats = statDB.getStats(E.Target.getDBID());
}
Log.Write(E.Origin.getName() + " killed " + E.Target.getName() + " with a " + E.Data, Log.Level.Debug);
if (E.Origin != E.Target)
{
E.Origin.stats.Kills++;
E.Origin.stats.Kills += 1;
E.Origin.stats.updateKDR();
E.Origin.stats.lastMew = TrueSkill.calculateWinnerMu(E.Origin.stats, E.Target.stats);
E.Origin.stats.lastSigma = TrueSkill.calculateWinnerSigma(E.Origin.stats, E.Target.stats);
E.Origin.stats.updateSkill();
E.Target.stats.Deaths++;
E.Target.stats.Deaths += 1;
E.Target.stats.updateKDR();
E.Target.stats.lastMew = TrueSkill.calculateLoserMu(E.Target.stats, E.Origin.stats);
E.Target.stats.lastSigma = TrueSkill.calculateLoserSigma(E.Target.stats, E.Origin.stats);
Skills.updateNewSkill(E.Origin, E.Target);
E.Owner.statDB.updatePlayer(E.Origin);
E.Owner.statDB.updatePlayer(E.Target);
totalKills++;
Log.Write(E.Origin.getName() + " killed " + E.Target.getName() + " with a " + E.Data, Log.Level.Debug);
}
else //Suicide
{
E.Origin.stats.Deaths++;
E.Origin.stats.updateKDR();
Log.Write(E.Origin.getName() + " suicided...", Log.Level.Debug);
}
}
@ -902,6 +955,7 @@ namespace IW4MAdmin
C.Execute(E);
return true;
}
else
{
Log.Write("Player didn't properly enter command - " + E.Origin.getName(), Log.Level.Debug);
@ -917,7 +971,7 @@ namespace IW4MAdmin
if (E.Type == Event.GType.MapChange)
{
Log.Write("New map loaded", Log.Level.Debug);
Log.Write("New map loaded - " + clientnum + " active players", Log.Level.Debug);
String[] statusResponse = E.Data.Split('\\');
if (statusResponse.Length >= 15 && statusResponse[13] == "mapname")
mapname = maps.Find(m => m.Name.Equals(statusResponse[14])).Alias; //update map for heartbeat
@ -969,17 +1023,21 @@ namespace IW4MAdmin
public void Tell(String Message, Player Target)
{
RCON.addRCON("tell " + Target.getClientNum() + " " + Message + "^7");
if (Target.getClientNum() > -1)
RCON.addRCON("tell " + Target.getClientNum() + " " + Message + "^7");
}
public void Kick(String Message, Player Target)
{
RCON.addRCON("clientkick " + Target.getClientNum() + " \"" + Message + "^7\"");
if (Target.getClientNum() > -1)
RCON.addRCON("clientkick " + Target.getClientNum() + " \"" + Message + "^7\"");
}
public void Ban(String Message, Player Target, Player Origin)
{
RCON.addRCON("tempbanclient " + Target.getClientNum() + " \"" + Message + "^7\"");
if (Target.getClientNum() > -1)
RCON.addRCON("tempbanclient " + Target.getClientNum() + " \"" + Message + "^7\"");
if (Origin != null)
{
Target.setLevel(Player.Permission.Banned);
@ -987,6 +1045,21 @@ namespace IW4MAdmin
Bans.Add(newBan);
clientDB.addBan(newBan);
clientDB.updatePlayer(Target);
lock (Reports) // threading seems to do something weird here
{
List<Report> toRemove = new List<Report>();
foreach (Report R in Reports)
{
if (R.Target.getID() == Target.getID())
toRemove.Add(R);
}
foreach (Report R in toRemove)
{
Reports.Remove(R);
Log.Write("Removing report for banned GUID -- " + R.Origin.getID(), Log.Level.Debug);
}
}
}
}
@ -1043,20 +1116,27 @@ namespace IW4MAdmin
public void ToAdmins(String message)
{
List<Player> admins = players;
foreach (Player P in admins)
lock (players) // threading can modify list while we do this
{
if (P == null)
continue;
if (P.getLevel() > Player.Permission.User)
foreach (Player P in players)
{
RCON.addRCON("admin_lastevent alert;" + P.getID() + ";0;mp_killstreak_nuclearstrike");
P.Tell(message);
if (P == null)
continue;
if (P.getLevel() > Player.Permission.User)
{
P.Alert();
P.Tell(message);
}
}
}
}
public void Alert(Player P)
{
RCON.addRCON("admin_lastevent alert;" + P.getID() + ";0;mp_killstreak_nuclearstrike");
}
//END
//THIS IS BAD BECAUSE WE DON"T WANT EVERYONE TO HAVE ACCESS :/
@ -1068,6 +1148,7 @@ namespace IW4MAdmin
private void initMacros()
{
Macros = new Dictionary<String, Object>();
Macros.Add("WEBSITE", "nbsclan.org");
Macros.Add("WISDOM", Wisdom());
Macros.Add("TOTALPLAYERS", clientDB.totalPlayers());
Macros.Add("TOTALKILLS", totalKills);
@ -1185,7 +1266,11 @@ namespace IW4MAdmin
commands.Add(new Flag("flag", "flag a suspicious player and announce to admins on join . syntax !flag <player>:", "flag", Player.Permission.Moderator, 1, true));
commands.Add(new _Report("report", "report a player for suspicious behaivor. syntax !report <player> <reason>", "rep", Player.Permission.User, 2, true));
commands.Add(new Reports("reports", "get most recent reports. syntax !reports", "reports", Player.Permission.Moderator, 0, false));
commands.Add(new _Tell("tell", "send onscreen message to player. synrax !tell <player> <message>", "t", Player.Permission.Moderator, 2, true));
commands.Add(new _Tell("tell", "send onscreen message to player. syntax !tell <player> <message>", "t", Player.Permission.Moderator, 2, true));
commands.Add(new Mask("mask", "hide your online presence from online admin list. syntax: !mask", "mask", Player.Permission.Administrator, 0, false));
commands.Add(new BanInfo("baninfo", "get information about a ban for a player. syntax: !baninfo <player>", "bi", Player.Permission.Moderator, 1, true));
commands.Add(new Alias("alias", "get past aliases and ips of a player. syntax: !alias <player>", "known", Player.Permission.Moderator, 1, true));
commands.Add(new _RCON("rcon", "send rcon command to server. syntax: !rcon <command>", "rcon", Player.Permission.Owner, 1, false));
}
//Objects
@ -1221,10 +1306,12 @@ namespace IW4MAdmin
private String IW_Ver;
private int maxClients;
private Dictionary<String, Object> Macros;
private Moserware.TrueSkill Skills;
//Will probably move this later
private Dictionary<String, String> IPS;
//public Dictionary<String, String> IPS;
public Dictionary<String, Player> statusPlayers;
public bool isRunning;
private DateTime lastPoll;

View File

@ -1,55 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
using Moserware.Skills.TrueSkill;
using IW4MAdmin;
namespace IW4MAdmin
namespace Moserware
{
class TrueSkill
{
public static double calculateWinnerMu(Stats originStats, Stats targetStats)
{
double Beta = originStats.lastMew / 6;
double lastSkill = Gaussian(originStats.lastMew, originStats.lastSigma);
double c = Math.Sqrt((2 * Beta * Beta) + (originStats.lastSigma * originStats.lastSigma) + (targetStats.lastSigma * targetStats.lastSigma));
double newMew = originStats.lastMew + ((originStats.lastSigma) * (originStats.lastSigma) / c) * ((originStats.lastMew - targetStats.lastMew) / c);
return newMew;
}
public TrueSkill()
{
calculator = new TwoPlayerTrueSkillCalculator();
gInfo = Skills.GameInfo.DefaultGameInfo;
}
public static double calculateLoserMu(Stats originStats, Stats targetStats)
{
double Beta = originStats.lastMew / 6;
double lastSkill = Gaussian(originStats.lastMew, originStats.lastSigma);
double c = Math.Sqrt( (2 * Beta * Beta) + (originStats.lastSigma * originStats.lastSigma) + (targetStats.lastSigma * targetStats.lastSigma));
double newMew = originStats.lastMew - ((targetStats.lastSigma) * (targetStats.lastSigma) / c) * ((originStats.lastMew - targetStats.lastMew) / c);
return newMew;
}
public void updateNewSkill(Player P1, Player P2)
{
var player1 = new Skills.Player(P1.getDBID());
var player2 = new Skills.Player(P2.getDBID());
public static double calculateLoserSigma(Stats originStats, Stats targetStats)
{
double Beta = originStats.lastMew / 6;
double lastSkill = Gaussian(originStats.lastMew, originStats.lastSigma);
double c = ((2 * Beta * Beta) + originStats.lastSigma * originStats.lastSigma) + (targetStats.lastSigma * targetStats.lastSigma);
double newSigma = originStats.lastSigma * ( 1 - (targetStats.lastSigma) * (targetStats.lastSigma) / c) * ((originStats.lastMew - targetStats.lastMew) / c);
return newSigma;
}
var team1 = new Skills.Team(player1, P1.stats.Rating);
var team2 = new Skills.Team(player2, P2.stats.Rating);
public static double calculateWinnerSigma(Stats originStats, Stats targetStats)
{
double Beta = originStats.lastMew / 6;
double lastSkill = Gaussian(originStats.lastMew, originStats.lastSigma);
double c = ((2 * Beta * Beta) + originStats.lastSigma * originStats.lastSigma) + (targetStats.lastSigma * targetStats.lastSigma);
double newSigma = originStats.lastSigma * (1 - (originStats.lastSigma) * (originStats.lastSigma) / c) * ((originStats.lastMew - targetStats.lastMew) / c);
return newSigma;
}
var newRatings = calculator.CalculateNewRatings(gInfo, Skills.Teams.Concat(team1, team2), 1, 2);
P1.stats.Rating = newRatings[player1];
P2.stats.Rating = newRatings[player2];
//https://gist.github.com/tansey/1444070
public static double Gaussian( double mean, double stddev)
{
double y1 = Math.Sqrt(-2.0 * Math.Log(.5)) * Math.Cos(2.0 * Math.PI * .5);
return y1 * stddev + mean;
}
P1.stats.Skill = Math.Round(P1.stats.Rating.ConservativeRating, 3)*10;
P2.stats.Skill = Math.Round(P2.stats.Rating.ConservativeRating, 3)*10;
}
private Skills.SkillCalculator calculator;
public Skills.GameInfo gInfo;
}
}
}

View File

@ -94,9 +94,9 @@ namespace IW4MAdmin
case Player.Permission.Owner:
return "^5" + Player.Permission.Owner;
case Player.Permission.User:
return "^3" + Player.Permission.User;
return "^2" + Player.Permission.User;
default:
return "^2" + level;
return "^3" + level;
}
}
@ -144,12 +144,135 @@ namespace IW4MAdmin
}
public static String nameHTMLFormatted(Player P)
{
switch (P.getLevel())
{
case Player.Permission.User:
return "<span style='color:rgb(121, 194, 97)'>" + P.getName() + "</span>";
case Player.Permission.Moderator:
return "<span style='color:#e7b402'>" + P.getName() + "</span>";
case Player.Permission.Administrator:
return "<span style='color:#ec82de'>" + P.getName() + "</span>";
case Player.Permission.SeniorAdmin:
return "<span style='color:#2eb6bf'>" + P.getName() + "</span>";
case Player.Permission.Owner | Player.Permission.Creator:
return "<span style='color:rgb(38,120,230)'>" + P.getName() + "</span>";
default:
return "<i>" + P.getName() + "</i>";
}
}
public static String gametypeLocalized(String input)
{
switch (input)
{
case "dm":
return "Deathmatch";
case "war":
return "Team Deathmatch";
case "koth":
return "Headquarters";
case "ctf":
return "Capture The Flag";
case "dd":
return "Demolition";
case "dom":
return "Domination";
case "sab":
return "Sabotage";
case "sd":
return "Search & Destroy";
case "vip":
return "Very Important Person";
case "gtnw":
return "Global Thermonuclear War";
case "oitc":
return "One In The Chamber";
case "arena":
return "Arena";
case "dzone":
return "Drop Zone";
case "gg":
return "Gun Game";
case "snipe":
return "Sniping";
case "ss":
return "Sharp Shooter";
case "m40a3":
return "M40A3";
case "fo":
return "Face Off";
case "dmc":
return "Deathmatch Classic";
case "killcon":
return "Kill Confirmed";
case "oneflag":
return "One Flag CTF";
default:
return "Unknown";
}
}
public static Dictionary<String, Player> playersFromStatus(String[] Status)
{
Dictionary<String, Player> playerDictionary = new Dictionary<String, Player>();
if (Status == null) // looks like we didn't get a proper response
return null;
foreach (String S in Status)
{
String responseLine = S.Trim();
if (Regex.Matches(responseLine, @"\d+$", RegexOptions.IgnoreCase).Count > 0 && responseLine.Length > 72) // its a client line!
{
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
String cName = stripColors(responseLine.Substring(46, 18)).Trim();
String npID = responseLine.Substring(29, 17).Trim(); // DONT TOUCH PLZ
int cID = Convert.ToInt32(playerInfo[0]);
String cIP = responseLine.Substring(72,20).Trim().Split(':')[0];
Player P = new Player(cName, npID, cID, cIP);
try
{
playerDictionary.Add(npID, P);
}
catch(Exception E)
{
/// need to handle eventually
Console.WriteLine("Error handling player add -- " + E.Message);
continue;
}
}
}
return playerDictionary;
}
public static String DateTimeSQLite(DateTime datetime)
{
string dateTimeFormat = "{0}-{1}-{2} {3}:{4}:{5}.{6}";
return string.Format(dateTimeFormat, datetime.Year, datetime.Month, datetime.Day, datetime.Hour, datetime.Minute, datetime.Second, datetime.Millisecond);
}
public static String timePassed(DateTime start)
{
TimeSpan Elapsed = DateTime.Now - start;
if (Elapsed.Hours < 1 && Elapsed.Minutes < 60)
return Elapsed.Minutes + " minutes";
if (Elapsed.Days < 1 && Elapsed.Hours <= 24)
return Elapsed.Hours + " hours";
if (Elapsed.Days <= 365)
return Elapsed.Days + " days";
else
return "a very long time";
}
public static String timesConnected(int connection)
{
String Prefix = String.Empty;
@ -168,8 +291,7 @@ namespace IW4MAdmin
case 3:
Prefix = "rd";
break;
}
}
}
switch (connection)

394
Admin/WebFront.cs Normal file
View File

@ -0,0 +1,394 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kayak;
using Kayak.Http;
using System.Net;
namespace IW4MAdmin_Web
{
class WebFront
{
public enum Page
{
main,
stats,
bans
}
public WebFront()
{
}
public void Init()
{
webSchedule = KayakScheduler.Factory.Create(new SchedulerDelegate());
webServer = KayakServer.Factory.CreateHttp(new RequestDelegate(), webSchedule);
using (webServer.Listen(new IPEndPoint(IPAddress.Any, 1624)))
{
// runs scheduler on calling thread. this method will block until
// someone calls Stop() on the scheduler.
webSchedule.Start();
}
}
private IScheduler webSchedule;
private IServer webServer;
}
static class Macro
{
static public String parsePagination(int server, int totalItems, int itemsPerPage, int currentPage, String Page)
{
StringBuilder output = new StringBuilder();
output.Append("<div id=pages>");
if ( currentPage > 0)
output.AppendFormat("<a href=/{0}/{1}/?{2}>PREV</a>", server, currentPage - 1, Page);
double totalPages = Math.Ceiling(((float)totalItems / itemsPerPage));
output.Append("<span id=pagination>" + (currentPage + 1) + "/" + totalPages + "</span>");
if ((currentPage + 1) < totalPages)
output.AppendFormat("<a href=/{0}/{1}/?{2}>NEXT</a>", server, currentPage + 1, Page);
output.Append("</div>");
return output.ToString();
}
static public String parseMacros(String input, WebFront.Page Page, int Pagination, int server)
{
StringBuilder buffer = new StringBuilder();
switch (input)
{
case "SERVERS":
var Servers = IW4MAdmin.Program.Servers;
for (int i = 0; i < Servers.Count; i++)
{
StringBuilder players = new StringBuilder();
if (Servers[i].getClientNum() < 1)
players.Append("<th>No Players</th>");
else
{
int count = 0;
foreach (IW4MAdmin.Player P in Servers[i].statusPlayers.Values)
{
if (count > 0 && count % 6 == 0)
players.Append("</tr><tr>");
players.AppendFormat("<td>{0}</td>", P.getName());
count++;
}
}
buffer.AppendFormat(@"<table cellpadding=0 cellspacing=0 class=server>
<tr>
<th class=server_title><span>{0}</span></th>
<th class=server_map><span>{1}</span></th>
<th class=server_players><span>{2}</span></th>
<th class=server_gametype><span>{3}</span></th>
<th><a href=/{4}/0/?stats>Stats</a></th>
<th><a href=/{4}/0/?bans>Bans</a></th>
</tr>
</table>
<table class=players>
<tr>
{5}
</tr>
</table>
<hr/>",
Servers[i].getName(), Servers[i].getMap(), Servers[i].getClientNum() + "/" + Servers[i].getMaxClients(), IW4MAdmin.Utilities.gametypeLocalized(Servers[i].getGametype()), i, players.ToString());
}
return buffer.ToString();
case "TITLE":
return "IW4M Administration";
case "BANS":
buffer.Append("<table cellspacing=0 class=bans>");
int range;
int start = Pagination*30 + 1;
if (IW4MAdmin.Program.Servers[0].Bans.Count <= 30)
range = IW4MAdmin.Program.Servers[0].Bans.Count - 1;
else if ((IW4MAdmin.Program.Servers[0].Bans.Count - start) < 30 )
range = (IW4MAdmin.Program.Servers[0].Bans.Count - start);
else
range = 30;
List<IW4MAdmin.Ban> Bans = IW4MAdmin.Program.Servers[0].Bans.GetRange(start, range);
buffer.Append("<h1 style=margin-top: 0;>{{TIME}}</h1><hr /><tr><th>Name</th><th style=text-align:left;>Offense</th><th style=text-align:left;>Banned By</th><th style='width: 175px; text-align:right;padding-right: 80px;'>Time</th></tr>");
if (Bans[0] != null)
buffer = buffer.Replace("{{TIME}}", "From " + IW4MAdmin.Utilities.timePassed(Bans[0].getTime()) + " ago" + " &mdash; " + IW4MAdmin.Program.Servers[0].Bans.Count + " total");
int cycleFix = 0;
for (int i = 0; i < Bans.Count; i++)
{
if (Bans[i] == null)
continue;
IW4MAdmin.Player P = IW4MAdmin.Program.Servers[0].clientDB.getPlayer(Bans[i].getID(), -1);
IW4MAdmin.Player B = IW4MAdmin.Program.Servers[0].clientDB.getPlayer(Bans[i].getBanner(), -1);
if (P == null)
P = new IW4MAdmin.Player("Unknown", "n/a", 0, 0, 0, "Unknown", 0, "");
if (B == null)
B = new IW4MAdmin.Player("Unknown", "n/a", 0, 0, 0, "Unknown", 0, "");
if (P.getLastO() == String.Empty)
P.LastOffense = "Evade";
if (P != null && B != null)
{
if (B.getID() == P.getID())
B.updateName("IW4MAdmin"); // shh it will all be over soon
String Prefix;
if (cycleFix % 2 == 0)
Prefix = "class=row-grey";
else
Prefix = "class=row-white";
buffer.AppendFormat("<tr {4}><td>{0}</th><td style='border-left: 3px solid #bbb; text-align:left;'>{1}</td><td style='border-left: 3px solid #bbb;text-align:left;'>{2}</td><td style='width: 175px; text-align:right;'>{3}</td></tr></div>", P.getName(), P.getLastO(), IW4MAdmin.Utilities.nameHTMLFormatted(B), Bans[i].getWhen(), Prefix);
cycleFix++;
}
}
buffer.Append("</table><hr/>");
buffer.Append(parsePagination(server, IW4MAdmin.Program.Servers[0].Bans.Count, 30, Pagination, "bans"));
return buffer.ToString();
case "PAGE":
buffer.Append("<div id=pages>");
return buffer.ToString();
case "STATS":
int totalStats = IW4MAdmin.Program.Servers[server].statDB.totalStats()-1;
buffer.Append("<h1 style='margin-top: 0;'>Starting at #{{TOP}}</h1><hr />");
buffer.Append("<table style='width:100%' cellspacing=0 class=stats>");
start = Pagination*30 + 1;
if (totalStats <= 30)
range = totalStats - 1;
else if ((totalStats - start) < 30 )
range = (totalStats - start);
else
range = 30;
List<IW4MAdmin.Stats> Stats = IW4MAdmin.Program.Servers[server].statDB.getMultipleStats(start, range);
buffer.Append("<tr><th style=text-align:left;>Name</th><th style=text-align:left;>Kills</th><th style=text-align:left;>Deaths</th><th style=text-align:left;>KDR</th><th style='width: 175px; text-align:right;'>Rating</th></tr>");
cycleFix = 0;
for (int i = 0; i < totalStats; i++)
{
if (i >= Stats.Count -1 || Stats[i] == null )
continue;
IW4MAdmin.Player P = IW4MAdmin.Program.Servers[server].clientDB.getPlayer(Stats[i].statIndex);
if (P == null)
continue;
P.stats = Stats[i];
if (P.stats != null)
{
String Prefix;
if (cycleFix % 2 == 0)
Prefix = "class=row-grey";
else
Prefix = "class=row-white";
buffer.AppendFormat("<tr {5}><td>{0}</th><td style='border-left: 3px solid #bbb; text-align:left;'>{1}</td><td style='border-left: 3px solid #bbb;text-align:left;'>{2}</td><td style='border-left: 3px solid #bbb;text-align:left;'>{3}</td><td style='width: 175px; text-align:right;'>{4}</td></tr></div>", P.getName(), P.stats.Kills, P.stats.Deaths, P.stats.KDR, P.stats.Skill, Prefix);
cycleFix++;
}
}
buffer.Append("</table><hr/>");
buffer.Append(parsePagination(server, totalStats, 30, Pagination, "stats"));
return buffer.ToString().Replace("{{TOP}}", (start).ToString());
default:
return input;
}
}
static public String findMacros(String input, int pageNumber, int server, WebFront.Page page)
{
String output = input;
switch (page)
{
case WebFront.Page.main:
output = output.Replace("{{SERVERS}}", parseMacros("SERVERS", page, pageNumber, server));
break;
case WebFront.Page.bans:
output = output.Replace("{{BANS}}", parseMacros("BANS", page, pageNumber, server));
break;
case WebFront.Page.stats:
output = output.Replace("{{STATS}}", parseMacros("STATS", page, pageNumber, server));
break;
}
//output = output.Replace("{{PAGE}}", parseMacros("PAGE", page, pageNumber, server));
//output = output.Replace("{{SERVERS}}", parseMacros("SERVERS", 0));
//output = output.Replace("{{BANS}}", parseMacros("BANS", page));
output = output.Replace("{{TITLE}}", "IW4M Administration");
//output = output.Replace("{{PAGE}}", parseMacros("PAGE", page));
//output = output.Replace("{{STATS}}", parseMacros("STATS", page));
return output;
}
}
class SchedulerDelegate : ISchedulerDelegate
{
public void OnException(IScheduler scheduler, Exception e)
{
Console.WriteLine(e.InnerException.Message);
Console.Write(e.InnerException);
e.DebugStackTrace();
}
public void OnStop(IScheduler scheduler)
{
}
}
class RequestDelegate : IHttpRequestDelegate
{
public void OnRequest(HttpRequestHead request, IDataProducer requestBody, IHttpResponseDelegate response)
{
if (request.Uri.StartsWith("/"))
{
Console.WriteLine("[WEBFRONT] Processing Request for " + request.Uri);
var body = String.Empty;
if (request.Uri.StartsWith("/"))
{
IW4MAdmin.file Header = new IW4MAdmin.file("webfront\\header.html");
var header = Header.getLines();
Header.Close();
String[] req = request.Path.Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
int server = 0;
int page = 0;
if (req.Length > 1)
{
Int32.TryParse(req[0], out server);
Int32.TryParse(req[1], out page);
}
if (request.QueryString == "bans")
{
IW4MAdmin.file Bans = new IW4MAdmin.file("webfront\\bans.html");
var bans = Bans.getLines();
Bans.Close();
body = Macro.findMacros((header + bans), page, server, WebFront.Page.bans);
}
else if (request.QueryString == "stats")
{
IW4MAdmin.file Stats = new IW4MAdmin.file("webfront\\stats.html");
var stats = Stats.getLines();
Stats.Close();
body = Macro.findMacros(header + stats, page, server, WebFront.Page.stats);
}
else
{
IW4MAdmin.file Main = new IW4MAdmin.file("webfront\\main.html");
var main = Main.getLines();
Main.Close();
body = Macro.findMacros(header + main, 0, server, WebFront.Page.main);
}
}
/*var body = string.Format(
"Uri: {0}\r\nPath: {1}\r\nQuery:{2}\r\nFragment: {3}\r\n",
request.Uri,
request.Path,
request.QueryString,
request.Fragment);*/
var headers = new HttpResponseHead()
{
Status = "200 OK",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/html" },
{ "Content-Length", body.Length.ToString() },
}
};
response.OnResponse(headers, new BufferedProducer(body));
}
else
{
var responseBody = "The resource you requested ('" + request.Uri + "') could not be found.";
var headers = new HttpResponseHead()
{
Status = "404 Not Found",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/text" },
{ "Content-Length", responseBody.Length.ToString() }
}
};
var body = new BufferedProducer(responseBody);
response.OnResponse(headers, body);
}
}
class BufferedProducer : IDataProducer
{
ArraySegment<byte> data;
public BufferedProducer(string data) : this(data, Encoding.UTF8) { }
public BufferedProducer(string data, Encoding encoding) : this(encoding.GetBytes(data)) { }
public BufferedProducer(byte[] data) : this(new ArraySegment<byte>(data)) { }
public BufferedProducer(ArraySegment<byte> data)
{
this.data = data;
}
public IDisposable Connect(IDataConsumer channel)
{
// null continuation, consumer must swallow the data immediately.
channel.OnData(data, null);
channel.OnEnd();
return null;
}
}
class BufferedConsumer : IDataConsumer
{
List<ArraySegment<byte>> buffer = new List<ArraySegment<byte>>();
Action<string> resultCallback;
Action<Exception> errorCallback;
public BufferedConsumer(Action<string> resultCallback, Action<Exception> errorCallback)
{
this.resultCallback = resultCallback;
this.errorCallback = errorCallback;
}
public bool OnData(ArraySegment<byte> data, Action continuation)
{
// since we're just buffering, ignore the continuation.
buffer.Add(data);
return false;
}
public void OnError(Exception error)
{
errorCallback(error);
}
public void OnEnd()
{
// turn the buffer into a string.
var str = buffer
.Select(b => Encoding.UTF8.GetString(b.Array, b.Offset, b.Count))
.Aggregate((result, next) => result + next);
resultCallback(str);
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
Admin/packages.config Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Kayak" version="0.7.2" targetFramework="net40" />
</packages>

View File

@ -1,4 +1,21 @@
VERSION: 0.7
VERSION: 0.8
CHANGELOG:
-rcon tweaks
-so much stuff cant remember
-added mask command
-added baninfo command
-added alias command and removed redundant output from `find`
-added rcon command
-added webfront (http://127.0.0.1:1624)
-true skill is officially implemented
-find now shows last connect time
-noise on pm (if gsc_enabled)
-force 8 line chat height (if gsc_enabled)
-tell admins the number of reports on join
-enhanced ban tracking
-ip wait timeout added
-remove report on ban
-can't report yourself
-remove reported players when banned
-fixed rare crash with toadmins backend
-fixed crash when finding player stats that don't exist
-fixed a bug that caused owner command to reactivate only `creator` rank player existed
-fixed a bug that caused certain notifications to be sent to all players

11
Admin/webfront/bans.html Normal file
View File

@ -0,0 +1,11 @@
<body>
<div id="container">
<div class="h0" style="margin-top: 0; line-height:normal;">BANS<br/><a style="padding: 0; margin: 0; font-size: 24px; float: right;" href="/">Back</a></div>
<div id="logo_shit"></div>
{{BANS}}
</div>
<div id="footer">IW4M Admin &mdash; <a href="http://raidmax.org/IW4MAdmin">RaidMax.org</a></div>
</body>
</html>

277
Admin/webfront/header.html Normal file
View File

@ -0,0 +1,277 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>{{TITLE}}</title>
<style>
* {
font-family: 'Robot', sans-serif;
margin: 0;
}
html, body {
width: 100%;
height: 100%;
background-color: #171717;
}
#container {
width: 1100px;
background-color: #fff;
margin: 0 auto;
padding: 30px;
}
.h0 {
font-size: 40pt;
text-align: center;
margin-bottom: 0px;
float: right;
line-height: 100px;
}
h1 {
margin-top: 20px;
clear: both;
}
#header_img {
width: 100px;
height: 96px;
float: right;
background-image: url("http://23.251.150.125:8000/static/images/logo.png");
background-size: 100px 96px;
background-repeat: no-repeat;
}
#logo_shit
{
width: 100px;
height: 96px;
float: left;
background-image: url("http://23.251.150.125:8000/static/images/logo.png");
background-size: 100px 96px;
background-repeat: no-repeat;
}
p {
margin-top: 10px;
margin-bottom: 10px;
}
ul {
padding: 0;
margin: 0;
display: table;
width: 100%;
}
ul.tablehead li {
display: table-cell;
list-style-type: none;
font-size: 18pt;
margin: 0;
padding-top: 10px;
padding-bottom: 10px;
}
ul.row li {
overflow: hidden;
display: table-cell;
list-style-type: none;
font-size: 12pt;
}
li {
}
td{
padding: 8px;
overflow: hidden;
white-space: nowrap;
}
tr.row-white {
background-color: #fff;
}
tr.row-grey {
background-color: #eee;
}
th
{
font-size: 16pt;
}
li.row-green {
background-color: rgba(121, 194, 97, .3);
padding: 10px 0px 10px 0px;
width: 70px;
text-align: center;
}
li.row-red {
background-color: rgba(196, 22, 28, .3);
padding: 10px 0px 10px 0px;
width: 70px;
text-align: center;
}
input[type="submit"] {
border: none;
border-radius: 4px;
color: #fff;
font-size: 14pt;
width: 250px;
height: 40px;
background-color: rgb(121, 194, 97);
}
input[type="submit"]:hover {
background-color: #fff;
color: #171717;
border: 1px solid #171717;
}
.question_title {
color: #171717;
font-size: 16pt;
font-weight: bold;
margin-top: 10px;
}
.question_answer {
background-color: rgb(121, 194, 97);
color: #fff;
padding: 5px;
border-radius: 4px;
}
.question_answer a:hover {
color: cyan;
}
ol, ol li {
margin-left: 0;
padding-left: 30px;
}
a:link, a:visited {
text-decoration: none;
color: rgb(38,120,230);
}
a:hover {
color: #171717;
}
.BigList {
font-size: 12pt;
opacity: 0.5;
}
.separator {
position: absolute;
width: 3px;
height: 40px;
background-color: #ccc;
left: 25%;
right: 75%;
}
.asterik {
font-size: 11pt;
color: #171717;
font-style: italic;
}
#commands {
margin: 0 auto;
}
.block {
margin-top: 10px;
margin-bottom: 10px;
}
hr {
background-color: rgb(38,120,230);
border: none;
width: 100%;
height: 5px;
margin-bottom: 5px;
margin-top: 5px;
}
.server {
width: 100%;
text-align: left;
}
.server_title {
font-size: 24pt;
margin-bottom: 20px;
min-width: 550px;
}
.server_info {
font-size: 14pt;
}
.server_map {
min-width: 140px;
}
.server_players {
min-width: 50px;
}
.server_gametype {
min-width: 175px;
}
.players {
width: 60%;
text-align: left;
}
.bans {
text-align: left;
width: 100%;
}
.bans th
{
font-size: 20pt;
}
#pages{
font-size: 14pt;
text-align:center;
}
#pages a {
margin: 10px;
}
#pagination{
}
#footer{
background-color: #fff;
padding-top: 5px;
padding-bottom: 10px;
text-align: center;
width: 1160px;
margin: 0 auto;
border-radius: 0px 0px 11px 11px;
}
.players tbody tr td
{
padding: 1px;
}
</style>
</head>

12
Admin/webfront/main.html Normal file
View File

@ -0,0 +1,12 @@
<body>
<div id="container">
<div class="h0" style="margin-top: 0">IW4M Admin</div><div id="header_img"></div>
<h1 style="margin-top: 0;">Currently Monitoring</h1>
<hr />
{{SERVERS}}
</div>
<div id="footer">IW4M Admin &mdash; <a href="http://raidmax.org/IW4MAdmin">RaidMax.org</a></div>
</body>
</html>

10
Admin/webfront/stats.html Normal file
View File

@ -0,0 +1,10 @@
<body>
<div id="container">
<div class="h0" style="margin-top: 0; line-height:normal;">STATS<br /><a style="padding: 0; margin: 0; font-size: 24px; float: right;" href="/">Back</a></div>
<div id="logo_shit"></div>
{{STATS}}
</div>
<div id="footer">IW4M Admin &mdash; <a href="http://raidmax.org/IW4MAdmin">RaidMax.org</a></div>
</body>
</html>