From 88394ac5618335138299876cb33535724e5d8592 Mon Sep 17 00:00:00 2001 From: Zack Piispanen Date: Sat, 15 Sep 2012 16:20:16 -0400 Subject: [PATCH] Added back in region manager. Added back in spawnmob commands, including the obsolete boss commands. --- TShockAPI/BackupManager.cs | 2 +- TShockAPI/Commands.cs | 717 +++++++++++++++++++++++++++++++++- TShockAPI/DB/RegionManager.cs | 676 ++++++++++++++++++++++++++++++++ TShockAPI/GetDataHandlers.cs | 17 + TShockAPI/TShock.cs | 31 +- TShockAPI/TShockAPI.csproj | 3 +- 6 files changed, 1438 insertions(+), 8 deletions(-) create mode 100644 TShockAPI/DB/RegionManager.cs diff --git a/TShockAPI/BackupManager.cs b/TShockAPI/BackupManager.cs index 2046698c..4cb41e87 100644 --- a/TShockAPI/BackupManager.cs +++ b/TShockAPI/BackupManager.cs @@ -60,7 +60,7 @@ namespace TShockAPI if (worldpath != null && !Directory.Exists(worldpath)) Directory.CreateDirectory(worldpath); - TShock.Utils.Broadcast("Server map saving, potential lag spike.."); + TShock.Utils.Broadcast("Server map saving, potential lag spike."); Console.WriteLine("Backing up world..."); SaveManager.Instance.SaveWorld(); diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index fe734174..bffc67ea 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -151,6 +151,16 @@ namespace TShockAPI add(Permissions.causeevents, Fullmoon, "fullmoon"); add(Permissions.causeevents, Bloodmoon, "bloodmoon"); add(Permissions.causeevents, Invade, "invade"); + add(Permissions.spawnboss, Eater, "eater"); + add(Permissions.spawnboss, Eye, "eye"); + add(Permissions.spawnboss, King, "king"); + add(Permissions.spawnboss, Skeletron, "skeletron"); + add(Permissions.spawnboss, WoF, "wof", "wallofflesh"); + add(Permissions.spawnboss, Twins, "twins"); + add(Permissions.spawnboss, Destroyer, "destroyer"); + add(Permissions.spawnboss, SkeletronPrime, "skeletronp", "prime"); + add(Permissions.spawnboss, Hardcore, "hardcore"); + add(Permissions.spawnmob, SpawnMob, "spawnmob", "sm"); add(Permissions.warp, Warp, "warp", "setwarp", "delwarp", "sendwarp", "sw"); add(Permissions.managegroup, AddGroup, "addgroup"); add(Permissions.managegroup, DeleteGroup, "delgroup"); @@ -161,6 +171,8 @@ namespace TShockAPI add(Permissions.manageitem, ListItems, "listitems", "listbanneditems"); add(Permissions.manageitem, AddItemGroup, "additemgroup"); add(Permissions.manageitem, DeleteItemGroup, "delitemgroup"); + add(Permissions.manageregion, Region, "region"); + add(Permissions.manageregion, DebugRegions, "debugreg"); add(Permissions.cfg, Reload, "reload"); add(Permissions.cfg, ServerPassword, "serverpassword"); add(Permissions.cfg, Save, "save"); @@ -1199,9 +1211,9 @@ namespace TShockAPI #endregion Server Maintenence Commands - #region Cause Events and Spawn Monsters Commands + #region Cause Events and Spawn Monsters Commands - private static void DropMeteor(CommandArgs args) + private static void DropMeteor(CommandArgs args) { WorldGen.spawnMeteor = false; WorldGen.dropMeteor(); @@ -1264,6 +1276,248 @@ namespace TShockAPI Main.hardMode = false; args.Player.SendMessage("Hardmode is now disabled.", Color.Green); } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void Eater(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /eater [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /eater [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC eater = TShock.Utils.GetNPCById(13); + TSPlayer.Server.SpawnNPC(eater.type, eater.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned eater of worlds {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void Eye(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /eye [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /eye [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC eye = TShock.Utils.GetNPCById(4); + TSPlayer.Server.SetTime(false, 0.0); + TSPlayer.Server.SpawnNPC(eye.type, eye.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned eye {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void King(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /king [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /king [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC king = TShock.Utils.GetNPCById(50); + TSPlayer.Server.SpawnNPC(king.type, king.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned king slime {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void Skeletron(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /skeletron [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /skeletron [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC skeletron = TShock.Utils.GetNPCById(35); + TSPlayer.Server.SetTime(false, 0.0); + TSPlayer.Server.SpawnNPC(skeletron.type, skeletron.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned skeletron {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void WoF(CommandArgs args) + { + if (Main.wof >= 0 || (args.Player.Y / 16f < (Main.maxTilesY - 205))) + { + args.Player.SendMessage("Can't spawn Wall of Flesh!", Color.Red); + return; + } + NPC.SpawnWOF(new Vector2(args.Player.X, args.Player.Y)); + TShock.Utils.Broadcast(string.Format("{0} has spawned Wall of Flesh!", args.Player.Name)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void Twins(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /twins [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /twins [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC retinazer = TShock.Utils.GetNPCById(125); + NPC spaz = TShock.Utils.GetNPCById(126); + TSPlayer.Server.SetTime(false, 0.0); + TSPlayer.Server.SpawnNPC(retinazer.type, retinazer.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(spaz.type, spaz.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned the twins {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void Destroyer(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /destroyer [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /destroyer [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC destroyer = TShock.Utils.GetNPCById(134); + TSPlayer.Server.SetTime(false, 0.0); + TSPlayer.Server.SpawnNPC(destroyer.type, destroyer.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned the destroyer {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void SkeletronPrime(CommandArgs args) + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /prime [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /prime [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs); + NPC prime = TShock.Utils.GetNPCById(127); + TSPlayer.Server.SetTime(false, 0.0); + TSPlayer.Server.SpawnNPC(prime.type, prime.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned skeletron prime {1} times!", args.Player.Name, amount)); + } + + [Obsolete("This specific command for spawning mobs will replaced soon.")] + private static void Hardcore(CommandArgs args) // TODO: Add all 8 bosses + { + if (args.Parameters.Count > 1) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /hardcore [amount]", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 1 && !int.TryParse(args.Parameters[0], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /hardcore [amount]", Color.Red); + return; + } + amount = Math.Min(amount, Main.maxNPCs / 4); + NPC retinazer = TShock.Utils.GetNPCById(125); + NPC spaz = TShock.Utils.GetNPCById(126); + NPC destroyer = TShock.Utils.GetNPCById(134); + NPC prime = TShock.Utils.GetNPCById(127); + NPC eater = TShock.Utils.GetNPCById(13); + NPC eye = TShock.Utils.GetNPCById(4); + NPC king = TShock.Utils.GetNPCById(50); + NPC skeletron = TShock.Utils.GetNPCById(35); + TSPlayer.Server.SetTime(false, 0.0); + TSPlayer.Server.SpawnNPC(retinazer.type, retinazer.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(spaz.type, spaz.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(destroyer.type, destroyer.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(prime.type, prime.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(eater.type, eater.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(eye.type, eye.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(king.type, king.name, amount, args.Player.TileX, args.Player.TileY); + TSPlayer.Server.SpawnNPC(skeletron.type, skeletron.name, amount, args.Player.TileX, args.Player.TileY); + TShock.Utils.Broadcast(string.Format("{0} has spawned all bosses {1} times!", args.Player.Name, amount)); + } + + private static void SpawnMob(CommandArgs args) + { + if (args.Parameters.Count < 1 || args.Parameters.Count > 2) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /spawnmob [amount]", Color.Red); + return; + } + if (args.Parameters[0].Length == 0) + { + args.Player.SendMessage("Missing mob name/id", Color.Red); + return; + } + int amount = 1; + if (args.Parameters.Count == 2 && !int.TryParse(args.Parameters[1], out amount)) + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /spawnmob [amount]", Color.Red); + return; + } + + amount = Math.Min(amount, Main.maxNPCs); + + var npcs = TShock.Utils.GetNPCByIdOrName(args.Parameters[0]); + if (npcs.Count == 0) + { + args.Player.SendMessage("Invalid mob type!", Color.Red); + } + else if (npcs.Count > 1) + { + args.Player.SendMessage(string.Format("More than one ({0}) mob matched!", npcs.Count), Color.Red); + } + else + { + var npc = npcs[0]; + if (npc.type >= 1 && npc.type < Main.maxNPCTypes && npc.type != 113) + //Do not allow WoF to spawn, in certain conditions may cause loops in client + { + TSPlayer.Server.SpawnNPC(npc.type, npc.name, amount, args.Player.TileX, args.Player.TileY, 50, 20); + TSPlayer.All.SendSuccessMessage(string.Format("{0} was spawned {1} time(s).", npc.name, amount)); + } + else if (npc.type == 113) + args.Player.SendErrorMessage("Sorry, you can't spawn Wall of Flesh! Try /wof instead."); + // Maybe perhaps do something with WorldGen.SpawnWoF? + else + args.Player.SendMessage("Invalid mob type!", Color.Red); + } + } + #endregion Cause Events and Spawn Monsters Commands #region Teleport Commands @@ -1921,7 +2175,7 @@ namespace TShockAPI TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); TShock.Groups.LoadPermisions(); //todo: Create an event for reloads to propegate to plugins. - //TShock.Regions.ReloadAllRegions(); + TShock.Regions.ReloadAllRegions(); args.Player.SendSuccessMessage( "Configuration, permissions, and regions reload complete. Some changes may require a server restart."); } @@ -2106,9 +2360,462 @@ namespace TShockAPI #endregion Time/PvpFun Commands - #region World Protection Commands + #region Region Commands - private static void ToggleAntiBuild(CommandArgs args) + private static void DebugRegions(CommandArgs args) + { + foreach (Region r in TShock.Regions.Regions) + { + args.Player.SendMessage(r.Name + ": P: " + r.DisableBuild + " X: " + r.Area.X + " Y: " + r.Area.Y + " W: " + + r.Area.Width + " H: " + r.Area.Height); + foreach (int s in r.AllowedIDs) + { + args.Player.SendMessage(r.Name + ": " + s); + } + } + } + + private static void Region(CommandArgs args) + { + string cmd = "help"; + if (args.Parameters.Count > 0) + { + cmd = args.Parameters[0].ToLower(); + } + switch (cmd) + { + case "name": + { + { + args.Player.SendMessage("Hit a block to get the name of the region", Color.Yellow); + args.Player.AwaitingName = true; + } + break; + } + case "set": + { + int choice = 0; + if (args.Parameters.Count == 2 && + int.TryParse(args.Parameters[1], out choice) && + choice >= 1 && choice <= 2) + { + args.Player.SendMessage("Hit a block to Set Point " + choice, Color.Yellow); + args.Player.AwaitingTempPoint = choice; + } + else + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /region set [1/2]", Color.Red); + } + break; + } + case "define": + { + if (args.Parameters.Count > 1) + { + if (!args.Player.TempPoints.Any(p => p == Point.Zero)) + { + string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); + var x = Math.Min(args.Player.TempPoints[0].X, args.Player.TempPoints[1].X); + var y = Math.Min(args.Player.TempPoints[0].Y, args.Player.TempPoints[1].Y); + var width = Math.Abs(args.Player.TempPoints[0].X - args.Player.TempPoints[1].X); + var height = Math.Abs(args.Player.TempPoints[0].Y - args.Player.TempPoints[1].Y); + + if (TShock.Regions.AddRegion(x, y, width, height, regionName, args.Player.UserAccountName, + Main.worldID.ToString())) + { + args.Player.TempPoints[0] = Point.Zero; + args.Player.TempPoints[1] = Point.Zero; + args.Player.SendMessage("Set region " + regionName, Color.Yellow); + } + else + { + args.Player.SendMessage("Region " + regionName + " already exists", Color.Red); + } + } + else + { + args.Player.SendMessage("Points not set up yet", Color.Red); + } + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region define [name]", Color.Red); + break; + } + case "protect": + { + if (args.Parameters.Count == 3) + { + string regionName = args.Parameters[1]; + if (args.Parameters[2].ToLower() == "true") + { + if (TShock.Regions.SetRegionState(regionName, true)) + args.Player.SendMessage("Protected region " + regionName, Color.Yellow); + else + args.Player.SendMessage("Could not find specified region", Color.Red); + } + else if (args.Parameters[2].ToLower() == "false") + { + if (TShock.Regions.SetRegionState(regionName, false)) + args.Player.SendMessage("Unprotected region " + regionName, Color.Yellow); + else + args.Player.SendMessage("Could not find specified region", Color.Red); + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region protect [name] [true/false]", Color.Red); + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region protect [name] [true/false]", Color.Red); + break; + } + case "delete": + { + if (args.Parameters.Count > 1) + { + string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); + if (TShock.Regions.DeleteRegion(regionName)) + args.Player.SendMessage("Deleted region " + regionName, Color.Yellow); + else + args.Player.SendMessage("Could not find specified region", Color.Red); + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region delete [name]", Color.Red); + break; + } + case "clear": + { + args.Player.TempPoints[0] = Point.Zero; + args.Player.TempPoints[1] = Point.Zero; + args.Player.SendMessage("Cleared temp area", Color.Yellow); + args.Player.AwaitingTempPoint = 0; + break; + } + case "allow": + { + if (args.Parameters.Count > 2) + { + string playerName = args.Parameters[1]; + string regionName = ""; + + for (int i = 2; i < args.Parameters.Count; i++) + { + if (regionName == "") + { + regionName = args.Parameters[2]; + } + else + { + regionName = regionName + " " + args.Parameters[i]; + } + } + if (TShock.Users.GetUserByName(playerName) != null) + { + if (TShock.Regions.AddNewUser(regionName, playerName)) + { + args.Player.SendMessage("Added user " + playerName + " to " + regionName, Color.Yellow); + } + else + args.Player.SendMessage("Region " + regionName + " not found", Color.Red); + } + else + { + args.Player.SendMessage("Player " + playerName + " not found", Color.Red); + } + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow [name] [region]", Color.Red); + break; + } + case "remove": + if (args.Parameters.Count > 2) + { + string playerName = args.Parameters[1]; + string regionName = ""; + + for (int i = 2; i < args.Parameters.Count; i++) + { + if (regionName == "") + { + regionName = args.Parameters[2]; + } + else + { + regionName = regionName + " " + args.Parameters[i]; + } + } + if (TShock.Users.GetUserByName(playerName) != null) + { + if (TShock.Regions.RemoveUser(regionName, playerName)) + { + args.Player.SendMessage("Removed user " + playerName + " from " + regionName, Color.Yellow); + } + else + args.Player.SendMessage("Region " + regionName + " not found", Color.Red); + } + else + { + args.Player.SendMessage("Player " + playerName + " not found", Color.Red); + } + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region remove [name] [region]", Color.Red); + break; + case "allowg": + { + if (args.Parameters.Count > 2) + { + string group = args.Parameters[1]; + string regionName = ""; + + for (int i = 2; i < args.Parameters.Count; i++) + { + if (regionName == "") + { + regionName = args.Parameters[2]; + } + else + { + regionName = regionName + " " + args.Parameters[i]; + } + } + if (TShock.Groups.GroupExists(group)) + { + if (TShock.Regions.AllowGroup(regionName, group)) + { + args.Player.SendMessage("Added group " + group + " to " + regionName, Color.Yellow); + } + else + args.Player.SendMessage("Region " + regionName + " not found", Color.Red); + } + else + { + args.Player.SendMessage("Group " + group + " not found", Color.Red); + } + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow [group] [region]", Color.Red); + break; + } + case "removeg": + if (args.Parameters.Count > 2) + { + string group = args.Parameters[1]; + string regionName = ""; + + for (int i = 2; i < args.Parameters.Count; i++) + { + if (regionName == "") + { + regionName = args.Parameters[2]; + } + else + { + regionName = regionName + " " + args.Parameters[i]; + } + } + if (TShock.Groups.GroupExists(group)) + { + if (TShock.Regions.RemoveGroup(regionName, group)) + { + args.Player.SendMessage("Removed group " + group + " from " + regionName, Color.Yellow); + } + else + args.Player.SendMessage("Region " + regionName + " not found", Color.Red); + } + else + { + args.Player.SendMessage("Group " + group + " not found", Color.Red); + } + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region removeg [group] [region]", Color.Red); + break; + case "list": + { + //How many regions per page + const int pagelimit = 15; + //How many regions per line + const int perline = 5; + //Pages start at 0 but are displayed and parsed at 1 + int page = 0; + + + if (args.Parameters.Count > 1) + { + if (!int.TryParse(args.Parameters[1], out page) || page < 1) + { + args.Player.SendMessage(string.Format("Invalid page number ({0})", page), Color.Red); + return; + } + page--; //Substract 1 as pages are parsed starting at 1 and not 0 + } + + var regions = TShock.Regions.ListAllRegions(Main.worldID.ToString()); + + // Are there even any regions to display? + if (regions.Count == 0) + { + args.Player.SendMessage("There are currently no regions defined.", Color.Red); + return; + } + + //Check if they are trying to access a page that doesn't exist. + int pagecount = regions.Count / pagelimit; + if (page > pagecount) + { + args.Player.SendMessage(string.Format("Page number exceeds pages ({0}/{1})", page + 1, pagecount + 1), Color.Red); + return; + } + + //Display the current page and the number of pages. + args.Player.SendMessage(string.Format("Current Regions ({0}/{1}):", page + 1, pagecount + 1), Color.Green); + + //Add up to pagelimit names to a list + var nameslist = new List(); + for (int i = (page * pagelimit); (i < ((page * pagelimit) + pagelimit)) && i < regions.Count; i++) + { + nameslist.Add(regions[i].Name); + } + + //convert the list to an array for joining + var names = nameslist.ToArray(); + for (int i = 0; i < names.Length; i += perline) + { + args.Player.SendMessage(string.Join(", ", names, i, Math.Min(names.Length - i, perline)), Color.Yellow); + } + + if (page < pagecount) + { + args.Player.SendMessage(string.Format("Type /region list {0} for more regions.", (page + 2)), Color.Yellow); + } + + break; + } + case "info": + { + if (args.Parameters.Count > 1) + { + string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); + Region r = TShock.Regions.GetRegionByName(regionName); + + if (r == null) + { + args.Player.SendMessage("Region {0} does not exist"); + break; + } + + args.Player.SendMessage(r.Name + ": P: " + r.DisableBuild + " X: " + r.Area.X + " Y: " + r.Area.Y + " W: " + + r.Area.Width + " H: " + r.Area.Height); + foreach (int s in r.AllowedIDs) + { + var user = TShock.Users.GetUserByID(s); + args.Player.SendMessage(r.Name + ": " + (user != null ? user.Name : "Unknown")); + } + } + else + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /region info [name]", Color.Red); + } + + break; + } + case "z": + { + if (args.Parameters.Count == 3) + { + string regionName = args.Parameters[1]; + int z = 0; + if (int.TryParse(args.Parameters[2], out z)) + { + if (TShock.Regions.SetZ(regionName, z)) + args.Player.SendMessage("Region's z is now " + z, Color.Yellow); + else + args.Player.SendMessage("Could not find specified region", Color.Red); + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region z [name] [#]", Color.Red); + } + else + args.Player.SendMessage("Invalid syntax! Proper syntax: /region z [name] [#]", Color.Red); + break; + } + case "resize": + case "expand": + { + if (args.Parameters.Count == 4) + { + int direction; + switch (args.Parameters[2]) + { + case "u": + case "up": + { + direction = 0; + break; + } + case "r": + case "right": + { + direction = 1; + break; + } + case "d": + case "down": + { + direction = 2; + break; + } + case "l": + case "left": + { + direction = 3; + break; + } + default: + { + direction = -1; + break; + } + } + int addAmount; + int.TryParse(args.Parameters[3], out addAmount); + if (TShock.Regions.resizeRegion(args.Parameters[1], addAmount, direction)) + { + args.Player.SendMessage("Region Resized Successfully!", Color.Yellow); + TShock.Regions.ReloadAllRegions(); + } + else + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /region resize [regionname] [u/d/l/r] [amount]", + Color.Red); + } + } + else + { + args.Player.SendMessage("Invalid syntax! Proper syntax: /region resize [regionname] [u/d/l/r] [amount]1", + Color.Red); + } + break; + } + case "help": + default: + { + args.Player.SendMessage("Avialable region commands:", Color.Green); + args.Player.SendMessage("/region set [1/2] /region define [name] /region protect [name] [true/false]", + Color.Yellow); + args.Player.SendMessage("/region name (provides region name)", Color.Yellow); + args.Player.SendMessage("/region delete [name] /region clear (temporary region)", Color.Yellow); + args.Player.SendMessage("/region allow [name] [regionname]", Color.Yellow); + args.Player.SendMessage("/region resize [regionname] [u/d/l/r] [amount]", Color.Yellow); + break; + } + } + } + + #endregion Region Commands + + #region World Protection Commands + + private static void ToggleAntiBuild(CommandArgs args) { TShock.Config.DisableBuild = (TShock.Config.DisableBuild == false); TSPlayer.All.SendSuccessMessage(string.Format("Anti-build is now {0}.", (TShock.Config.DisableBuild ? "on" : "off"))); diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs new file mode 100644 index 00000000..31519726 --- /dev/null +++ b/TShockAPI/DB/RegionManager.cs @@ -0,0 +1,676 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2012 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), + new SqlColumn("Z", MySqlDbType.Int32){ DefaultValue = "0" } + ); + var creator = new SqlTableCreator(db, + db.GetSqlType() == SqlType.Sqlite + ? (IQueryBuilder) new SqliteQueryCreator() + : new MysqlQueryCreator()); + creator.EnsureExists(table); + + ReloadAllRegions(); + } + + 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"); + int z = reader.Get("Z"); + + string[] splitids = mergedids.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + + Region r = new Region(new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString(), z); + 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"); + int z = reader.Get("Z"); + + Region r = new Region(new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString(), z); + 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, int z = 0) + { + if (GetRegionByName(regionname) != null) + { + return false; + } + try + { + database.Query( + "INSERT INTO Regions (X1, Y1, width, height, RegionName, WorldID, UserIds, Protected, Groups, Owner, Z) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10);", + tx, ty, width, height, regionname, worldid, "", 1, "", owner, z); + Regions.Add(new Region(new Rectangle(tx, ty, width, height), regionname, owner, true, worldid, z)); + 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; + } + Region top = null; + for (int i = 0; i < Regions.Count; i++) + { + if (Regions[i].InArea(x,y) ) + { + if (top == null) + top = Regions[i]; + else + { + if (Regions[i].Z > top.Z) + top = Regions[i]; + } + } + } + return top == null || top.HasPermissionToBuildInRegion(ply); + } + + 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 List InAreaRegionName(int x, int y) + { + List regions = new List() { }; + foreach (Region region in Regions) + { + if (x >= region.Area.Left && x <= region.Area.Right && + y >= region.Area.Top && y <= region.Area.Bottom && + region.DisableBuild) + { + regions.Add(region.Name); + } + } + return regions; + } + + public List InAreaRegion(int x, int y) + { + List regions = new List() { }; + foreach (Region region in Regions) + { + if (x >= region.Area.Left && x <= region.Area.Right && + y >= region.Area.Top && y <= region.Area.Bottom && + region.DisableBuild) + { + regions.Add(region); + } + } + return regions; + } + + 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 Region GetTopRegion( List regions ) + { + Region ret = null; + foreach( Region r in regions) + { + if (ret == null) + ret = r; + else + { + if (r.Z > ret.Z) + ret = r; + } + } + return ret; + } + + public bool SetZ( string name, int z ) + { + try + { + database.Query("UPDATE Regions SET Z=@0 WHERE RegionName=@1 AND WorldID=@2", z, name, + Main.worldID.ToString()); + + var region = GetRegionByName(name); + if (region != null) + region.Z = z; + return true; + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + 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 int Z { get; set; } + + public Region(Rectangle region, string name, string owner, bool disablebuild, string RegionWorldIDz, int z) + : this() + { + Area = region; + Name = name; + Owner = owner; + DisableBuild = disablebuild; + WorldID = RegionWorldIDz; + Z = z; + } + + public Region() + { + Area = Rectangle.Empty; + Name = string.Empty; + DisableBuild = true; + WorldID = string.Empty; + AllowedIDs = new List(); + AllowedGroups = new List(); + Z = 0; + } + + public bool InArea(Rectangle point) + { + if (Area.Contains(point.X, point.Y)) + { + return true; + } + return false; + } + + public bool InArea(int x, int y) //overloaded with x,y + { + + if (x >= Area.Left && x <= Area.Right && y >= Area.Top && y <= Area.Bottom) + { + 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); + } + } +} diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 1fde7fc9..6b34735b 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1659,6 +1659,23 @@ namespace TShockAPI if (args.Player.Dead && TShock.Config.PreventDeadModification) return true; + if (args.Player.AwaitingName) + { + var protectedregions = TShock.Regions.InAreaRegionName(tileX, tileY); + if (protectedregions.Count == 0) + { + args.Player.SendMessage("Region is not protected", Color.Yellow); + } + else + { + string regionlist = string.Join(",", protectedregions.ToArray()); + args.Player.SendMessage("Region Name(s): " + regionlist, Color.Yellow); + } + args.Player.SendTileSquare(tileX, tileY); + args.Player.AwaitingName = false; + return true; + } + if (args.Player.AwaitingTempPoint > 0) { args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].X = tileX; diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 4df96695..f97a56fc 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -51,6 +51,7 @@ namespace TShockAPI public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers]; public static BanManager Bans; public static WarpManager Warps; + public static RegionManager Regions; public static BackupManager Backups; public static GroupManager Groups; public static UserManager Users; @@ -187,6 +188,7 @@ namespace TShockAPI Backups.Interval = Config.BackupInterval; Bans = new BanManager(DB); Warps = new WarpManager(DB); + Regions = new RegionManager(DB); Users = new UserManager(DB); Groups = new GroupManager(DB); Itembans = new ItemManager(DB); @@ -520,6 +522,8 @@ namespace TShockAPI AuthToken = 0; } + Regions.ReloadAllRegions(); + StatTracker.CheckIn(); FixChestStacks(); @@ -1320,7 +1324,19 @@ namespace TShockAPI return true; } - + + if (!player.Group.HasPermission(Permissions.editspawn) && !Regions.CanBuild(tileX, tileY, player) && + Regions.InArea(tileX, tileY)) + { + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000) + { + player.SendMessage("This region is protected from changes.", Color.Red); + player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + + } + return true; + } + if (Config.DisableBuild) { if (!player.Group.HasPermission(Permissions.editspawn)) @@ -1363,6 +1379,19 @@ namespace TShockAPI return true; } + if (!player.Group.HasPermission(Permissions.editspawn) && !Regions.CanBuild(tileX, tileY, player) && + Regions.InArea(tileX, tileY)) + { + + + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000) + { + player.SendMessage("This region is protected from changes.", Color.Red); + player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + } + return true; + } + if (Config.DisableBuild) { if (!player.Group.HasPermission(Permissions.editspawn)) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index a7bd7b5c..468a4a30 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -85,6 +85,7 @@ + @@ -187,7 +188,7 @@ - +