/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; using System.Collections.Generic; using System.Data; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Xml; using MySql.Data.MySqlClient; using Terraria; namespace TShockAPI.DB { public class RegionManager { public List Regions = new List(); private IDbConnection database; public RegionManager(IDbConnection db) { database = db; var table = new SqlTable("Regions", new SqlColumn("X1", MySqlDbType.Int32), new SqlColumn("Y1", MySqlDbType.Int32), new SqlColumn("width", MySqlDbType.Int32), new SqlColumn("height", MySqlDbType.Int32), new SqlColumn("RegionName", MySqlDbType.VarChar, 50) { Primary = true }, new SqlColumn("WorldID", MySqlDbType.Text), new SqlColumn("UserIds", MySqlDbType.Text), new SqlColumn("Protected", MySqlDbType.Int32), new SqlColumn("Groups", MySqlDbType.Text), new SqlColumn("Owner", MySqlDbType.VarChar, 50) ); var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder)new SqliteQueryCreator() : new MysqlQueryCreator()); creator.EnsureExists(table); ImportOld(); ReloadAllRegions(); } [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public void ImportOld() { String file = Path.Combine(TShock.SavePath, "regions.xml"); if (!File.Exists(file)) return; Region region; Rectangle rect; using (var reader = XmlReader.Create(new StreamReader(file), new XmlReaderSettings { CloseInput = true })) { // Parse the file and display each of the nodes. while (reader.Read()) { if (reader.NodeType != XmlNodeType.Element || reader.Name != "ProtectedRegion") continue; region = new Region(); rect = new Rectangle(); bool endregion = false; while (reader.Read() && !endregion) { if (reader.NodeType != XmlNodeType.Element) continue; string name = reader.Name; while (reader.Read() && reader.NodeType != XmlNodeType.Text) ; int t = 0; switch (name) { case "RegionName": region.Name = reader.Value; break; case "Point1X": int.TryParse(reader.Value, out t); rect.X = t; break; case "Point1Y": int.TryParse(reader.Value, out t); rect.Y = t; break; case "Point2X": int.TryParse(reader.Value, out t); rect.Width = t; break; case "Point2Y": int.TryParse(reader.Value, out t); rect.Height = t; break; case "Protected": region.DisableBuild = reader.Value.ToLower().Equals("true"); break; case "WorldName": region.WorldID = reader.Value; break; case "AllowedUserCount": break; case "IP": region.AllowedIDs.Add(int.Parse(reader.Value)); break; default: endregion = true; break; } } region.Area = rect; string query = (TShock.Config.StorageType.ToLower() == "sqlite") ? "INSERT OR IGNORE INTO Regions VALUES (@0, @1, @2, @3, @4, @5, @6, @7);" : "INSERT IGNORE INTO Regions SET X1=@0, Y1=@1, height=@2, width=@3, RegionName=@4, WorldID=@5, UserIds=@6, Protected=@7;"; database.Query(query, region.Area.X, region.Area.Y, region.Area.Width, region.Area.Height, region.Name, region.WorldID, "", region.DisableBuild); //Todo: What should this be? We don't really have a way to go from ips to userids /*string.Join(",", region.AllowedIDs)*/ } } String path = Path.Combine(TShock.SavePath, "old_configs"); String file2 = Path.Combine(path, "regions.xml"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); if (File.Exists(file2)) File.Delete(file2); File.Move(file, file2); ReloadAllRegions(); } public void ConvertDB() { try { database.Query("UPDATE Regions SET WorldID=@0, UserIds=''", Main.worldID.ToString()); ReloadAllRegions(); } catch (Exception ex) { Log.Error(ex.ToString()); } } public void ReloadAllRegions() { try { using (var reader = database.QueryReader("SELECT * FROM Regions WHERE WorldID=@0", Main.worldID.ToString())) { Regions.Clear(); while (reader.Read()) { int X1 = reader.Get("X1"); int Y1 = reader.Get("Y1"); int height = reader.Get("height"); int width = reader.Get("width"); int Protected = reader.Get("Protected"); string mergedids = reader.Get("UserIds"); string name = reader.Get("RegionName"); string owner = reader.Get("Owner"); string groups = reader.Get("Groups"); string[] splitids = mergedids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); Region r = new Region(new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString()); r.SetAllowedGroups( groups ); try { for (int i = 0; i < splitids.Length; i++) { int id; if (Int32.TryParse(splitids[i], out id)) // if unparsable, it's not an int, so silently skip r.AllowedIDs.Add(id); else Log.Warn("One of your UserIDs is not a usable integer: " + splitids[i]); } } catch (Exception e) { Log.Error("Your database contains invalid UserIDs (they should be ints)."); Log.Error("A lot of things will fail because of this. You must manually delete and re-create the allowed field."); Log.Error(e.ToString()); Log.Error(e.StackTrace); } Regions.Add(r); } } } catch (Exception ex) { Log.Error(ex.ToString()); } } public void ReloadForUnitTest(String n) { using (var reader = database.QueryReader("SELECT * FROM Regions WHERE WorldID=@0", n)) { Regions.Clear(); while (reader.Read()) { int X1 = reader.Get("X1"); int Y1 = reader.Get("Y1"); int height = reader.Get("height"); int width = reader.Get("width"); int Protected = reader.Get("Protected"); string MergedIDs = reader.Get("UserIds"); string name = reader.Get("RegionName"); string[] SplitIDs = MergedIDs.Split(','); string owner = reader.Get("Owner"); string groups = reader.Get("Groups"); Region r = new Region(new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString()); r.SetAllowedGroups( groups ); try { for (int i = 0; i < SplitIDs.Length; i++) { int id; if (Int32.TryParse(SplitIDs[i], out id)) // if unparsable, it's not an int, so silently skip r.AllowedIDs.Add(id); else if (SplitIDs[i] == "") // Split gotcha, can return an empty string with certain conditions // but we only want to let the user know if it's really a nonparsable integer. Log.Warn("UnitTest: One of your UserIDs is not a usable integer: " + SplitIDs[i]); } } catch (Exception e) { Log.Error("Your database contains invalid UserIDs (they should be ints)."); Log.Error("A lot of things will fail because of this. You must manually delete and re-create the allowed field."); Log.Error(e.Message); Log.Error(e.StackTrace); } Regions.Add(r); } } } public bool AddRegion(int tx, int ty, int width, int height, string regionname, string owner, string worldid) { if (GetRegionByName(regionname) != null) { return false; } try { database.Query("INSERT INTO Regions (X1, Y1, width, height, RegionName, WorldID, UserIds, Protected, Groups, Owner) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9);", tx, ty, width, height, regionname, worldid, "", 1, "", owner); Regions.Add(new Region(new Rectangle(tx, ty, width, height), regionname, owner, true, worldid)); return true; } catch (Exception ex) { Log.Error(ex.ToString()); } return false; } public bool DeleteRegion(string name) { try { database.Query("DELETE FROM Regions WHERE RegionName=@0 AND WorldID=@1", name, Main.worldID.ToString()); var worldid = Main.worldID.ToString(); Regions.RemoveAll(r => r.Name == name && r.WorldID == worldid); return true; } catch (Exception ex) { Log.Error(ex.ToString()); } return false; } public bool SetRegionState(string name, bool state) { try { database.Query("UPDATE Regions SET Protected=@0 WHERE RegionName=@1 AND WorldID=@2", state ? 1 : 0, name, Main.worldID.ToString()); var region = GetRegionByName(name); if (region != null) region.DisableBuild = state; return true; } catch (Exception ex) { Log.Error(ex.ToString()); return false; } } public bool SetRegionStateTest(string name, string world, bool state) { try { database.Query("UPDATE Regions SET Protected=@0 WHERE RegionName=@1 AND WorldID=@2", state ? 1 : 0, name, world); var region = GetRegionByName(name); if (region != null) region.DisableBuild = state; return true; } catch (Exception ex) { Log.Error(ex.ToString()); return false; } } public bool CanBuild(int x, int y, TSPlayer ply) { if (!ply.Group.HasPermission(Permissions.canbuild)) { return false; } for (int i = 0; i < Regions.Count; i++) { if (Regions[i].InArea(new Rectangle(x, y, 0, 0)) && !Regions[i].HasPermissionToBuildInRegion(ply)) { return false; } } return true; } public bool InArea(int x, int y) { foreach (Region region in Regions) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && region.DisableBuild) { return true; } } return false; } public string InAreaRegionName(int x, int y) { foreach (Region region in Regions) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && region.DisableBuild) { return region.Name; } } return null; } public static List ListIDs(string MergedIDs) { return MergedIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); } public bool resizeRegion(string regionName, int addAmount, int direction) { //0 = up //1 = right //2 = down //3 = left int X = 0; int Y = 0; int height = 0; int width = 0; try { using (var reader = database.QueryReader("SELECT X1, Y1, height, width FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) X = reader.Get("X1"); width = reader.Get("width"); Y = reader.Get("Y1"); height = reader.Get("height"); } if (!(direction == 0)) { if (!(direction == 1)) { if (!(direction == 2)) { if (!(direction == 3)) { return false; } else { X -= addAmount; width += addAmount; } } else { height += addAmount; } } else { width += addAmount; } } else { Y -= addAmount; height += addAmount; } int q = database.Query("UPDATE Regions SET X1 = @0, Y1 = @1, width = @2, height = @3 WHERE RegionName = @4 AND WorldID=@5", X, Y, width, height, regionName, Main.worldID.ToString()); if (q > 0) return true; } catch (Exception ex) { Log.Error(ex.ToString()); } return false; } public bool RemoveUser(string regionName, string userName) { Region r = GetRegionByName(regionName); if (r != null) { r.RemoveID(TShock.Users.GetUserID(userName)); string ids = string.Join(",", r.AllowedIDs); int q = database.Query("UPDATE Regions SET UserIds=@0 WHERE RegionName=@1 AND WorldID=@2", ids, regionName, Main.worldID.ToString()); if (q > 0) return true; } return false; } public bool AddNewUser(string regionName, String userName) { try { string MergedIDs = string.Empty; using (var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) MergedIDs = reader.Get("UserIds"); } if (string.IsNullOrEmpty(MergedIDs)) MergedIDs = Convert.ToString(TShock.Users.GetUserID(userName)); else MergedIDs = MergedIDs + "," + Convert.ToString(TShock.Users.GetUserID(userName)); int q = database.Query("UPDATE Regions SET UserIds=@0 WHERE RegionName=@1 AND WorldID=@2", MergedIDs, regionName, Main.worldID.ToString()); foreach (var r in Regions) { if (r.Name == regionName && r.WorldID == Main.worldID.ToString()) r.setAllowedIDs(MergedIDs); } return q != 0; } catch (Exception ex) { Log.Error(ex.ToString()); } return false; } /// /// Gets all the regions names from world /// /// World name to get regions from /// List of regions with only their names public List ListAllRegions(string worldid) { var regions = new List(); try { using (var reader = database.QueryReader("SELECT RegionName FROM Regions WHERE WorldID=@0", worldid)) { while (reader.Read()) regions.Add(new Region { Name = reader.Get("RegionName") }); } } catch (Exception ex) { Log.Error(ex.ToString()); } return regions; } public Region GetRegionByName(String name) { return Regions.FirstOrDefault(r => r.Name.Equals(name) && r.WorldID == Main.worldID.ToString()); } public Region ZacksGetRegionByName(String name) { foreach (Region r in Regions) { if (r.Name.Equals(name)) return r; } return null; } public bool ChangeOwner( string regionName, string newOwner ) { var region = GetRegionByName(regionName); if( region != null ) { region.Owner = newOwner; int q = database.Query("UPDATE Regions SET Owner=@0 WHERE RegionName=@1 AND WorldID=@2", newOwner, regionName, Main.worldID.ToString()); if (q > 0) return true; } return false; } public bool AllowGroup( string regionName, string groups) { string groupsNew = ""; using (var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) groupsNew = reader.Get("Groups"); } if (groupsNew != "") groupsNew += ","; groupsNew += groups; int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", groupsNew, regionName, Main.worldID.ToString()); Region r = GetRegionByName(regionName); if( r != null ) { r.SetAllowedGroups( groupsNew ); } else { return false; } return q > 0; } public bool RemoveGroup( string regionName, string group ) { Region r = GetRegionByName(regionName); if (r != null) { r.RemoveGroup(group); string groups = string.Join(",", r.AllowedGroups); int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", groups, regionName, Main.worldID.ToString()); if (q > 0) return true; } return false; } } public class Region { public Rectangle Area { get; set; } public string Name { get; set; } public string Owner { get; set; } public bool DisableBuild { get; set; } public string WorldID { get; set; } public List AllowedIDs { get; set; } public List AllowedGroups { get; set; } public Region(Rectangle region, string name, string owner, bool disablebuild, string RegionWorldIDz) : this() { Area = region; Name = name; Owner = owner; DisableBuild = disablebuild; WorldID = RegionWorldIDz; } public Region() { Area = Rectangle.Empty; Name = string.Empty; DisableBuild = true; WorldID = string.Empty; AllowedIDs = new List(); AllowedGroups = new List(); } public bool InArea(Rectangle point) { if (Area.Contains(point.X, point.Y)) { return true; } return false; } public bool HasPermissionToBuildInRegion(TSPlayer ply) { if (!ply.IsLoggedIn) { if (!ply.HasBeenNaggedAboutLoggingIn) { ply.SendMessage("You must be logged in to take advantage of protected regions.", Color.Red); ply.HasBeenNaggedAboutLoggingIn = true; } return false; } if (!DisableBuild) { return true; } return AllowedIDs.Contains(ply.UserID) || AllowedGroups.Contains(ply.Group.Name) || Owner == ply.UserAccountName || ply.Group.HasPermission("manageregion"); } public void setAllowedIDs(String ids) { String[] id_arr = ids.Split(','); List id_list = new List(); foreach (String id in id_arr) { int i = 0; int.TryParse(id, out i); if (i != 0) id_list.Add(i); } AllowedIDs = id_list; } public void SetAllowedGroups( String groups ) { // prevent null pointer exceptions if (!string.IsNullOrEmpty(groups)) { List groupArr = groups.Split(',').ToList(); for (int i = 0; i < groupArr.Count; i++) groupArr[i] = groupArr[i].Trim(); AllowedGroups = groupArr; } } public void RemoveID(int id) { var index = -1; for (int i = 0; i < AllowedIDs.Count; i++) { if (AllowedIDs[i] == id) { index = i; break; } } AllowedIDs.RemoveAt(index); } public bool RemoveGroup(string groupName) { return AllowedGroups.Remove(groupName); } } }