diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..598dd721 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = crlf +insert_final_newline = true + +[*.cs] +indent_style = tab +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..026167bb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto +*.cs text eol=crlf +*.sln text eol=crlf +*.csproj text eol=crlf +*.vsmdi text eol=crlf diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..22e6ce9b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: c +install: +- sudo apt-get install mono-devel mono-gmcs nunit-console +script: +- xbuild ./TShockAPI/TShockAPI.csproj +notifications: + irc: irc.rizon.net#tshock + hipchat: + secure: hpRLWiHF2j6O2qJOVs++aqAmryN6G5kY0SF26/rKCpQ7klhMlDZIgI1V1dbkKqlculFtW1neS0EBJyV9lmcV5b26H+KhlZYGN0j7q1VcOTM3rvtU6wW0Ap22uRLl2RrnA4kEsgDAsNouPOkyLZ19hlHAISlsId6G4+Rfqg6k+zQ= \ No newline at end of file diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 00000000..8f36fd52 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,36 @@ +### Issue Guidelines +Please follow these simple requirements before posting an issue: + +1. TShock version number +2. Any stack traces that may have happened when the issue occurred +3. How to reproduce the issue + +### Pull Request Dev Guidelines + +These guidelines are for contributors. If you do not follow these guidelines your commits will be reverted. + +Required: +- Follow the code style. We generally use microsofts except for m_ infront of private variables. +- Do not push unfinished features to the master branch, instead create a remote branch and push to that. +- Do not push untested code to the master branch, instead push to the test branch. +- Document all compatibility issues in the COMPATIBILITY file. (IE file formats changing) +- DO NOT MASS COMMIT. Commit changes as you go (without pushing). That way when you push we don't get a thousand changes with a 1-3 line commit message. + +Optional: +- Build Version Increment (http://autobuildversion.codeplex.com/). + +---- + +### Dev Team Guidelines + +These guidelines are to be followed by all developers with commit level access to this repository: + +- Do not, for any reason, submit code to the master branch before it hits the development branch first. If the development branch is far ahead, and a new bug fix is going out, branch master, then merge with master and remove your branch. + - If you are found to do this, you will be the person merging and rebasing your code to fit general-devel. +- Prior to posting any version on the website, you must tick the version in AssemblyInfo.cs. This is the versioning formula: + - Major.Minor.Revision.BuildDate (tick Revision if you're fixing prior to an actual planned release) +- Do not release any development builds on the forums without consulting another developer first. +- __Document code prior to marking it done in JIRA__ +- Move any un-tested code to the "Needs Validation" section on JIRA prior to marking it as done. +- Do not push changes to any branch without a proper issue being assigned in JIRA. If a feature isn't planned for this release, __it shouldn't be in the repo about to be released__. +- Submit all pull requests to the general-devel branch prior to the master branch, or you will be ignored. \ No newline at end of file diff --git a/README.md b/README.md index 741aca81..f394cf6c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TShock +# TShock [![Build Status](https://travis-ci.org/NyxStudios/TShock.png?branch=general-devel)](https://travis-ci.org/NyxStudios/TShock) TShock is a server modification for Terraria, written in C#, and based upon the [Terraria Server API](https://github.com/Deathmax/TerrariaAPI-Server). It uses JSON for configuration management, and offers several features not present in the Terraria Server normally. @@ -24,4 +24,3 @@ Feeling like helping out? Want to find an awesome server? Some awesome plugins? * [Github Releases](https://github.com/TShock/TShock/releases) * [Download Archive](https://github.com/TShock/TShock/downloads) -* [Latest Version (4.0.5)](https://s3.amazonaws.com/tshock/TShock+4.0.5.zip) diff --git a/TShockAPI/BackupManager.cs b/TShockAPI/BackupManager.cs index 4cb41e87..bc5cec6a 100644 --- a/TShockAPI/BackupManager.cs +++ b/TShockAPI/BackupManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.Threading; diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 81893761..7ff6516d 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,18 +15,18 @@ 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.Collections.ObjectModel; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Net; using System.Text; using System.Threading; +using TShockAPI.PluginUpdater; using Terraria; using TShockAPI.DB; -using System.Reflection; namespace TShockAPI { @@ -66,34 +66,54 @@ namespace TShockAPI public List Names { get; protected set; } public bool AllowServer { get; set; } public bool DoLog { get; set; } - public string Permission { get; protected set; } - private CommandDelegate command; + public List Permissions { get; protected set; } + + private CommandDelegate commandDelegate; + public CommandDelegate CommandDelegate + { + get { return commandDelegate; } + set + { + if (value == null) + throw new ArgumentNullException(); + + commandDelegate = value; + } + } + + public Command(List permissionsneeded, CommandDelegate cmd, params string[] names) + : this(cmd, names) + { + Permissions = permissionsneeded; + } public Command(string permissionneeded, CommandDelegate cmd, params string[] names) : this(cmd, names) { - Permission = permissionneeded; + Permissions = new List { permissionneeded }; } public Command(CommandDelegate cmd, params string[] names) { + if (cmd == null) + throw new ArgumentNullException("cmd"); if (names == null || names.Length < 1) - throw new NotSupportedException(); - Permission = null; + throw new ArgumentException("names"); + Permissions = new List(); Names = new List(names); - command = cmd; + CommandDelegate = cmd; AllowServer = true; DoLog = true; } public bool Run(string msg, TSPlayer ply, List parms) { - if (!ply.Group.HasPermission(Permission)) + if (!CanRun(ply)) return false; try { - command(new CommandArgs(msg, ply, parms)); + CommandDelegate(new CommandArgs(msg, ply, parms)); } catch (Exception e) { @@ -111,42 +131,57 @@ namespace TShockAPI public bool CanRun(TSPlayer ply) { - return ply.Group.HasPermission(Permission); + if (Permissions == null || Permissions.Count < 1) + return true; + foreach (var Permission in Permissions) + { + if (ply.Group.HasPermission(Permission)) + return true; + } + return false; } } public static class Commands { public static List ChatCommands = new List(); + public static ReadOnlyCollection TShockCommands = new ReadOnlyCollection(new List()); private delegate void AddChatCommand(string permission, CommandDelegate command, params string[] names); public static void InitCommands() { - AddChatCommand add = (p, c, n) => ChatCommands.Add(new Command(p, c, n)); - ChatCommands.Add(new Command(AuthToken, "auth") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.canchangepassword, PasswordUser, "password") { AllowServer = false, DoLog = false }); - ChatCommands.Add(new Command(Permissions.canregister, RegisterUser, "register") { AllowServer = false, DoLog = false }); - ChatCommands.Add(new Command(Permissions.rootonly, ManageUsers, "user") { DoLog = false }); - ChatCommands.Add(new Command(Permissions.canlogin, AttemptLogin, "login") { AllowServer = false, DoLog = false }); - ChatCommands.Add(new Command(Permissions.buff, Buff, "buff") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.cfg, SetSpawn, "setspawn") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.grow, Grow, "grow") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.item, Item, "item", "i") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, Home, "home") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.canpartychat, PartyChat, "p") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, Spawn, "spawn") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, TP, "tp") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tp, TPHere, "tphere") { AllowServer = false }); - ChatCommands.Add(new Command(Permissions.tpallow, TPAllow, "tpallow") { AllowServer = false }); + List tshockCommands = new List(100); + Action add2 = (cmd) => + { + tshockCommands.Add(cmd); + ChatCommands.Add(cmd); + }; + AddChatCommand add = (p, c, n) => add2(new Command(p, c, n)); + + add2(new Command(AuthToken, "auth") { AllowServer = false }); + add2(new Command(Permissions.canchangepassword, PasswordUser, "password") { AllowServer = false, DoLog = false }); + add2(new Command(Permissions.canregister, RegisterUser, "register") { AllowServer = false, DoLog = false }); + add2(new Command(Permissions.user, ManageUsers, "user") { DoLog = false }); + add2(new Command(Permissions.canlogin, AttemptLogin, "login") { AllowServer = false, DoLog = false }); + add2(new Command(Permissions.buff, Buff, "buff") { AllowServer = false }); + add2(new Command(Permissions.worldspawn, SetSpawn, "setspawn") { AllowServer = false }); + add2(new Command(Permissions.grow, Grow, "grow") { AllowServer = false }); + add2(new Command(Permissions.item, Item, "item", "i") { AllowServer = false }); + add2(new Command(Permissions.home, Home, "home") { AllowServer = false }); + add2(new Command(Permissions.canpartychat, PartyChat, "p") { AllowServer = false }); + add2(new Command(Permissions.spawn, Spawn, "spawn") { AllowServer = false }); + add2(new Command(Permissions.tp, TP, "tp") { AllowServer = false }); + add2(new Command(Permissions.tphere, TPHere, "tphere") { AllowServer = false }); + add2(new Command(Permissions.tpallow, TPAllow, "tpallow") { AllowServer = false }); add(Permissions.kick, Kick, "kick"); - add(Permissions.ban, DeprecateBans, "banip", "listbans", "unban", "unbanip", "clearbans"); add(Permissions.ban, Ban, "ban"); add(Permissions.whitelist, Whitelist, "whitelist"); add(Permissions.maintenance, Off, "off", "exit"); - add(Permissions.maintenance, Restart, "restart"); //Added restart command + add(Permissions.maintenance, Restart, "restart"); add(Permissions.maintenance, OffNoSave, "off-nosave", "exit-nosave"); add(Permissions.maintenance, CheckUpdates, "checkupdates"); + add(Permissions.updateplugins, UpdatePlugins, "updateplugins"); add(Permissions.causeevents, DropMeteor, "dropmeteor"); add(Permissions.causeevents, Star, "star"); add(Permissions.causeevents, Fullmoon, "fullmoon"); @@ -163,26 +198,21 @@ namespace TShockAPI add(Permissions.spawnboss, Hardcore, "hardcore"); add(Permissions.spawnmob, SpawnMob, "spawnmob", "sm"); add(Permissions.warp, Warp, "warp"); - add(null, DeprecateWarp, "setwarp", "sendwarp", "delwarp", "sw"); - add(Permissions.managegroup, AddGroup, "addgroup"); - add(Permissions.managegroup, DeleteGroup, "delgroup"); - add(Permissions.managegroup, ModifyGroup, "modgroup"); - add(Permissions.managegroup, ViewGroups, "group"); - add(Permissions.manageitem, AddItem, "additem", "banitem"); - add(Permissions.manageitem, DeleteItem, "delitem", "unbanitem"); - add(Permissions.manageitem, ListItems, "listitems", "listbanneditems"); - add(Permissions.manageitem, AddItemGroup, "additemgroup"); - add(Permissions.manageitem, DeleteItemGroup, "delitemgroup"); + add(Permissions.managegroup, Group, "group"); + add(Permissions.managegroup, GroupDeprecated, "addgroup", "delgroup", "modgroup"); + add(Permissions.manageitem, ItemBan, "itemban"); + add(Permissions.manageitem, ItemBanDeprecated, + "additem", "additemgroup", "banitem", "delitem", "delitemgroup", "listitems", "listbanneditems", "unbanitem"); 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"); - add(Permissions.cfg, Settle, "settle"); - add(Permissions.cfg, MaxSpawns, "maxspawns"); - add(Permissions.cfg, SpawnRate, "spawnrate"); + add(Permissions.cfgreload, Reload, "reload"); + add(Permissions.cfgpassword, ServerPassword, "serverpassword"); + add(Permissions.worldsave, Save, "save"); + add(Permissions.worldsettle, Settle, "settle"); + add(Permissions.cfgmaxspawns, MaxSpawns, "maxspawns"); + add(Permissions.cfgspawnrate, SpawnRate, "spawnrate"); add(Permissions.time, Time, "time"); - add(Permissions.pvpfun, Slap, "slap"); + add(Permissions.slap, Slap, "slap"); add(Permissions.editspawn, ToggleAntiBuild, "antibuild"); add(Permissions.editspawn, ProtectSpawn, "protectspawn"); add(Permissions.maintenance, GetVersion, "version"); @@ -194,8 +224,8 @@ namespace TShockAPI add(Permissions.mute, Mute, "mute", "unmute"); add(Permissions.logs, DisplayLogs, "displaylogs"); add(Permissions.userinfo, GrabUserUserInfo, "userinfo", "ui"); - add(Permissions.rootonly, AuthVerify, "auth-verify"); - add(Permissions.cfg, Broadcast, "broadcast", "bc", "say"); + add(Permissions.authverify, AuthVerify, "auth-verify"); + add(Permissions.broadcast, Broadcast, "broadcast", "bc", "say"); add(Permissions.whisper, Whisper, "whisper", "w", "tell"); add(Permissions.whisper, Reply, "reply", "r"); add(Permissions.annoy, Annoy, "annoy"); @@ -207,12 +237,17 @@ namespace TShockAPI add(Permissions.buffplayer, GBuff, "gbuff", "buffplayer"); add(Permissions.hardmode, StartHardMode, "hardmode"); add(Permissions.hardmode, DisableHardMode, "stophardmode", "disablehardmode"); - add(Permissions.cfg, ServerInfo, "stats"); - add(Permissions.cfg, WorldInfo, "world"); + add(Permissions.serverinfo, ServerInfo, "stats"); + add(Permissions.worldinfo, WorldInfo, "world"); add(Permissions.savessi, SaveSSI, "savessi"); add(Permissions.savessi, OverrideSSI, "overridessi", "ossi"); add(Permissions.xmas, ForceXmas, "forcexmas"); + add(Permissions.settempgroup, TempGroup, "tempgroup"); + add(null, Aliases, "aliases"); + add(Rests.RestPermissions.restmanage, ManageRest, "rest"); //add(null, TestCallbackCommand, "test"); + + TShockCommands = new ReadOnlyCollection(tshockCommands); } public static bool HandleCommand(TSPlayer player, string text) @@ -226,6 +261,9 @@ namespace TShockAPI string cmdName = args[0].ToLower(); args.RemoveAt(0); + if (Hooks.PlayerHooks.OnPlayerCommand(player, cmdName, cmdText, args)) + return true; + IEnumerable cmds = ChatCommands.Where(c => c.HasAlias(cmdName)); if (cmds.Count() == 0) @@ -244,7 +282,7 @@ namespace TShockAPI { if (!cmd.CanRun(player)) { - TShock.Utils.SendLogs(string.Format("{0} tried to execute /{1}.", player.Name, cmdText), Color.Red); + TShock.Utils.SendLogs(string.Format("{0} tried to execute /{1}.", player.Name, cmdText), Color.PaleVioletRed, player); player.SendErrorMessage("You do not have access to that command."); } else if (!cmd.AllowServer && !player.RealPlayer) @@ -254,7 +292,7 @@ namespace TShockAPI else { if (cmd.DoLog) - TShock.Utils.SendLogs(string.Format("{0} executed: /{1}.", player.Name, cmdText), Color.Red); + TShock.Utils.SendLogs(string.Format("{0} executed: /{1}.", player.Name, cmdText), Color.PaleVioletRed, player); cmd.Run(cmdText, player, args); } } @@ -352,7 +390,7 @@ namespace TShockAPI #region Account commands - public static void AttemptLogin(CommandArgs args) + private static void AttemptLogin(CommandArgs args) { if (args.Player.LoginAttempts > TShock.Config.MaximumLoginAttempts && (TShock.Config.MaximumLoginAttempts != -1)) { @@ -361,17 +399,22 @@ namespace TShockAPI TShock.Utils.Kick(args.Player, "Too many invalid login attempts."); return; } - + User user = TShock.Users.GetUserByName(args.Player.Name); string encrPass = ""; if (args.Parameters.Count == 1) { + if (Hooks.PlayerHooks.OnPlayerPreLogin(args.Player, args.Player.Name, args.Parameters[0])) + return; user = TShock.Users.GetUserByName(args.Player.Name); encrPass = TShock.Utils.HashPassword(args.Parameters[0]); } else if (args.Parameters.Count == 2 && TShock.Config.AllowLoginAnyUsername) { + if (Hooks.PlayerHooks.OnPlayerPreLogin(args.Player, args.Parameters[0], args.Parameters[1])) + return; + user = TShock.Users.GetUserByName(args.Parameters[0]); encrPass = TShock.Utils.HashPassword(args.Parameters[1]); if (String.IsNullOrEmpty(args.Parameters[0])) @@ -382,7 +425,7 @@ namespace TShockAPI } else { - args.Player.SendErrorMessage(String.Format("Syntax: /login{0} ", TShock.Config.AllowLoginAnyUsername ? " " : " [username]")); + args.Player.SendErrorMessage(String.Format("Syntax: /login{0} ", TShock.Config.AllowLoginAnyUsername ? " [username]" : " ")); args.Player.SendErrorMessage("If you forgot your password, there is no way to recover it."); return; } @@ -406,11 +449,13 @@ namespace TShockAPI } else if (!TShock.CheckInventory(args.Player)) { + args.Player.LoginFailsBySsi = true; args.Player.SendErrorMessage("Login failed. Please fix the above errors then /login again."); args.Player.IgnoreActionsForClearingTrashCan = true; return; } } + args.Player.LoginFailsBySsi = false; if (group.HasPermission(Permissions.ignorestackhackdetection)) args.Player.IgnoreActionsForCheating = "none"; @@ -419,6 +464,7 @@ namespace TShockAPI args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.Group = group; + args.Player.tempGroup = null; args.Player.UserAccountName = user.Name; args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName); args.Player.IsLoggedIn = true; @@ -443,7 +489,7 @@ namespace TShockAPI } - Hooks.PlayerLoginEvent.OnPlayerLogin(args.Player); + Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); } else { @@ -482,7 +528,7 @@ namespace TShockAPI } else { - args.Player.SendErrorMessage("Not logged in or invalid syntax! Syntax: /password "); + args.Player.SendErrorMessage("Not logged in or invalid syntax! Proper syntax: /password "); } } catch (UserManagerException ex) @@ -516,7 +562,7 @@ namespace TShockAPI user.Group = TShock.Config.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB. --Why? - if (TShock.Users.GetUserByName(user.Name) == null) // Cheap way of checking for existance of a user + if (TShock.Users.GetUserByName(user.Name) == null && user.Name != TSServerPlayer.AccountName) // Cheap way of checking for existance of a user { args.Player.SendSuccessMessage("Account " + user.Name + " has been registered."); args.Player.SendSuccessMessage("Your password is " + user.Password); @@ -547,41 +593,22 @@ namespace TShockAPI string subcmd = args.Parameters[0]; - // Add requires a username:password pair/ip address and a group specified. + // Add requires a username, password, and a group specified. if (subcmd == "add") { - var namepass = args.Parameters[1].Split(':'); var user = new User(); try { - if (args.Parameters.Count > 2) + if (args.Parameters.Count == 4) { - if (namepass.Length == 2) - { - user.Name = namepass[0]; - user.Password = namepass[1]; - user.Group = args.Parameters[2]; - } - else if (namepass.Length == 1) - { - user.Address = namepass[0]; - user.Group = args.Parameters[2]; - user.Name = user.Address; - } - if (!string.IsNullOrEmpty(user.Address)) - { - args.Player.SendSuccessMessage("IP address admin added. If they're logged in, tell them to rejoin."); - args.Player.SendSuccessMessage("WARNING: This is insecure! It would be better to use a user account instead."); - TShock.Users.AddUser(user); - Log.ConsoleInfo(args.Player.Name + " added IP " + user.Address + " to group " + user.Group); - } - else - { - args.Player.SendSuccessMessage("Account " + user.Name + " has been added to group " + user.Group + "!"); - TShock.Users.AddUser(user); - Log.ConsoleInfo(args.Player.Name + " added Account " + user.Name + " to group " + user.Group); - } + user.Name = args.Parameters[1]; + user.Password = args.Parameters[2]; + user.Group = args.Parameters[3]; + + args.Player.SendSuccessMessage("Account " + user.Name + " has been added to group " + user.Group + "!"); + TShock.Users.AddUser(user); + Log.ConsoleInfo(args.Player.Name + " added Account " + user.Name + " to group " + user.Group); } else { @@ -598,13 +625,7 @@ namespace TShockAPI else if (subcmd == "del" && args.Parameters.Count == 2) { var user = new User(); - if (args.Parameters[1].Split('.').Count() ==4) - - // changed to support dot character in usernames - // if (args.Parameters[1].Contains(".")) - user.Address = args.Parameters[1]; - else - user.Name = args.Parameters[1]; + user.Name = args.Parameters[1]; try { @@ -646,32 +667,16 @@ namespace TShockAPI // Group changing requires a username or IP address, and a new group to set else if (subcmd == "group") { - var user = new User(); - if (args.Parameters[1].Split('.').Count()==4) - - //changed to support dot character in usernames - //if (args.Parameters[1].Contains(".")) - - user.Address = args.Parameters[1]; - else - user.Name = args.Parameters[1]; + var user = new User(); + user.Name = args.Parameters[1]; try { if (args.Parameters.Count == 3) { - if (!string.IsNullOrEmpty(user.Address)) - { - args.Player.SendSuccessMessage("IP address " + user.Address + " has been changed to group " + args.Parameters[2] + "!"); - TShock.Users.SetUserGroup(user, args.Parameters[2]); - Log.ConsoleInfo(args.Player.Name + " changed IP address " + user.Address + " to group " + args.Parameters[2] + "."); - } - else - { - args.Player.SendSuccessMessage("Account " + user.Name + " has been changed to group " + args.Parameters[2] + "!"); - TShock.Users.SetUserGroup(user, args.Parameters[2]); - Log.ConsoleInfo(args.Player.Name + " changed account " + user.Name + " to group " + args.Parameters[2] + "."); - } + args.Player.SendSuccessMessage("Account " + user.Name + " has been changed to group " + args.Parameters[2] + "!"); + TShock.Users.SetUserGroup(user, args.Parameters[2]); + Log.ConsoleInfo(args.Player.Name + " changed account " + user.Name + " to group " + args.Parameters[2] + "."); } else { @@ -687,7 +692,7 @@ namespace TShockAPI else if (subcmd == "help") { args.Player.SendInfoMessage("Use command help:"); - args.Player.SendInfoMessage("/user add username:password group -- Adds a specified user"); + args.Player.SendInfoMessage("/user add username password group -- Adds a specified user"); args.Player.SendInfoMessage("/user del username -- Removes a specified user"); args.Player.SendInfoMessage("/user password username newpassword -- Changes a user's password"); args.Player.SendInfoMessage("/user group username newgroup -- Changes a user's group"); @@ -702,7 +707,7 @@ namespace TShockAPI #region Stupid commands - public static void ServerInfo(CommandArgs args) + private static void ServerInfo(CommandArgs args) { args.Player.SendInfoMessage("Memory usage: " + Process.GetCurrentProcess().WorkingSet64); args.Player.SendInfoMessage("Allocated memory: " + Process.GetCurrentProcess().VirtualMemorySize64); @@ -712,9 +717,10 @@ namespace TShockAPI args.Player.SendInfoMessage("Machine name: " + Environment.MachineName); } - public static void WorldInfo(CommandArgs args) + private static void WorldInfo(CommandArgs args) { args.Player.SendInfoMessage("World name: " + Main.worldName); + args.Player.SendInfoMessage("World size: {0}x{1}", Main.maxTilesX, Main.maxTilesY); args.Player.SendInfoMessage("World ID: " + Main.worldID); } @@ -805,21 +811,10 @@ namespace TShockAPI } } - private static void DeprecateBans(CommandArgs args) - { - args.Player.SendInfoMessage("All ban commands were merged into one in TShock 4.0."); - args.Player.SendInfoMessage("Syntax: /ban [option] [arguments]"); - args.Player.SendInfoMessage("Options: list, listip, clear, add, addip, del, delip"); - args.Player.SendInfoMessage("Arguments: list, listip, clear [code], add [name], addip [ip], del [name], delip [name]"); - args.Player.SendInfoMessage("In addition, a reason may be provided for all new bans after the arguments."); - return; - } - private static void Ban(CommandArgs args) { if (args.Parameters.Count == 0 || args.Parameters[0].ToLower() == "help") { - args.Player.SendInfoMessage("All ban commands were merged into one in TShock 4.0."); args.Player.SendInfoMessage("Syntax: /ban [option] [arguments]"); args.Player.SendInfoMessage("Options: list, listip, clear, add, addip, del, delip"); args.Player.SendInfoMessage("Arguments: list, listip, clear [code], add [name], addip [ip], del [name], delip [name]"); @@ -984,7 +979,7 @@ namespace TShockAPI string reason = args.Parameters.Count > 2 ? String.Join(" ", args.Parameters.GetRange(2, args.Parameters.Count - 2)) : "Misbehavior."; - if (!TShock.Utils.Ban(players[0], reason, !args.Player.RealPlayer, args.Player.Name)) + if (!TShock.Utils.Ban(players[0], reason, !args.Player.RealPlayer, args.Player.UserAccountName)) { args.Player.SendErrorMessage("You can't ban another admin!"); } @@ -999,7 +994,7 @@ namespace TShockAPI string reason = args.Parameters.Count > 2 ? String.Join(" ", args.Parameters.GetRange(2, args.Parameters.Count - 2)) : "Manually added IP address ban."; - TShock.Bans.AddBan(ip, "", reason); + TShock.Bans.AddBan(ip, "", reason, false, args.Player.UserAccountName); args.Player.SendSuccessMessage(ip + " banned."); return; #endregion Add ip ban @@ -1092,7 +1087,7 @@ namespace TShockAPI private static int ClearBansCode = -1; - public static void Whitelist(CommandArgs args) + private static void Whitelist(CommandArgs args) { if (args.Parameters.Count == 1) { @@ -1104,13 +1099,13 @@ namespace TShockAPI } } - public static void DisplayLogs(CommandArgs args) + private static void DisplayLogs(CommandArgs args) { args.Player.DisplayLogs = (!args.Player.DisplayLogs); args.Player.SendSuccessMessage("You will " + (args.Player.DisplayLogs ? "now" : "no longer") + " receive logs."); } - public static void SaveSSI(CommandArgs args ) + private static void SaveSSI(CommandArgs args) { if (TShock.Config.ServerSideInventory) { @@ -1125,31 +1120,51 @@ namespace TShockAPI } } - public static void OverrideSSI( CommandArgs args ) + private static void OverrideSSI(CommandArgs args) { + if (!TShock.Config.ServerSideInventory) + { + args.Player.SendErrorMessage("Server Side Inventory is disabled."); + return; + } if( args.Parameters.Count < 1 ) { - args.Player.SendErrorMessage("Correct usage: /overridessi(/ossi) "); + args.Player.SendErrorMessage("Correct usage: /overridessi|/ossi "); return; } - var players = TShock.Utils.FindPlayer(args.Parameters[0]); - if( players.Count < 1 ) + string playerNameToMatch = string.Join(" ", args.Parameters); + var matchedPlayers = TShock.Utils.FindPlayer(playerNameToMatch); + if( matchedPlayers.Count < 1 ) { - args.Player.SendErrorMessage("No players match " + args.Parameters[0] + "!"); + args.Player.SendErrorMessage("No players matched \"{0}\".", playerNameToMatch); + return; } - else if( players.Count > 1 ) + else if( matchedPlayers.Count > 1 ) { - args.Player.SendErrorMessage( players.Count + " players matched " + args.Parameters[0] + "!"); + args.Player.SendErrorMessage("{0} players matched \"{1}\".", matchedPlayers.Count, playerNameToMatch); + return; } - else if (TShock.Config.ServerSideInventory) + + TSPlayer matchedPlayer = matchedPlayers[0]; + if (matchedPlayer.IsLoggedIn) { - if( players[0] != null && players[0].IsLoggedIn && !players[0].IgnoreActionsForClearingTrashCan) - { - args.Player.SendSuccessMessage( players[0].Name + " has been exempted and updated."); - TShock.InventoryDB.InsertPlayerData(players[0]); - } + args.Player.SendErrorMessage("Player \"{0}\" is already logged in.", matchedPlayer.Name); + return; } + if (!matchedPlayer.LoginFailsBySsi) + { + args.Player.SendErrorMessage("Player \"{0}\" has to perform a /login attempt first.", matchedPlayer.Name); + return; + } + if (matchedPlayer.IgnoreActionsForClearingTrashCan) + { + args.Player.SendErrorMessage("Player \"{0}\" has to reconnect first.", matchedPlayer.Name); + return; + } + + TShock.InventoryDB.InsertPlayerData(matchedPlayer); + args.Player.SendSuccessMessage("SSI of player \"{0}\" has been overriden.", matchedPlayer.Name); } private static void ForceXmas(CommandArgs args) @@ -1184,21 +1199,54 @@ namespace TShockAPI (TShock.Config.ForceXmas ? "in" : "not in"))); } + private static void TempGroup(CommandArgs args) + { + if (args.Parameters.Count < 2) + { + args.Player.SendInfoMessage("Invalid usage"); + args.Player.SendInfoMessage("Usage: /tempgroup "); + return; + } + + List ply = TShock.Utils.FindPlayer(args.Parameters[0]); + if(ply.Count < 1) + { + args.Player.SendErrorMessage(string.Format("Could not find player {0}.", args.Parameters[0])); + return; + } + + if (ply.Count > 1) + { + args.Player.SendErrorMessage(string.Format("Found more than one match for {0}.", args.Parameters[0])); + return; + } + + if(!TShock.Groups.GroupExists(args.Parameters[1])) + { + args.Player.SendErrorMessage(string.Format("Could not find group {0}", args.Parameters[1])); + return; + } + + Group g = TShock.Utils.GetGroup(args.Parameters[1]); + + ply[0].tempGroup = g; + + args.Player.SendSuccessMessage(string.Format("You have changed {0}'s group to {1}", ply[0].Name, g.Name)); + ply[0].SendSuccessMessage(string.Format("Your group has temporarily been changed to {0}", g.Name)); + } + #endregion Player Management Commands #region Server Maintenence Commands private static void Broadcast(CommandArgs args) { - string message = ""; + string message = string.Join(" ", args.Parameters); - for (int i = 0; i < args.Parameters.Count; i++) - { - message += " " + args.Parameters[i]; - } - - TShock.Utils.Broadcast("(Server Broadcast)" + message, Color.Red); - return; + TShock.Utils.Broadcast( + "(Server Broadcast) " + message, + Convert.ToByte(TShock.Config.BroadcastRGB[0]), Convert.ToByte(TShock.Config.BroadcastRGB[1]), + Convert.ToByte(TShock.Config.BroadcastRGB[2])); } private static void Off(CommandArgs args) @@ -1218,7 +1266,7 @@ namespace TShockAPI string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!"); TShock.Utils.StopServer(true, reason); } - //Added restart command + private static void Restart(CommandArgs args) { if (Main.runningMono) @@ -1227,21 +1275,8 @@ namespace TShockAPI } else { - if (TShock.Config.ServerSideInventory) - { - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) - { - TShock.InventoryDB.InsertPlayerData(player); - } - } - } - string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!"); - TShock.Utils.StopServer(true, reason); - System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); - Environment.Exit(0); + TShock.Utils.RestartServer(true, reason); } } @@ -1257,6 +1292,65 @@ namespace TShockAPI ThreadPool.QueueUserWorkItem(UpdateManager.CheckUpdate); } + private static void UpdatePlugins(CommandArgs args) + { + args.Player.SendInfoMessage("Starting plugin update process:"); + args.Player.SendInfoMessage("This may take a while, do not turn off the server!"); + new PluginUpdaterThread(args.Player); + } + + private static void ManageRest(CommandArgs args) + { + string subCommand = "help"; + if (args.Parameters.Count > 0) + subCommand = args.Parameters[0]; + + switch(subCommand.ToLower()) + { + case "listusers": + { + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; + + Dictionary restUsersTokens = new Dictionary(); + foreach (Rests.SecureRest.TokenData tokenData in TShock.RestApi.Tokens.Values) + { + if (restUsersTokens.ContainsKey(tokenData.Username)) + restUsersTokens[tokenData.Username]++; + else + restUsersTokens.Add(tokenData.Username, 1); + } + + List restUsers = new List( + restUsersTokens.Select(ut => string.Format("{0} ({1} tokens)", ut.Key, ut.Value))); + + PaginationTools.SendPage( + args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(restUsers), new PaginationTools.Settings { + NothingToDisplayString = "There are currently no active REST users.", + HeaderFormat = "Active REST Users ({0}/{1}):", + FooterFormat = "Type /rest listusers {0} for more." + } + ); + + break; + } + case "destroytokens": + { + TShock.RestApi.Tokens.Clear(); + args.Player.SendSuccessMessage("All REST tokens have been destroyed."); + break; + } + default: + { + args.Player.SendInfoMessage("Available REST Sub-Commands:"); + args.Player.SendMessage("listusers - Lists all REST users and their current active tokens.", Color.White); + args.Player.SendMessage("destroytokens - Destroys all current REST tokens.", Color.White); + break; + } + } + } + #endregion Server Maintenence Commands #region Cause Events and Spawn Monsters Commands @@ -1667,25 +1761,6 @@ namespace TShockAPI args.Player.TPAllow = !args.Player.TPAllow; } - private static void DeprecateWarp(CommandArgs args) - { - if (args.Player.Group.HasPermission(Permissions.managewarp)) - { - args.Player.SendInfoMessage("All warp commands were merged into one in TShock 4.0."); - args.Player.SendInfoMessage("Previous warps with spaces should be wrapped in single quotes."); - args.Player.SendInfoMessage("Invalid syntax. Syntax: /warp [command] [arguments]"); - args.Player.SendInfoMessage("Commands: add, del, hide, list, send, [warpname]"); - args.Player.SendInfoMessage("Arguments: add [warp name], del [warp name], list [page]"); - args.Player.SendInfoMessage("Arguments: send [player] [warp name], hide [warp name] [Enable(true/false)]"); - args.Player.SendInfoMessage("Examples: /warp add foobar, /warp hide foobar true, /warp foobar"); - } - else - { - args.Player.SendErrorMessage("Invalid syntax. Syntax: /warp [name] or /warp list "); - args.Player.SendErrorMessage("Previous warps with spaces should be wrapped in single quotes."); - } - } - private static void Warp(CommandArgs args) { bool hasManageWarpPermission = args.Player.Group.HasPermission(Permissions.managewarp); @@ -1693,9 +1768,7 @@ namespace TShockAPI { if (hasManageWarpPermission) { - args.Player.SendInfoMessage("All warp commands were merged into one in TShock 4.0."); - args.Player.SendInfoMessage("Previous warps with spaces should be wrapped in single quotes."); - args.Player.SendInfoMessage("Invalid syntax. Syntax: /warp [command] [arguments]"); + args.Player.SendInfoMessage("Invalid syntax! Proper syntax: /warp [command] [arguments]"); args.Player.SendInfoMessage("Commands: add, del, hide, list, send, [warpname]"); args.Player.SendInfoMessage("Arguments: add [warp name], del [warp name], list [page]"); args.Player.SendInfoMessage("Arguments: send [player] [warp name], hide [warp name] [Enable(true/false)]"); @@ -1704,65 +1777,26 @@ namespace TShockAPI } else { - args.Player.SendErrorMessage("Invalid syntax. Syntax: /warp [name] or /warp list "); - args.Player.SendErrorMessage("Previous warps with spaces should be wrapped in single quotes."); - + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /warp [name] or /warp list "); return; } } if (args.Parameters[0].Equals("list")) { - #region - //How many warps per page - const int pagelimit = 15; - //How many warps 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.SendErrorMessage(string.Format("Invalid page number ({0})", page)); - return; - } - page--; //Substract 1 as pages are parsed starting at 1 and not 0 - } - - var warps = TShock.Warps.ListAllPublicWarps(Main.worldID.ToString()); - - //Check if they are trying to access a page that doesn't exist. - int pagecount = warps.Count/pagelimit; - if (page > pagecount) - { - args.Player.SendErrorMessage(string.Format("Page number exceeds pages ({0}/{1}).", page + 1, pagecount + 1)); + #region List warps + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) return; - } - - //Display the current page and the number of pages. - args.Player.SendSuccessMessage(string.Format("Current warps ({0}/{1}):", page + 1, pagecount + 1)); - - //Add up to pagelimit names to a list - var nameslist = new List(); - for (int i = (page*pagelimit); (i < ((page*pagelimit) + pagelimit)) && i < warps.Count; i++) - { - nameslist.Add(warps[i].WarpName); - } - - //convert the list to an array for joining - var names = nameslist.ToArray(); - for (int i = 0; i < names.Length; i += perline) - { - args.Player.SendInfoMessage(string.Join(", ", names, i, Math.Min(names.Length - i, perline))); - } - - if (page < pagecount) - { - args.Player.SendInfoMessage(string.Format("Type /warp list {0} for more warps.", (page + 2))); - } + IEnumerable warpNames = from warp in TShock.Warps.ListAllPublicWarps(Main.worldID.ToString()) + select warp.WarpName; + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(warpNames), + new PaginationTools.Settings + { + HeaderFormat = "Warps ({0}/{1}):", + FooterFormat = "Type /warp list {0} for more.", + NothingToDisplayString = "There are currently no warps defined." + }); #endregion } else if (args.Parameters[0].ToLower() == "add" && hasManageWarpPermission) @@ -1787,7 +1821,6 @@ namespace TShockAPI else args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /warp add [name]"); #endregion - } else if (args.Parameters[0].ToLower() == "del" && hasManageWarpPermission) { @@ -1803,7 +1836,6 @@ namespace TShockAPI else args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /warp del [name]"); #endregion - } else if (args.Parameters[0].ToLower() == "hide" && hasManageWarpPermission) { @@ -1867,7 +1899,6 @@ namespace TShockAPI args.Player.SendErrorMessage("Specified warp not found."); } #endregion - } else { @@ -1889,341 +1920,442 @@ namespace TShockAPI #region Group Management - private static void AddGroup(CommandArgs args) + private static void GroupDeprecated(CommandArgs args) { - if (args.Parameters.Count > 0) - { - String groupname = args.Parameters[0]; - args.Parameters.RemoveAt(0); - String permissions = String.Join(",", args.Parameters); - - String response = TShock.Groups.AddGroup(groupname, permissions); - if (response.Length > 0) - args.Player.SendSuccessMessage(response); - } - else - { - args.Player.SendErrorMessage("Incorrect format: /addgroup [optional permissions]"); - } + args.Player.SendInfoMessage("The group commands were merged into /group in TShock 4.1; check /group help."); } - private static void DeleteGroup(CommandArgs args) + private static void Group(CommandArgs args) { - if (args.Parameters.Count > 0) + if (args.Parameters.Count == 0) { - String groupname = args.Parameters[0]; - - String response = TShock.Groups.DeleteGroup(groupname); - if (response.Length > 0) - args.Player.SendSuccessMessage(response); + args.Player.SendInfoMessage("Invalid syntax! Proper syntax: /group [arguments]"); + args.Player.SendInfoMessage("Commands: add, addperm, del, delperm, list, listperm"); + args.Player.SendInfoMessage("Arguments: add , addperm , del "); + args.Player.SendInfoMessage("Arguments: delperm , list [page], listperm [page]"); + return; } - else + + switch (args.Parameters[0].ToLower()) { - args.Player.SendErrorMessage("Incorrect format: /delgroup "); - } - } - - private static void ModifyGroup(CommandArgs args) - { - if (args.Parameters.Count > 2) - { - String com = args.Parameters[0]; - args.Parameters.RemoveAt(0); - - String groupname = args.Parameters[0]; - args.Parameters.RemoveAt(0); - - string response = ""; - if (com.Equals("add")) - { - if( groupname == "*" ) + case "add": + #region Add group { - int count = 0; - foreach( Group g in TShock.Groups ) + if (args.Parameters.Count < 2) { - response = TShock.Groups.AddPermissions(g.Name, args.Parameters); - if (!response.StartsWith("Error:")) - count++; - } - args.Player.SendSuccessMessage(String.Format("{0} groups were modified.", count )); - return; - } - response = TShock.Groups.AddPermissions(groupname, args.Parameters); - if (response.Length > 0) - args.Player.SendSuccessMessage(response); - return; - } - - if (com.Equals("del") || com.Equals("delete")) - { - if (groupname == "*") - { - int count = 0; - foreach (Group g in TShock.Groups) - { - response = TShock.Groups.DeletePermissions(g.Name, args.Parameters); - if (!response.StartsWith("Error:")) - count++; - } - args.Player.SendSuccessMessage(String.Format("{0} groups were modified.", count)); - return; - } - response = TShock.Groups.DeletePermissions(groupname, args.Parameters); - if (response.Length > 0) - args.Player.SendSuccessMessage(response); - return; - } - } - args.Player.SendErrorMessage("Incorrect format: /modgroup add|del "); - } - - private static void ViewGroups(CommandArgs args) - { - if (args.Parameters.Count > 0) - { - String com = args.Parameters[0]; - - if( com == "list" ) - { - string ret = "Groups: "; - foreach( Group g in TShock.Groups.groups ) - { - if (ret.Length > 50) - { - args.Player.SendSuccessMessage(ret); - ret = ""; - } - - if( ret != "" ) - { - ret += ", "; - } - - ret += g.Name; - } - - if (ret.Length > 0) - { - args.Player.SendSuccessMessage(ret); - } - return; - } - else if( com == "perm") - { - if (args.Parameters.Count > 1) - { - String groupname = args.Parameters[1]; - - if( TShock.Groups.GroupExists( groupname ) ) - { - string ret = String.Format("Permissions for {0}: ", groupname); - foreach (string p in TShock.Utils.GetGroup( groupname ).permissions) - { - if (ret.Length > 50) - { - args.Player.SendSuccessMessage(ret); - ret = ""; - } - - if (ret != "") - { - ret += ", "; - } - - ret += p; - } - if (ret.Length > 0) - { - args.Player.SendSuccessMessage(ret); - } - + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /group add [permissions]"); return; } + + string groupName = args.Parameters[1]; + args.Parameters.RemoveRange(0, 2); + string permissions = String.Join(",", args.Parameters); + + try + { + string response = TShock.Groups.AddGroup(groupName, permissions); + if (response.Length > 0) + { + args.Player.SendSuccessMessage(response); + } + } + catch (GroupManagerException ex) + { + args.Player.SendErrorMessage(ex.ToString()); + } + } + #endregion + return; + case "addperm": + #region Add permissions + { + if (args.Parameters.Count < 3) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /group addperm "); + return; + } + + string groupName = args.Parameters[1]; + args.Parameters.RemoveRange(0, 2); + if (groupName == "*") + { + foreach (Group g in TShock.Groups) + { + TShock.Groups.AddPermissions(g.Name, args.Parameters); + } + args.Player.SendSuccessMessage("Modified all groups."); + return; + } + try + { + string response = TShock.Groups.AddPermissions(groupName, args.Parameters); + if (response.Length > 0) + { + args.Player.SendSuccessMessage(response); + } + return; + } + catch (GroupManagerException ex) + { + args.Player.SendErrorMessage(ex.ToString()); + } + } + #endregion + return; + + case "parent": + #region Parent + { + if (args.Parameters.Count < 2) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /group parent [new parent group name]"); + return; + } + + string groupName = args.Parameters[1]; + Group group = TShock.Groups.GetGroupByName(groupName); + if (group == null) + { + args.Player.SendErrorMessage("No such group \"{0}\".", groupName); + return; + } + + if (args.Parameters.Count > 2) + { + string newParentGroupName = string.Join(" ", args.Parameters.Skip(2)); + if (!string.IsNullOrWhiteSpace(newParentGroupName) && !TShock.Groups.GroupExists(newParentGroupName)) + { + args.Player.SendErrorMessage("No such group \"{0}\".", newParentGroupName); + return; + } + + try + { + TShock.Groups.UpdateGroup(groupName, newParentGroupName, group.Permissions, group.ChatColor); + + if (!string.IsNullOrWhiteSpace(newParentGroupName)) + args.Player.SendSuccessMessage("Parent of group \"{0}\" set to \"{1}\".", groupName, newParentGroupName); + else + args.Player.SendSuccessMessage("Removed parent of group \"{0}\".", groupName); + } + catch (GroupManagerException ex) + { + args.Player.SendErrorMessage(ex.Message); + } + } else { - args.Player.SendErrorMessage("Group does not exist."); - return; + if (group.Parent != null) + args.Player.SendSuccessMessage("Parent of \"{0}\" is \"{1}\".", group.Name, group.Parent.Name); + else + args.Player.SendSuccessMessage("Group \"{0}\" has no parent.", group.Name); } } - } - } - args.Player.SendErrorMessage("Incorrect format: /group list"); - args.Player.SendErrorMessage(" /group perm "); - } + #endregion + return; + case "del": + #region Delete group + { + if (args.Parameters.Count != 2) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /group del "); + return; + } + try + { + string response = TShock.Groups.DeleteGroup(args.Parameters[1]); + if (response.Length > 0) + { + args.Player.SendSuccessMessage(response); + } + } + catch (GroupManagerException ex) + { + args.Player.SendErrorMessage(ex.ToString()); + } + } + #endregion + return; + case "delperm": + #region Delete permissions + { + if (args.Parameters.Count < 3) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /group delperm "); + return; + } + + string groupName = args.Parameters[1]; + args.Parameters.RemoveRange(0, 2); + if (groupName == "*") + { + foreach (Group g in TShock.Groups) + { + TShock.Groups.DeletePermissions(g.Name, args.Parameters); + } + args.Player.SendSuccessMessage("Modified all groups."); + return; + } + try + { + string response = TShock.Groups.DeletePermissions(groupName, args.Parameters); + if (response.Length > 0) + { + args.Player.SendSuccessMessage(response); + } + return; + } + catch (GroupManagerException ex) + { + args.Player.SendErrorMessage(ex.ToString()); + } + } + #endregion + return; + case "list": + #region List groups + { + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; + IEnumerable groupNames = from grp in TShock.Groups.groups + select grp.Name; + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(groupNames), + new PaginationTools.Settings + { + HeaderFormat = "Groups ({0}/{1}):", + FooterFormat = "Type /group list {0} for more." + }); + } + #endregion + return; + case "listperm": + #region List permissions + { + if (args.Parameters.Count == 1) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /group listperm [page]"); + return; + } + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 2, args.Player, out pageNumber)) + return; + + if (!TShock.Groups.GroupExists(args.Parameters[1])) + { + args.Player.SendErrorMessage("Invalid group."); + return; + } + Group grp = TShock.Utils.GetGroup(args.Parameters[1]); + List permissions = grp.TotalPermissions; + + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(permissions), + new PaginationTools.Settings + { + HeaderFormat = "Permissions for " + grp.Name + " ({0}/{1}):", + FooterFormat = "Type /group permlist " + grp.Name + " {0} for more.", + NothingToDisplayString = "There are currently no permissions for " + grp.Name + "." + }); + } + #endregion + return; + case "help": + args.Player.SendInfoMessage("Syntax: /group [arguments]"); + args.Player.SendInfoMessage("Commands: add, addperm, parent, del, delperm, list, listperm"); + args.Player.SendInfoMessage("Arguments: add , addperm , del "); + args.Player.SendInfoMessage("Arguments: delperm , list [page], listperm [page]"); + return; + } + } #endregion Group Management #region Item Management - private static void AddItem(CommandArgs args) + private static void ItemBanDeprecated(CommandArgs args) { - if (args.Parameters.Count == 1) - { - var items = TShock.Utils.GetItemByIdOrName(args.Parameters[0]); - if (items.Count == 0) - { - args.Player.SendErrorMessage("Invalid item type!"); - } - else if (items.Count > 1) - { - args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched!", items.Count)); - } - else - { - var item = items[0]; - if (item.type >= 1) - { - TShock.Itembans.AddNewBan(item.name); - args.Player.SendErrorMessage(item.name + " has been banned."); - } - else - { - args.Player.SendErrorMessage("Invalid item type!"); - } - } - } - else - { - args.Player.SendErrorMessage("Invalid use: /additem \"item name\" or /additem ##."); - } + args.Player.SendInfoMessage("The item ban commands were merged into /itemban in TShock 4.1; check /itemban help."); } - private static void DeleteItem(CommandArgs args) + private static void ItemBan(CommandArgs args) { - if (args.Parameters.Count == 1) + if (args.Parameters.Count == 0) { - var items = TShock.Utils.GetItemByIdOrName(args.Parameters[0]); - if (items.Count == 0) - { - args.Player.SendErrorMessage("Invalid item type!"); - } - else if (items.Count > 1) - { - args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched!", items.Count)); - } - else - { - var item = items[0]; - if (item.type >= 1) - { - TShock.Itembans.RemoveBan(item.name); - args.Player.SendSuccessMessage(item.name + " has been unbanned."); - } - else - { - args.Player.SendErrorMessage("Invalid item type!"); - } - } + args.Player.SendInfoMessage("Invalid syntax! Proper syntax: /itemban [arguments]"); + args.Player.SendInfoMessage("Commands: add, allow, del, disallow, list"); + args.Player.SendInfoMessage("Arguments: add , allow "); + args.Player.SendInfoMessage("Arguments: del , disallow , list [page]"); + return; } - else + + switch (args.Parameters[0].ToLower()) { - args.Player.SendErrorMessage("Invalid use: /delitem \"item name\" or /delitem ##"); - } - } - - private static void ListItems(CommandArgs args) - { - args.Player.SendInfoMessage("The banned items are: " + String.Join(",", TShock.Itembans.ItemBans) + "."); - } - - private static void AddItemGroup(CommandArgs args) - { - if (args.Parameters.Count == 2) - { - var items = TShock.Utils.GetItemByIdOrName(args.Parameters[0]); - if (items.Count == 0) - { - args.Player.SendErrorMessage("Invalid item type!"); - } - else if (items.Count > 1) - { - args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched!", items.Count)); - } - else - { - var item = items[0]; - if (item.type >= 1) + case "add": + #region Add item { - if(TShock.Groups.GroupExists(args.Parameters[1])) + if (args.Parameters.Count != 2) { - ItemBan ban = TShock.Itembans.GetItemBanByName(item.name); - - if(!ban.AllowedGroups.Contains(args.Parameters[1])) - { - TShock.Itembans.AllowGroup(item.name, args.Parameters[1]); - args.Player.SendSuccessMessage("Banned item " + item.name + " has been allowed for group " + args.Parameters[1] + "."); - } - else - { - args.Player.SendWarningMessage("Banned item " + item.name + " is already allowed for group " + args.Parameters[1] + "!"); - } + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /itemban add "); + return; + } + + List items = TShock.Utils.GetItemByIdOrName(args.Parameters[1]); + if (items.Count == 0) + { + args.Player.SendErrorMessage("Invalid item."); + } + else if (items.Count > 1) + { + args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched.", items.Count)); } else { - args.Player.SendErrorMessage("Group " + args.Parameters[1] + " not found!"); + TShock.Itembans.AddNewBan(items[0].name); + args.Player.SendSuccessMessage("Banned " + items[0].name + "."); } } - else + #endregion + return; + case "allow": + #region Allow group to item { - args.Player.SendErrorMessage("Invalid item type!"); - } - } - } - else - { - args.Player.SendErrorMessage("Invalid use: /additemgroup \"item name\" \"group name\""); - } - } - - private static void DeleteItemGroup(CommandArgs args) - { - if (args.Parameters.Count == 2) - { - var items = TShock.Utils.GetItemByIdOrName(args.Parameters[0]); - if (items.Count == 0) - { - args.Player.SendErrorMessage("Invalid item type!"); - } - else if (items.Count > 1) - { - args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched!", items.Count)); - } - else - { - var item = items[0]; - if (item.type >= 1) - { - if(TShock.Groups.GroupExists(args.Parameters[1])) + if (args.Parameters.Count != 3) { - ItemBan ban = TShock.Itembans.GetItemBanByName(item.name); - - if(ban.AllowedGroups.Contains(args.Parameters[1])) - { - TShock.Itembans.RemoveGroup(item.name, args.Parameters[1]); - args.Player.SendSuccessMessage("Removed access for group " + args.Parameters[1] + " to banned item " + item.name + "."); - } - else - { - args.Player.SendWarningMessage("Group " + args.Parameters[1] + " did not have access to banned item " + item.name + "!"); - } + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /itemban allow "); + return; + } + + List items = TShock.Utils.GetItemByIdOrName(args.Parameters[1]); + if (items.Count == 0) + { + args.Player.SendErrorMessage("Invalid item."); + } + else if (items.Count > 1) + { + args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched.", items.Count)); } else { - args.Player.SendErrorMessage("Group " + args.Parameters[1] + " not found!"); + if (!TShock.Groups.GroupExists(args.Parameters[2])) + { + args.Player.SendErrorMessage("Invalid group."); + return; + } + + ItemBan ban = TShock.Itembans.GetItemBanByName(items[0].name); + if (ban == null) + { + args.Player.SendErrorMessage(items[0].name + " is not banned."); + return; + } + if (!ban.AllowedGroups.Contains(args.Parameters[2])) + { + TShock.Itembans.AllowGroup(items[0].name, args.Parameters[2]); + args.Player.SendSuccessMessage(String.Format("{0} has been allowed to use {1}.", args.Parameters[2], items[0].name)); + } + else + { + args.Player.SendWarningMessage(String.Format("{0} is already allowed to use {1}.", args.Parameters[2], items[0].name)); + } } } - else + #endregion + return; + case "del": + #region Delete item { - args.Player.SendErrorMessage("Invalid item type!"); + if (args.Parameters.Count != 2) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /itemban del "); + return; + } + + List items = TShock.Utils.GetItemByIdOrName(args.Parameters[1]); + if (items.Count == 0) + { + args.Player.SendErrorMessage("Invalid item."); + } + else if (items.Count > 1) + { + args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched.", items.Count)); + } + else + { + TShock.Itembans.RemoveBan(items[0].name); + args.Player.SendSuccessMessage("Unbanned " + items[0].name + "."); + } } - } - } - else - { - args.Player.SendErrorMessage("Invalid use: /delitemgroup \"item name\" \"group name\""); + #endregion + return; + case "disallow": + #region Allow group to item + { + if (args.Parameters.Count != 3) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /itemban disallow "); + return; + } + + List items = TShock.Utils.GetItemByIdOrName(args.Parameters[1]); + if (items.Count == 0) + { + args.Player.SendErrorMessage("Invalid item."); + } + else if (items.Count > 1) + { + args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched.", items.Count)); + } + else + { + if (!TShock.Groups.GroupExists(args.Parameters[2])) + { + args.Player.SendErrorMessage("Invalid group."); + return; + } + + ItemBan ban = TShock.Itembans.GetItemBanByName(items[0].name); + if (ban == null) + { + args.Player.SendErrorMessage(items[0].name + " is not banned."); + return; + } + if (ban.AllowedGroups.Contains(args.Parameters[2])) + { + TShock.Itembans.RemoveGroup(items[0].name, args.Parameters[2]); + args.Player.SendSuccessMessage(String.Format("{0} has been disallowed to use {1}.", args.Parameters[2], items[0].name)); + } + else + { + args.Player.SendWarningMessage(String.Format("{0} is already disallowed to use {1}.", args.Parameters[2], items[0].name)); + } + } + } + #endregion + return; + case "help": + args.Player.SendInfoMessage("Syntax: /itemban [arguments]"); + args.Player.SendInfoMessage("Commands: add, allow, del, disallow, list"); + args.Player.SendInfoMessage("Arguments: add , allow "); + args.Player.SendInfoMessage("Arguments: del , disallow , list [page]"); + return; + case "list": + #region List items + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; + IEnumerable itemNames = from itemBan in TShock.Itembans.ItemBans + select itemBan.Name; + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(itemNames), + new PaginationTools.Settings + { + HeaderFormat = "Item bans ({0}/{1}):", + FooterFormat = "Type /itemban list {0} for more.", + NothingToDisplayString = "There are currently no banned items." + }); + #endregion + return; } } - #endregion Item Management #region Server Config Commands @@ -2238,11 +2370,8 @@ namespace TShockAPI private static void Reload(CommandArgs args) { - FileTools.SetupConfig(); - TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); - TShock.Groups.LoadPermisions(); - //todo: Create an event for reloads to propegate to plugins. - TShock.Regions.ReloadAllRegions(); + TShock.Utils.Reload(args.Player); + args.Player.SendSuccessMessage( "Configuration, permissions, and regions reload complete. Some changes may require a server restart."); } @@ -2467,6 +2596,7 @@ namespace TShockAPI { args.Player.SendMessage("Hit a block to get the name of the region", Color.Yellow); args.Player.AwaitingName = true; + args.Player.AwaitingNameParameters = args.Parameters.Skip(1).ToArray(); } break; } @@ -2482,7 +2612,7 @@ namespace TShockAPI } else { - args.Player.SendMessage("Invalid syntax! Proper syntax: /region set [1/2]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region set <1/2>", Color.Red); } break; } @@ -2516,7 +2646,7 @@ namespace TShockAPI } } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region define [name]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region define ", Color.Red); break; } case "protect": @@ -2539,10 +2669,10 @@ namespace TShockAPI 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); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region protect ", Color.Red); } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region protect [name] [true/false]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region protect ", Color.Red); break; } case "delete": @@ -2556,7 +2686,7 @@ namespace TShockAPI args.Player.SendMessage("Could not find specified region", Color.Red); } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region delete [name]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region delete ", Color.Red); break; } case "clear": @@ -2600,7 +2730,7 @@ namespace TShockAPI } } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow [name] [region]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow ", Color.Red); break; } case "remove": @@ -2635,7 +2765,7 @@ namespace TShockAPI } } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region remove [name] [region]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region remove ", Color.Red); break; case "allowg": { @@ -2670,7 +2800,7 @@ namespace TShockAPI } } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow [group] [region]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region allowg ", Color.Red); break; } case "removeg": @@ -2705,93 +2835,132 @@ namespace TShockAPI } } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region removeg [group] [region]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region removeg ", 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; + case "list": + { + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber)) + return; - - 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; - } + IEnumerable regionNames = from region in TShock.Regions.Regions + where region.WorldID == Main.worldID.ToString() + select region.Name; + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(regionNames), + new PaginationTools.Settings + { + HeaderFormat = "Regions ({0}/{1}):", + FooterFormat = "Type /region list {0} for more.", + NothingToDisplayString = "There are currently no regions defined." + }); + break; + } case "info": { - if (args.Parameters.Count > 1) + if (args.Parameters.Count == 1 || args.Parameters.Count > 4) { - string regionName = String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)); - Region r = TShock.Regions.GetRegionByName(regionName); + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /region info [-d] [page]"); + break; + } - if (r == null) - { - args.Player.SendMessage("Region {0} does not exist"); - break; - } + string regionName = args.Parameters[1]; + bool displayBoundaries = args.Parameters.Skip(2).Any( + p => p.Equals("-d", StringComparison.InvariantCultureIgnoreCase) + ); - 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) + Region region = TShock.Regions.GetRegionByName(regionName); + if (region == null) + { + args.Player.SendErrorMessage("Region \"{0}\" does not exist.", regionName); + break; + } + + int pageNumberIndex = displayBoundaries ? 3 : 2; + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, pageNumberIndex, args.Player, out pageNumber)) + break; + + List lines = new List + { + string.Format("X: {0}; Y: {1}; W: {2}; H: {3}, Z: {4}", region.Area.X, region.Area.Y, region.Area.Width, region.Area.Height, region.Z), + string.Concat("Owner: ", region.Owner), + string.Concat("Protected: ", region.DisableBuild.ToString()), + }; + + if (region.AllowedIDs.Count > 0) + { + IEnumerable sharedUsersSelector = region.AllowedIDs.Select(userId => { - var user = TShock.Users.GetUserByID(s); - args.Player.SendMessage(r.Name + ": " + (user != null ? user.Name : "Unknown")); - } + User user = TShock.Users.GetUserByID(userId); + if (user != null) + return user.Name; + else + return string.Concat("{ID: ", userId, "}"); + }); + List extraLines = PaginationTools.BuildLinesFromTerms(sharedUsersSelector.Distinct()); + extraLines[0] = "Shared with: " + extraLines[0]; + lines.AddRange(extraLines); } else { - args.Player.SendMessage("Invalid syntax! Proper syntax: /region info [name]", Color.Red); + lines.Add("Region is not shared with any users."); + } + + if (region.AllowedGroups.Count > 0) + { + List extraLines = PaginationTools.BuildLinesFromTerms(region.AllowedGroups.Distinct()); + extraLines[0] = "Shared with groups: " + extraLines[0]; + lines.AddRange(extraLines); + } + else + { + lines.Add("Region is not shared with any groups."); + } + + PaginationTools.SendPage( + args.Player, pageNumber, lines, new PaginationTools.Settings + { + HeaderFormat = string.Format("Information About Region \"{0}\" ({{0}}/{{1}}):", region.Name), + FooterFormat = string.Format("Type /region info {0} {{0}} for more information.", regionName) + } + ); + + if (displayBoundaries) + { + Rectangle regionArea = region.Area; + foreach (Point boundaryPoint in Utils.Instance.EnumerateRegionBoundaries(regionArea)) + { + // Preferring dotted lines as those should easily be distinguishable from actual wires. + if ((boundaryPoint.X + boundaryPoint.Y & 1) == 0) + { + // Could be improved by sending raw tile data to the client instead but not really + // worth the effort as chances are very low that overwriting the wire for a few + // nanoseconds will cause much trouble. + Tile tile = Main.tile[boundaryPoint.X, boundaryPoint.Y]; + bool oldWireState = tile.wire; + tile.wire = true; + + try { + args.Player.SendTileSquare(boundaryPoint.X, boundaryPoint.Y, 1); + } finally { + tile.wire = oldWireState; + } + } + } + + Timer boundaryHideTimer = null; + boundaryHideTimer = new Timer((state) => { + foreach (Point boundaryPoint in Utils.Instance.EnumerateRegionBoundaries(regionArea)) + if ((boundaryPoint.X + boundaryPoint.Y & 1) == 0) + args.Player.SendTileSquare(boundaryPoint.X, boundaryPoint.Y, 1); + + // ReSharper disable AccessToModifiedClosure + Debug.Assert(boundaryHideTimer != null); + boundaryHideTimer.Dispose(); + // ReSharper restore AccessToModifiedClosure + }, + null, 5000, Timeout.Infinite + ); } break; @@ -2810,10 +2979,10 @@ namespace TShockAPI args.Player.SendMessage("Could not find specified region", Color.Red); } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region z [name] [#]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region z <#>", Color.Red); } else - args.Player.SendMessage("Invalid syntax! Proper syntax: /region z [name] [#]", Color.Red); + args.Player.SendMessage("Invalid syntax! Proper syntax: /region z <#>", Color.Red); break; } case "resize": @@ -2863,27 +3032,79 @@ namespace TShockAPI } else { - args.Player.SendMessage("Invalid syntax! Proper syntax: /region resize [regionname] [u/d/l/r] [amount]", + args.Player.SendMessage("Invalid syntax! Proper syntax: /region resize ", Color.Red); } } else { - args.Player.SendMessage("Invalid syntax! Proper syntax: /region resize [regionname] [u/d/l/r] [amount]1", + args.Player.SendMessage("Invalid syntax! Proper syntax: /region resize ", Color.Red); } break; } + case "tp": + { + if (!args.Player.Group.HasPermission(Permissions.tp)) + { + args.Player.SendErrorMessage("You don't have the necessary permission to do that."); + break; + } + if (args.Parameters.Count <= 1) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /region tp ."); + break; + } + + string regionName = string.Join(" ", args.Parameters.Skip(1)); + Region region = TShock.Regions.GetRegionByName(regionName); + if (region == null) + { + args.Player.SendErrorMessage("Region \"{0}\" does not exist.", regionName); + break; + } + + args.Player.Teleport(region.Area.Center.X, region.Area.Center.Y + 3); + + 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); + int pageNumber; + int pageParamIndex = 0; + if (args.Parameters.Count > 1) + pageParamIndex = 1; + if (!PaginationTools.TryParsePageNumber(args.Parameters, pageParamIndex, args.Player, out pageNumber)) + return; + + List lines = new List { + "set <1/2> - Sets the temporary region points.", + "clear - Clears the temporary region points.", + "define - Defines the region with the given name.", + "delete - Deletes the given region.", + "name [-u][-z][-p] - Shows the name of the region at the given point.", + "list - Lists all regions.", + "resize - Resizes a region.", + "allow - Allows a user to a region.", + "remove - Removes a user from a region.", + "allowg - Allows a user group to a region.", + "removeg - Removes a user group from a region.", + "info [-d] - Displays several information about the given region.", + "protect - Sets whether the tiles inside the region are protected or not.", + "z <#> - Sets the z-order of the region.", + }; + if (args.Player.Group.HasPermission(Permissions.tp)) + lines.Add("tp - Teleports you to the given region's center."); + + PaginationTools.SendPage( + args.Player, pageNumber, lines, + new PaginationTools.Settings + { + HeaderFormat = "Available Region Sub-Commands ({0}/{1}):", + FooterFormat = "Type /region {0} for more sub-commands." + } + ); break; } } @@ -2911,42 +3132,18 @@ namespace TShockAPI private static void Help(CommandArgs args) { - args.Player.SendInfoMessage("TShock Commands:"); - int page = 1; - if (args.Parameters.Count > 0) - int.TryParse(args.Parameters[0], out page); - var cmdlist = new List(); - for (int j = 0; j < ChatCommands.Count; j++) - { - if (ChatCommands[j].CanRun(args.Player)) + int pageNumber; + if (!PaginationTools.TryParsePageNumber(args.Parameters, 0, args.Player, out pageNumber)) + return; + IEnumerable cmdNames = from cmd in ChatCommands + where cmd.CanRun(args.Player) && (cmd.Name != "auth" || TShock.AuthToken != 0) + select "/" + cmd.Name; + PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(cmdNames), + new PaginationTools.Settings { - cmdlist.Add(ChatCommands[j]); - } - } - var sb = new StringBuilder(); - if (cmdlist.Count > (15*(page - 1))) - { - for (int j = (15*(page - 1)); j < (15*page); j++) - { - if (sb.Length != 0) - sb.Append(", "); - sb.Append("/").Append(cmdlist[j].Name); - if (j == cmdlist.Count - 1) - { - args.Player.SendInfoMessage(sb.ToString()); - break; - } - if ((j + 1)%5 == 0) - { - args.Player.SendInfoMessage(sb.ToString()); - sb.Clear(); - } - } - } - if (cmdlist.Count > (15*page)) - { - args.Player.SendInfoMessage(string.Format("Type /help {0} for more commands.", (page + 1))); - } + HeaderFormat = "Commands ({0}/{1}):", + FooterFormat = "Type /help {0} for more." + }); } private static void GetVersion(CommandArgs args) @@ -2957,59 +3154,47 @@ namespace TShockAPI private static void ListConnectedPlayers(CommandArgs args) { - //How many players per page - const int pagelimit = 15; - //How many players per line - const int perline = 5; - //Pages start at 0 but are displayed and parsed at 1 - int page = 0; + bool invalidUsage = (args.Parameters.Count > 2); - - if (args.Parameters.Count > 0) + bool displayIdsRequested = false; + int pageNumber = 1; + if (!invalidUsage) { - if (!int.TryParse(args.Parameters[0], out page) || page < 1) + foreach (string parameter in args.Parameters) { - args.Player.SendErrorMessage(string.Format("Invalid page number ({0})", page)); - return; + if (parameter.Equals("-i", StringComparison.InvariantCultureIgnoreCase)) + { + displayIdsRequested = true; + continue; + } + + if (!int.TryParse(parameter, out pageNumber)) + { + invalidUsage = true; + break; + } } - page--; //Substract 1 as pages are parsed starting at 1 and not 0 } - - var playerList = args.Player.Group.HasPermission(Permissions.seeids) - ? TShock.Utils.GetPlayers(true) - : TShock.Utils.GetPlayers(false); - - //Check if they are trying to access a page that doesn't exist. - int pagecount = playerList.Count / pagelimit; - if (page > pagecount) + if (invalidUsage) { - args.Player.SendErrorMessage(string.Format("Page number exceeds pages ({0}/{1})", page + 1, pagecount + 1)); + args.Player.SendErrorMessage("Invalid usage, proper usage: /who [-i] [pagenumber]"); + return; + } + if (displayIdsRequested && !args.Player.Group.HasPermission(Permissions.seeids)) + { + args.Player.SendErrorMessage("You don't have the required permission to list player ids."); return; } - //Display the current page and the number of pages. - args.Player.SendSuccessMessage(string.Format("Players: {0}/{1}", - TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); - args.Player.SendSuccessMessage(string.Format("Current players page {0}/{1}:", page + 1, pagecount + 1)); - - //Add up to pagelimit names to a list - var nameslist = new List(); - for (int i = (page * pagelimit); (i < ((page * pagelimit) + pagelimit)) && i < playerList.Count; i++) - { - nameslist.Add(playerList[i]); - } - - //convert the list to an array for joining - var names = nameslist.ToArray(); - for (int i = 0; i < names.Length; i += perline) - { - args.Player.SendInfoMessage(string.Join(", ", names, i, Math.Min(names.Length - i, perline))); - } - - if (page < pagecount) - { - args.Player.SendInfoMessage(string.Format("Type /who {0} for more players.", (page + 2))); - } + args.Player.SendSuccessMessage("Online Players ({0}/{1})", TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots); + PaginationTools.SendPage( + args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(TShock.Utils.GetPlayers(displayIdsRequested)), + new PaginationTools.Settings + { + IncludeHeader = false, + FooterFormat = string.Format("Type /who {0}{{0}} for more.", displayIdsRequested ? "-i " : string.Empty) + } + ); } private static void AuthToken(CommandArgs args) @@ -3025,10 +3210,9 @@ namespace TShockAPI { try { - TShock.Users.AddUser(new User(args.Player.IP, "", "", "superadmin")); args.Player.Group = TShock.Utils.GetGroup("superadmin"); - args.Player.SendInfoMessage("This IP address is now superadmin. Please perform the following command:"); - args.Player.SendInfoMessage("/user add : superadmin"); + args.Player.SendInfoMessage("You are now superadmin, please do the following to finish your install:"); + args.Player.SendInfoMessage("/user add superadmin"); args.Player.SendInfoMessage("Creates: with the password as part of the superadmin group."); args.Player.SendInfoMessage("Please use /login to login from now on."); args.Player.SendInfoMessage("If you understand, please /login now, and type /auth-verify."); @@ -3044,9 +3228,7 @@ namespace TShockAPI if (args.Player.Group.Name == "superadmin") { args.Player.SendInfoMessage("Please disable the auth system! If you need help, consult the forums. http://tshock.co/"); - args.Player.SendInfoMessage("This IP address is now superadmin. Please perform the following command:"); - args.Player.SendInfoMessage("/user add : superadmin"); - args.Player.SendInfoMessage("Creates: with the password as part of the superadmin group."); + args.Player.SendInfoMessage("This account is superadmin, please do the following to finish your install:"); args.Player.SendInfoMessage("Please use /login to login from now on."); args.Player.SendInfoMessage("If you understand, please /login now, and type /auth-verify."); return; @@ -3065,15 +3247,6 @@ namespace TShockAPI return; } - if (!args.Player.IsLoggedIn) - { - args.Player.SendWarningMessage("You must be logged in to disable the auth system."); - args.Player.SendWarningMessage("This is a security measure designed to prevent insecure administration setups."); - args.Player.SendWarningMessage("Please re-run /auth and read the instructions!"); - args.Player.SendWarningMessage("If you're still confused, consult the forums: http://tshock.co/"); - return; - } - args.Player.SendSuccessMessage("Your new account has been verified, and the /auth system has been turned off."); args.Player.SendSuccessMessage("You can always use the /user command to manage players. Don't just delete the auth.lck."); args.Player.SendSuccessMessage("Thank you for using TShock! http://tshock.co/ & http://github.com/TShock/TShock"); @@ -3231,6 +3404,41 @@ namespace TShockAPI } } + private static void Aliases(CommandArgs args) + { + if (args.Parameters.Count < 1) + { + args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /aliases "); + return; + } + + string givenCommandName = string.Join(" ", args.Parameters); + if (string.IsNullOrWhiteSpace(givenCommandName)) { + args.Player.SendErrorMessage("Please enter a proper command name or alias."); + return; + } + + string commandName; + if (givenCommandName[0] == '/') + commandName = givenCommandName.Substring(1); + else + commandName = givenCommandName; + + bool didMatch = false; + foreach (Command matchingCommand in ChatCommands.Where(cmd => cmd.Names.IndexOf(commandName) != -1)) { + if (matchingCommand.Names.Count > 1) + args.Player.SendInfoMessage( + "Aliases of /{0}: /{1}", matchingCommand.Name, string.Join(", /", matchingCommand.Names.Skip(1))); + else + args.Player.SendInfoMessage("/{0} defines no aliases.", matchingCommand.Name); + + didMatch = true; + } + + if (!didMatch) + args.Player.SendErrorMessage("No command or command alias matching \"{0}\" found.", givenCommandName); + } + #endregion General Commands #region Cheat Commands @@ -3293,59 +3501,87 @@ namespace TShockAPI args.Player.SendErrorMessage("Invalid syntax! Proper syntax: /item [item amount] [prefix id/name]"); return; } - if (args.Parameters[0].Length == 0) - { - args.Player.SendErrorMessage("Missing an item name/id."); - return; - } + + int amountParamIndex = -1; int itemAmount = 0; - int prefix = 0; - if (args.Parameters.Count == 2) - int.TryParse(args.Parameters[1], out itemAmount); - else if (args.Parameters.Count == 3) + for (int i = 1; i < args.Parameters.Count; i++) { - int.TryParse(args.Parameters[1], out itemAmount); - var found = TShock.Utils.GetPrefixByIdOrName(args.Parameters[2]); - if (found.Count == 1) - prefix = found[0]; + if (int.TryParse(args.Parameters[i], out itemAmount)) + { + amountParamIndex = i; + break; + } } - var items = TShock.Utils.GetItemByIdOrName(args.Parameters[0]); - if (items.Count == 0) + + string itemNameOrId; + if (amountParamIndex == -1) + itemNameOrId = string.Join(" ", args.Parameters); + else + itemNameOrId = string.Join(" ", args.Parameters.Take(amountParamIndex)); + + Item item; + List matchedItems = TShock.Utils.GetItemByIdOrName(itemNameOrId); + if (matchedItems.Count == 0) { args.Player.SendErrorMessage("Invalid item type!"); + return; } - else if (items.Count > 1) + else if (matchedItems.Count > 1) { - args.Player.SendErrorMessage(string.Format("More than one ({0}) item matched!", items.Count)); + args.Player.SendErrorMessage("More than one item matched:"); + args.Player.SendErrorMessage(string.Join(", ", matchedItems.Select(i => i.name))); + return; } else { - var item = items[0]; - if (item.type >= 1 && item.type < Main.maxItemTypes) + item = matchedItems[0]; + } + if (item.type < 1 && item.type >= Main.maxItemTypes) + { + args.Player.SendErrorMessage("The item type {0} is invalid.", itemNameOrId); + return; + } + + int prefixId = 0; + if (amountParamIndex != -1 && args.Parameters.Count > amountParamIndex + 1) + { + string prefixidOrName = args.Parameters[amountParamIndex + 1]; + List matchedPrefixIds = TShock.Utils.GetPrefixByIdOrName(prefixidOrName); + if (matchedPrefixIds.Count > 1) { - if (args.Player.InventorySlotAvailable || item.name.Contains("Coin")) - { - if (itemAmount == 0 || itemAmount > item.maxStack) - itemAmount = item.maxStack; - if (args.Player.GiveItemCheck(item.type, item.name, item.width, item.height, itemAmount, prefix)) - { - args.Player.SendSuccessMessage(string.Format("Gave {0} {1}(s).", itemAmount, item.name)); - } - else - { - args.Player.SendErrorMessage("The item is banned and the config prevents you from spawning banned items."); - } - } - else - { - args.Player.SendErrorMessage("You don't have free slots!"); - } + args.Player.SendErrorMessage("More than one ({0}) prefixes matched \"{1}\".", matchedPrefixIds.Count, prefixidOrName); + return; + } + else if (matchedPrefixIds.Count == 0) + { + args.Player.SendErrorMessage("No prefix matched \"{0}\".", prefixidOrName); + return; } else { - args.Player.SendErrorMessage("Invalid item type!"); + prefixId = matchedPrefixIds[0]; } } + + if (args.Player.InventorySlotAvailable || item.name.Contains("Coin")) + { + if (itemAmount == 0 || itemAmount > item.maxStack) + itemAmount = item.maxStack; + + if (args.Player.GiveItemCheck(item.type, item.name, item.width, item.height, itemAmount, prefixId)) + { + item.prefix = (byte)prefixId; + args.Player.SendSuccessMessage("Gave {0} {1}(s).", itemAmount, item.AffixName()); + } + else + { + args.Player.SendErrorMessage("The item is banned and the config prevents you from spawning banned items."); + } + } + else + { + args.Player.SendErrorMessage("Your inventory seems full."); + } } private static void Give(CommandArgs args) @@ -3435,7 +3671,7 @@ namespace TShockAPI } } - public static void ClearItems(CommandArgs args) + private static void ClearItems(CommandArgs args) { int radius = 50; if (args.Parameters.Count > 0) diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index 8ad3a8d4..9079f3e1 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,12 +15,15 @@ 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.ComponentModel; using System.IO; using System.Linq; using System.Text; using Newtonsoft.Json; +using Rests; namespace TShockAPI { @@ -129,7 +132,7 @@ namespace TShockAPI [Description("This will announce a player's location on join")] public bool EnableGeoIP; - [Description("This will turn on a token requirement for the /status API endpoint.")] public bool + [Description("This will turn on token requirement for the public REST API endpoints.")] public bool EnableTokenEndpointAuthentication; [Description("Deprecated. Use ServerName instead.")] public string ServerNickname = "TShock Server"; @@ -158,8 +161,6 @@ namespace TShockAPI [Description("Time, in milliseconds, to disallow discarding items after logging in when ServerSideInventory is ON.")] public int LogonDiscardThreshold=250; - [Description("Disables reporting of playercount to the stat system.")] public bool DisablePlayerCountReporting; - [Description("Disables clown bomb projectiles from spawning.")] public bool DisableClownBombs; [Description("Disables snow ball projectiles from spawning.")] public bool DisableSnowBalls; @@ -236,15 +237,37 @@ namespace TShockAPI [Description("Prevent banks on SSI.")] public bool DisablePiggybanksOnSSI = false; [Description("Prevent players from interacting with the world if dead.")] public bool PreventDeadModification = - false; + true; [Description("Displays chat messages above players' heads, but will disable chat prefixes to compensate.")] public bool EnableChatAboveHeads = false; - [Description("Hide stat tracker console messages.")] public bool HideStatTrackerDebugMessages = true; - [Description("Force Christmas only events to occur all year.")] public bool ForceXmas = false; + [Description("Allows groups on the banned item allowed list to spawn banned items.")] public bool AllowAllowedGroupsToSpawnBannedItems = false; + + [Description("Allows stacks in chests to be beyond the stack limit")] public bool IgnoreChestStacksOnLoad = false; + + [Description("The path of the directory where logs should be written into.")] public string LogPath = "tshock"; + + [Description("Prevents players from placing tiles with an invalid style.")] public bool PreventInvalidPlaceStyle = true; + + [Description("#.#.#. = Red/Blue/Green - RGB Colors for broadcasts. Max value: 255.")] public float[] BroadcastRGB = + {127,255,212}; + + // TODO: Get rid of this when the old REST permission model is removed. + [Description( + "Whether the REST API should use the new permission model. Note: The old permission model will become depracted in the future." + )] public bool RestUseNewPermissionModel = true; + + [Description("A dictionary of REST tokens that external applications may use to make queries to your server.")] + public Dictionary ApplicationRestTokens = new Dictionary(); + + [Description("The maximum value that a character may have for health.")] public int MaxHealth = 400; + + [Description("The maximum value that a character may have for health.")] public int MaxMana = 400; + + [Description("The number of reserved slots past your max server slot that can be joined by reserved players")] public int ReservedSlots = 20; /// /// Reads a configuration file from a given path /// diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs index 7ace1584..c2fef99e 100644 --- a/TShockAPI/DB/BanManager.cs +++ b/TShockAPI/DB/BanManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,10 +15,10 @@ 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.IO; using MySql.Data.MySqlClient; namespace TShockAPI.DB @@ -34,7 +34,10 @@ namespace TShockAPI.DB var table = new SqlTable("Bans", new SqlColumn("IP", MySqlDbType.String, 16) {Primary = true}, new SqlColumn("Name", MySqlDbType.Text), - new SqlColumn("Reason", MySqlDbType.Text) + new SqlColumn("Reason", MySqlDbType.Text), + new SqlColumn("BanningUser", MySqlDbType.Text), + new SqlColumn("Date", MySqlDbType.Text), + new SqlColumn("Expiration", MySqlDbType.Text) ); var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite @@ -58,7 +61,7 @@ namespace TShockAPI.DB using (var reader = database.QueryReader("SELECT * FROM Bans WHERE IP=@0", ip)) { if (reader.Read()) - return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason")); + return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration")); } } catch (Exception ex) @@ -77,7 +80,7 @@ namespace TShockAPI.DB { while (reader.Read()) { - banlist.Add(new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason"))); + banlist.Add(new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"))); } return banlist; } @@ -100,7 +103,7 @@ namespace TShockAPI.DB using (var reader = database.QueryReader("SELECT * FROM Bans WHERE " + namecol + "=@0", name)) { if (reader.Read()) - return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason")); + return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration")); } } catch (Exception ex) @@ -114,14 +117,14 @@ namespace TShockAPI.DB [Obsolete("This method is for signature compatibility for external code only")] public bool AddBan(string ip, string name, string reason) { - return AddBan(ip, name, reason, false); + return AddBan(ip, name, reason, false, "", ""); } #endif - public bool AddBan(string ip, string name = "", string reason = "", bool exceptions = false) + public bool AddBan(string ip, string name = "", string reason = "", bool exceptions = false, string banner = "", string expiration = "") { try { - return database.Query("INSERT INTO Bans (IP, Name, Reason) VALUES (@0, @1, @2);", ip, name, reason) != 0; + return database.Query("INSERT INTO Bans (IP, Name, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4, @5);", ip, name, reason, banner, DateTime.Now.ToString("G"), expiration) != 0; } catch (Exception ex) { @@ -180,11 +183,20 @@ namespace TShockAPI.DB public string Reason { get; set; } - public Ban(string ip, string name, string reason) + public string BanningUser { get; set; } + + public string Date { get; set; } + + public string Expiration { get; set; } + + public Ban(string ip, string name, string reason, string banner, string date, string exp) { IP = ip; Name = name; Reason = reason; + BanningUser = banner; + Date = date; + Expiration = exp; } public Ban() @@ -192,6 +204,9 @@ namespace TShockAPI.DB IP = string.Empty; Name = string.Empty; Reason = string.Empty; + BanningUser = ""; + Date = ""; + Expiration = ""; } } } diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index 5d962eb0..c7646d57 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,11 +15,12 @@ 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; using System.Collections.Generic; using System.Data; -using System.IO; +using System.Diagnostics; using System.Linq; using MySql.Data.MySqlClient; @@ -52,13 +53,29 @@ namespace TShockAPI.DB LoadPermisions(); // Add default groups if they don't exist - AddDefaultGroup("guest", "", "canbuild,canregister,canlogin,canpartychat,cantalkinthird"); - AddDefaultGroup("default", "guest", "warp,canchangepassword"); - AddDefaultGroup("newadmin", "default", "kick,editspawn,reservedslot"); + AddDefaultGroup(TShock.Config.DefaultGuestGroupName, "", + string.Join(",", Permissions.canbuild, Permissions.canregister, Permissions.canlogin, Permissions.canpartychat, + Permissions.cantalkinthird)); + + AddDefaultGroup("default", TShock.Config.DefaultGuestGroupName, + string.Join(",", Permissions.warp, Permissions.canchangepassword)); + + AddDefaultGroup("newadmin", "default", + string.Join(",", Permissions.kick, Permissions.editspawn, Permissions.reservedslot)); + AddDefaultGroup("admin", "newadmin", - "ban,unban,whitelist,causeevents,spawnboss,spawnmob,managewarp,time,tp,pvpfun,kill,logs,immunetokick,tphere"); - AddDefaultGroup("trustedadmin", "admin", "maintenance,cfg,butcher,item,heal,immunetoban,usebanneditem,manageusers"); - AddDefaultGroup("vip", "default", "reservedslot"); + string.Join(",", Permissions.ban, Permissions.whitelist, Permissions.causeevents, Permissions.spawnboss, + Permissions.spawnmob, Permissions.managewarp, Permissions.time, Permissions.tp, Permissions.slap, + Permissions.kill, Permissions.logs, + Permissions.immunetokick, Permissions.tphere)); + + AddDefaultGroup("trustedadmin", "admin", + string.Join(",", Permissions.maintenance, "tshock.cfg.*", "tshock.world.*", Permissions.butcher, Permissions.item, + Permissions.heal, Permissions.immunetoban, Permissions.usebanneditem)); + + AddDefaultGroup("vip", "default", string.Join(",", Permissions.reservedslot)); + + Group.DefaultGroup = GetGroupByName(TShock.Config.DefaultGuestGroupName); } private void AddDefaultGroup(string name, string parent, string permissions) @@ -114,7 +131,7 @@ namespace TShockAPI.DB if (!string.IsNullOrWhiteSpace(parentname)) { var parent = groups.FirstOrDefault(gp => gp.Name == parentname); - if (parent == null) + if (parent == null || name == parentname) { var error = "Invalid parent {0} for group {1}".SFormat(parentname, group.Name); if (exceptions) @@ -166,27 +183,40 @@ namespace TShockAPI.DB /// chatcolor public void UpdateGroup(string name, string parentname, string permissions, string chatcolor) { - if (!GroupExists(name)) + Group group = GetGroupByName(name); + if (group == null) throw new GroupNotExistException(name); Group parent = null; if (!string.IsNullOrWhiteSpace(parentname)) { - parent = groups.FirstOrDefault(gp => gp.Name == parentname); - if (null == parent) - throw new GroupManagerException("Invalid parent {0} for group {1}".SFormat(parentname, name)); + parent = GetGroupByName(parentname); + if (parent == null || parent == group) + throw new GroupManagerException("Invalid parent \"{0}\" for group \"{1}\".".SFormat(parentname, name)); + + // Check if the new parent would cause loops. + List groupChain = new List { group, parent }; + Group checkingGroup = parent.Parent; + while (checkingGroup != null) + { + if (groupChain.Contains(checkingGroup)) + throw new GroupManagerException( + string.Format("Invalid parent \"{0}\" for group \"{1}\" would cause loops in the parent chain.", parentname, name)); + + groupChain.Add(checkingGroup); + checkingGroup = checkingGroup.Parent; + } } - // NOTE: we use newgroup.XYZ to ensure any validation is also persisted to the DB - var newgroup = new Group(name, parent, chatcolor, permissions); + // Ensure any group validation is also persisted to the DB. + var newGroup = new Group(name, parent, chatcolor, permissions); string query = "UPDATE GroupList SET Parent=@0, Commands=@1, ChatColor=@2 WHERE GroupName=@3"; - if (database.Query(query, parentname, newgroup.Permissions, string.Format("{0},{1},{2}", newgroup.R, newgroup.G, newgroup.B), name) != 1) - throw new GroupManagerException("Failed to update group '" + name + "'"); + if (database.Query(query, parentname, newGroup.Permissions, string.Format("{0},{1},{2}", newGroup.R, newGroup.G, newGroup.B), name) != 1) + throw new GroupManagerException(string.Format("Failed to update group \"{0}\".", name)); - Group group = TShock.Utils.GetGroup(name); group.ChatColor = chatcolor; group.Permissions = permissions; - group.Parent = TShock.Utils.GetGroup(parentname); + group.Parent = parent; } #if COMPAT_SIGS @@ -252,53 +282,106 @@ namespace TShockAPI.DB public void LoadPermisions() { - // Create a temporary list so if there is an error it doesn't override the currently loaded groups with broken groups. - var tempgroups = new List(); - tempgroups.Add(new SuperAdminGroup()); - - if (groups == null || groups.Count < 2) - { - groups.Clear(); - groups.AddRange(tempgroups); - } - try { - var groupsparents = new List>(); + List newGroups = new List(groups.Count); + Dictionary newGroupParents = new Dictionary(groups.Count); using (var reader = database.QueryReader("SELECT * FROM GroupList")) { while (reader.Read()) { - var group = new Group(reader.Get("GroupName"), null, reader.Get("ChatColor"), reader.Get("Commands")); - group.Prefix = reader.Get("Prefix"); - group.Suffix = reader.Get("Suffix"); - groupsparents.Add(Tuple.Create(group, reader.Get("Parent"))); - } - } - - foreach (var t in groupsparents) - { - var group = t.Item1; - var parentname = t.Item2; - if (!string.IsNullOrWhiteSpace(parentname)) - { - var parent = groupsparents.FirstOrDefault(gp => gp.Item1.Name == parentname); - if (parent == null) + string groupName = reader.Get("GroupName"); + if (groupName == "superadmin") { - Log.ConsoleError("Invalid parent {0} for group {1}".SFormat(parentname, group.Name)); + Log.ConsoleInfo("WARNING: Group \"superadmin\" is defined in the database even though it's a reserved group name."); + continue; + } + + newGroups.Add(new Group(groupName, null, reader.Get("ChatColor"), reader.Get("Commands")) { + Prefix = reader.Get("Prefix"), + Suffix = reader.Get("Suffix"), + }); + + try + { + newGroupParents.Add(groupName, reader.Get("Parent")); + } + catch (ArgumentException) + { + // Just in case somebody messed with the unique primary key. + Log.ConsoleError("ERROR: Group name \"{0}\" occurs more than once. Keeping current group settings."); return; } - group.Parent = parent.Item1; } - tempgroups.Add(group); } - groups.Clear(); - groups.AddRange(tempgroups); + try + { + // Get rid of deleted groups. + for (int i = 0; i < groups.Count; i++) + if (newGroups.All(g => g.Name != groups[i].Name)) + groups.RemoveAt(i--); + + // Apply changed group settings while keeping the current instances and add new groups. + foreach (Group newGroup in newGroups) + { + Group currentGroup = groups.FirstOrDefault(g => g.Name == newGroup.Name); + if (currentGroup != null) + newGroup.AssignTo(currentGroup); + else + groups.Add(newGroup); + } + + // Resolve parent groups. + Debug.Assert(newGroups.Count == newGroupParents.Count); + for (int i = 0; i < groups.Count; i++) + { + Group group = groups[i]; + string parentGroupName; + if (!newGroupParents.TryGetValue(group.Name, out parentGroupName) || string.IsNullOrEmpty(parentGroupName)) + continue; + + group.Parent = groups.FirstOrDefault(g => g.Name == parentGroupName); + if (group.Parent == null) + { + Log.ConsoleError( + "ERROR: Group \"{0}\" is referencing non existent parent group \"{1}\", parent reference was removed.", + group.Name, parentGroupName); + } + else + { + if (group.Parent == group) + Log.ConsoleInfo( + "WARNING: Group \"{0}\" is referencing itself as parent group, parent reference was removed.", group.Name); + + List groupChain = new List { group }; + Group checkingGroup = group; + while (checkingGroup.Parent != null) + { + if (groupChain.Contains(checkingGroup.Parent)) + { + Log.ConsoleError( + "ERROR: Group \"{0}\" is referencing parent group \"{1}\" which is already part of the parent chain. Parent reference removed.", + checkingGroup.Name, checkingGroup.Parent.Name); + + checkingGroup.Parent = null; + break; + } + groupChain.Add(checkingGroup); + checkingGroup = checkingGroup.Parent; + } + } + } + } + finally + { + if (!groups.Any(g => g is SuperAdminGroup)) + groups.Add(new SuperAdminGroup()); + } } catch (Exception ex) { - Log.Error(ex.ToString()); + Log.ConsoleError("Error on reloading groups: " + ex); } } } diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs index 2d4a8662..d65af44a 100644 --- a/TShockAPI/DB/IQueryBuilder.cs +++ b/TShockAPI/DB/IQueryBuilder.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Linq; diff --git a/TShockAPI/DB/InventoryManager.cs b/TShockAPI/DB/InventoryManager.cs index 92f8a6bf..9cbff2f4 100644 --- a/TShockAPI/DB/InventoryManager.cs +++ b/TShockAPI/DB/InventoryManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Data; using MySql.Data.MySqlClient; diff --git a/TShockAPI/DB/ItemManager.cs b/TShockAPI/DB/ItemManager.cs index 6ef11b6b..b95bd886 100644 --- a/TShockAPI/DB/ItemManager.cs +++ b/TShockAPI/DB/ItemManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,10 +15,10 @@ 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.IO; using System.Linq; using MySql.Data.MySqlClient; diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs index 31519726..f9cce5c8 100644 --- a/TShockAPI/DB/RegionManager.cs +++ b/TShockAPI/DB/RegionManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,13 +15,11 @@ 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; @@ -238,25 +236,21 @@ namespace TShockAPI.DB 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]; - } - } - } + + foreach (Region region in Regions.ToList()) + { + if (region.InArea(x, y)) + { + if (top == null || region.Z > top.Z) + top = region; + } + } return top == null || top.HasPermissionToBuildInRegion(ply); } public bool InArea(int x, int y) { - foreach (Region region in Regions) + foreach (Region region in Regions.ToList()) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && @@ -271,7 +265,7 @@ namespace TShockAPI.DB public List InAreaRegionName(int x, int y) { List regions = new List() { }; - foreach (Region region in Regions) + foreach (Region region in Regions.ToList()) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && @@ -286,7 +280,7 @@ namespace TShockAPI.DB public List InAreaRegion(int x, int y) { List regions = new List() { }; - foreach (Region region in Regions) + foreach (Region region in Regions.ToList()) { if (x >= region.Area.Left && x <= region.Area.Right && y >= region.Area.Top && y <= region.Area.Bottom && @@ -385,30 +379,36 @@ namespace TShockAPI.DB return false; } - public bool AddNewUser(string regionName, String userName) + public bool AddNewUser(string regionName, string userName) { try { - string MergedIDs = string.Empty; + string mergedIDs = string.Empty; using ( - var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, + var reader = database.QueryReader("SELECT UserIds FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) - MergedIDs = reader.Get("UserIds"); + mergedIDs = reader.Get("UserIds"); } - if (string.IsNullOrEmpty(MergedIDs)) - MergedIDs = Convert.ToString(TShock.Users.GetUserID(userName)); - else - MergedIDs = MergedIDs + "," + Convert.ToString(TShock.Users.GetUserID(userName)); + string userIdToAdd = Convert.ToString(TShock.Users.GetUserID(userName)); + string[] ids = mergedIDs.Split(','); + // Is the user already allowed to the region? + if (ids.Contains(userIdToAdd)) + return true; - int q = database.Query("UPDATE Regions SET UserIds=@0 WHERE RegionName=@1 AND WorldID=@2", MergedIDs, + if (string.IsNullOrEmpty(mergedIDs)) + mergedIDs = userIdToAdd; + else + mergedIDs = string.Concat(mergedIDs, ",", userIdToAdd); + + 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); + r.setAllowedIDs(mergedIDs); } return q != 0; } @@ -471,27 +471,33 @@ namespace TShockAPI.DB return false; } - public bool AllowGroup(string regionName, string groups) + public bool AllowGroup(string regionName, string groupName) { - string groupsNew = ""; + string mergedGroups = ""; using ( - var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, + var reader = database.QueryReader("SELECT Groups FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString())) { if (reader.Read()) - groupsNew = reader.Get("Groups"); + mergedGroups = reader.Get("Groups"); } - if (groupsNew != "") - groupsNew += ","; - groupsNew += groups; - int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", groupsNew, + string[] groups = mergedGroups.Split(','); + // Is the group already allowed to the region? + if (groups.Contains(groupName)) + return true; + + if (mergedGroups != "") + mergedGroups += ","; + mergedGroups += groupName; + + int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", mergedGroups, regionName, Main.worldID.ToString()); Region r = GetRegionByName(regionName); if (r != null) { - r.SetAllowedGroups(groupsNew); + r.SetAllowedGroups(mergedGroups); } else { diff --git a/TShockAPI/DB/RememberedPosManager.cs b/TShockAPI/DB/RememberedPosManager.cs index 88d57215..604743ff 100644 --- a/TShockAPI/DB/RememberedPosManager.cs +++ b/TShockAPI/DB/RememberedPosManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Data; using MySql.Data.MySqlClient; @@ -112,7 +113,7 @@ namespace TShockAPI.DB try { if ((X != 0) && ( Y !=0)) //invalid pos! - database.Query("UPDATE RememberedPos SET X = @0, Y = @1, IP = @2 WHERE Name = @3 AND WorldID = @4;", X, Y, IP, name, Main.worldID.ToString()); + database.Query("UPDATE RememberedPos SET X = @0, Y = @1, IP = @2, WorldID = @3 WHERE Name = @4;", X, Y, IP, Main.worldID.ToString(), name); } catch (Exception ex) { diff --git a/TShockAPI/DB/SqlColumn.cs b/TShockAPI/DB/SqlColumn.cs index efc94649..9b59c89e 100644 --- a/TShockAPI/DB/SqlColumn.cs +++ b/TShockAPI/DB/SqlColumn.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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 MySql.Data.MySqlClient; namespace TShockAPI.DB diff --git a/TShockAPI/DB/SqlTable.cs b/TShockAPI/DB/SqlTable.cs index 0b4024c6..08275ef9 100644 --- a/TShockAPI/DB/SqlTable.cs +++ b/TShockAPI/DB/SqlTable.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; diff --git a/TShockAPI/DB/SqlValue.cs b/TShockAPI/DB/SqlValue.cs index 0efd78bf..04161e90 100644 --- a/TShockAPI/DB/SqlValue.cs +++ b/TShockAPI/DB/SqlValue.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Collections.Generic; using System.Data; diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index db360518..c8af324c 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,9 +15,9 @@ 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.Data; -using System.IO; using System.Collections.Generic; using System.Linq; using MySql.Data.MySqlClient; @@ -38,7 +38,8 @@ namespace TShockAPI.DB new SqlColumn("Username", MySqlDbType.VarChar, 32) {Unique = true}, new SqlColumn("Password", MySqlDbType.VarChar, 128), new SqlColumn("Usergroup", MySqlDbType.Text), - new SqlColumn("IP", MySqlDbType.VarChar, 16) + new SqlColumn("LastAccessed", MySqlDbType.Text), + new SqlColumn("KnownIPs", MySqlDbType.Text) ); var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite @@ -59,8 +60,8 @@ namespace TShockAPI.DB int ret; try { - ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name, - TShock.Utils.HashPassword(user.Password), user.Group, user.Address); + ret = database.Query("INSERT INTO Users (Username, Password, UserGroup) VALUES (@0, @1, @2);", user.Name, + TShock.Utils.HashPassword(user.Password), user.Group); } catch (Exception ex) { @@ -82,18 +83,10 @@ namespace TShockAPI.DB { try { - int affected = -1; - if (!string.IsNullOrEmpty(user.Address)) - { - affected = database.Query("DELETE FROM Users WHERE IP=@0", user.Address); - } - else - { - affected = database.Query("DELETE FROM Users WHERE Username=@0", user.Name); - } + int affected = database.Query("DELETE FROM Users WHERE Username=@0", user.Name); if (affected < 1) - throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address); + throw new UserNotExistException(user.Name); } catch (Exception ex) { @@ -101,7 +94,6 @@ namespace TShockAPI.DB } } - /// /// Sets the Hashed Password for a given username /// @@ -150,6 +142,19 @@ namespace TShockAPI.DB } } + public void UpdateLogin(User user) + { + try + { + if (database.Query("UPDATE Users SET LastAccessed = @0, KnownIps = @1 WHERE Username = @2;", DateTime.Now.ToString("G"), user.KnownIps, user.Name) == 0) + throw new UserNotExistException(user.Name); + } + catch (Exception ex) + { + throw new UserManagerException("UpdateLogin SQL returned an error", ex); + } + } + public int GetUserID(string username) { try @@ -169,53 +174,6 @@ namespace TShockAPI.DB return -1; } - /// - /// Returns a Group for a ip from the database - /// - /// string ip - public Group GetGroupForIP(string ip) - { - try - { - using (var reader = database.QueryReader("SELECT * FROM Users WHERE IP=@0", ip)) - { - if (reader.Read()) - { - string group = reader.Get("UserGroup"); - return TShock.Utils.GetGroup(group); - } - } - } - catch (Exception ex) - { - Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex); - } - return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName); - } - - public Group GetGroupForIPExpensive(string ip) - { - try - { - using (var reader = database.QueryReader("SELECT IP, UserGroup FROM Users")) - { - while (reader.Read()) - { - if (TShock.Utils.GetIPv4Address(reader.Get("IP")) == ip) - { - return TShock.Utils.GetGroup(reader.Get("UserGroup")); - } - } - } - } - catch (Exception ex) - { - Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex); - } - return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName); - } - - public User GetUserByName(string name) { try @@ -240,18 +198,6 @@ namespace TShockAPI.DB } } - public User GetUserByIP(string ip) - { - try - { - return GetUser(new User {Address = ip}); - } - catch (UserManagerException) - { - return null; - } - } - public User GetUser(User user) { bool multiple = false; @@ -264,18 +210,12 @@ namespace TShockAPI.DB arg = user.ID; type = "id"; } - else if (string.IsNullOrEmpty(user.Address)) + else { query = "SELECT * FROM Users WHERE Username=@0"; arg = user.Name; type = "name"; } - else - { - query = "SELECT * FROM Users WHERE IP=@0"; - arg = user.Address; - type = "ip"; - } try { @@ -298,7 +238,7 @@ namespace TShockAPI.DB if (multiple) throw new UserManagerException(String.Format("Multiple users found for {0} '{1}'", type, arg)); - throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address); + throw new UserNotExistException(user.Name); } public List GetUsers() @@ -328,7 +268,8 @@ namespace TShockAPI.DB user.Group = result.Get("Usergroup"); user.Password = result.Get("Password"); user.Name = result.Get("Username"); - user.Address = result.Get("IP"); + user.LastAccessed = result.Get("LastAccessed"); + user.KnownIps = result.Get("KnownIps"); return user; } } @@ -339,22 +280,25 @@ namespace TShockAPI.DB public string Name { get; set; } public string Password { get; set; } public string Group { get; set; } - public string Address { get; set; } + public string LastAccessed { get; set; } + public string KnownIps { get; set; } - public User(string ip, string name, string pass, string group) + public User(string name, string pass, string group, string last, string known) { - Address = ip; Name = name; Password = pass; Group = group; + LastAccessed = last; + KnownIps = known; } public User() { - Address = ""; Name = ""; Password = ""; Group = ""; + LastAccessed = ""; + KnownIps = ""; } } diff --git a/TShockAPI/DB/WarpsManager.cs b/TShockAPI/DB/WarpsManager.cs index 67d1afe8..9ece9591 100644 --- a/TShockAPI/DB/WarpsManager.cs +++ b/TShockAPI/DB/WarpsManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs index 42bcc6f1..3f1db5c7 100644 --- a/TShockAPI/Extensions/DbExt.cs +++ b/TShockAPI/Extensions/DbExt.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; diff --git a/TShockAPI/Extensions/LinqExt.cs b/TShockAPI/Extensions/LinqExt.cs index 25d06b7e..86b639bd 100644 --- a/TShockAPI/Extensions/LinqExt.cs +++ b/TShockAPI/Extensions/LinqExt.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; diff --git a/TShockAPI/Extensions/RandomExt.cs b/TShockAPI/Extensions/RandomExt.cs index 2e4d2681..e0a866bf 100644 --- a/TShockAPI/Extensions/RandomExt.cs +++ b/TShockAPI/Extensions/RandomExt.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Text; diff --git a/TShockAPI/Extensions/StringExt.cs b/TShockAPI/Extensions/StringExt.cs index e70be35b..ea67e801 100644 --- a/TShockAPI/Extensions/StringExt.cs +++ b/TShockAPI/Extensions/StringExt.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; namespace TShockAPI diff --git a/TShockAPI/FileTools.cs b/TShockAPI/FileTools.cs index 59072016..581fe393 100644 --- a/TShockAPI/FileTools.cs +++ b/TShockAPI/FileTools.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; diff --git a/TShockAPI/GeoIPCountry.cs b/TShockAPI/GeoIPCountry.cs index c09ccffc..4ca30777 100644 --- a/TShockAPI/GeoIPCountry.cs +++ b/TShockAPI/GeoIPCountry.cs @@ -1,4 +1,22 @@ -/* GeoIPCountry.cs +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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 . +*/ + +/* GeoIPCountry.cs * * Copyright (C) 2008 MaxMind, Inc. All Rights Reserved. * @@ -16,24 +34,6 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -/* -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.IO; using System.Net; diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index c085a31b..0b128ba9 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,13 +15,16 @@ 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.ComponentModel; +using System.Diagnostics; using System.IO; using System.IO.Streams; using System.Linq; using System.Text; +using TShockAPI.DB; using Terraria; using TShockAPI.Net; @@ -86,13 +89,18 @@ namespace TShockAPI /// Did the tile get destroyed successfully. /// public bool Fail { get; set; } + + /// + /// Used when a tile is placed to denote a subtype of tile. (e.g. for tile id 21: Chest = 0, Gold Chest = 1) + /// + public byte Style { get; set; } } /// /// TileEdit - called when a tile is placed or destroyed /// public static HandlerList TileEdit; - private static bool OnTileEdit(TSPlayer ply, int x, int y, byte type, byte editType, bool fail) + private static bool OnTileEdit(TSPlayer ply, int x, int y, byte type, byte editType, bool fail, byte style) { if (TileEdit == null) return false; @@ -104,7 +112,8 @@ namespace TShockAPI Y = y, Type = type, EditType = editType, - Fail = fail + Fail = fail, + Style = style }; TileEdit.Invoke(null, args); return args.Handled; @@ -1189,6 +1198,14 @@ namespace TShockAPI byte prefix = args.Data.ReadInt8(); short type = args.Data.ReadInt16(); + // Players send a slot update packet for each inventory slot right after they've joined. + bool bypassTrashCanCheck = false; + if (plr == args.Player.Index && !args.Player.HasSentInventory && slot == NetItem.maxNetInventory) + { + args.Player.HasSentInventory = true; + bypassTrashCanCheck = true; + } + if (OnPlayerSlot(plr, slot, stack, prefix, type)) return true; @@ -1202,6 +1219,7 @@ namespace TShockAPI return true; } + // Garabage? Or will it cause some internal initialization or whatever? var item = new Item(); item.netDefaults(type); item.Prefix(prefix); @@ -1210,6 +1228,13 @@ namespace TShockAPI { args.Player.PlayerData.StoreSlot(slot, type, prefix, stack); } + else if ( + TShock.Config.ServerSideInventory && TShock.Config.DisableLoginBeforeJoin && !bypassTrashCanCheck && + args.Player.HasSentInventory && !args.Player.Group.HasPermission(Permissions.bypassinventorychecks) + ) { + // The player might have moved an item to their trash can before they performed a single login attempt yet. + args.Player.IgnoreActionsForClearingTrashCan = true; + } return false; } @@ -1226,7 +1251,7 @@ namespace TShockAPI if (args.Player.FirstMaxHP == 0) args.Player.FirstMaxHP = max; - if (max > 400 && max > args.Player.FirstMaxHP) + if (max > TShock.Config.MaxHealth && max > args.Player.FirstMaxHP) { TShock.Utils.ForceKick(args.Player, "Hacked Client Detected.", true); return false; @@ -1252,7 +1277,7 @@ namespace TShockAPI if (args.Player.FirstMaxMP == 0) args.Player.FirstMaxMP = max; - if (max > 400 && max > args.Player.FirstMaxMP) + if (max > TShock.Config.MaxMana && max > args.Player.FirstMaxMP) { TShock.Utils.ForceKick(args.Player, "Hacked Client Detected.", true); return false; @@ -1281,12 +1306,6 @@ namespace TShockAPI TShock.Utils.ForceKick(args.Player, "Empty Name.", true); return true; } - var ban = TShock.Bans.GetBanByName(name); - if (ban != null) - { - TShock.Utils.ForceKick(args.Player, string.Format("You are banned: {0}", ban.Reason), true); - return true; - } if (args.Player.ReceivedInfo) { return true; @@ -1336,6 +1355,10 @@ namespace TShockAPI return true; string password = Encoding.UTF8.GetString(args.Data.ReadBytes((int) (args.Data.Length - args.Data.Position - 1))); + + if (Hooks.PlayerHooks.OnPlayerPreLogin(args.Player, args.Player.Name, password)) + return true; + var user = TShock.Users.GetUserByName(args.Player.Name); if (user != null && !TShock.Config.DisableLoginBeforeJoin) { @@ -1359,11 +1382,13 @@ namespace TShockAPI } else if (!TShock.CheckInventory(args.Player)) { + args.Player.LoginFailsBySsi = true; args.Player.SendMessage("Login Failed, Please fix the above errors then /login again.", Color.Cyan); args.Player.IgnoreActionsForClearingTrashCan = true; return true; } } + args.Player.LoginFailsBySsi = false; if (group.HasPermission(Permissions.ignorestackhackdetection)) args.Player.IgnoreActionsForCheating = "none"; @@ -1372,6 +1397,7 @@ namespace TShockAPI args.Player.IgnoreActionsForDisabledArmor = "none"; args.Player.Group = group; + args.Player.tempGroup = null; args.Player.UserAccountName = args.Player.Name; args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName); args.Player.IsLoggedIn = true; @@ -1384,7 +1410,7 @@ namespace TShockAPI } args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); - Hooks.PlayerLoginEvent.OnPlayerLogin(args.Player); + Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); return true; } TShock.Utils.ForceKick(args.Player, "Invalid user account password.", true); @@ -1418,7 +1444,7 @@ namespace TShockAPI TShock.Utils.ForceKick(args.Player, "Blank name.", true); return true; } - if (TShock.HackedHealth(args.Player) && !args.Player.Group.HasPermission(Permissions.ignorestathackdetection)) + if (TShock.HackedStats(args.Player) && !args.Player.Group.HasPermission(Permissions.ignorestathackdetection)) { TShock.Utils.ForceKick(args.Player, "You have hacked health/mana, please use a different character.", true); return true; @@ -1443,13 +1469,15 @@ namespace TShockAPI Log.Info(string.Format("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", args.Player.Name, args.Player.IP, args.Player.Group.Name, args.Player.Country, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); - TShock.Utils.Broadcast(string.Format("{0} ({1}) has joined.", args.Player.Name, args.Player.Country), Color.Yellow); + if (!args.Player.SilentJoinInProgress) + TShock.Utils.Broadcast(string.Format("{0} ({1}) has joined.", args.Player.Name, args.Player.Country), Color.Yellow); } else { Log.Info(string.Format("{0} ({1}) from '{2}' group joined. ({3}/{4})", args.Player.Name, args.Player.IP, args.Player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); - TShock.Utils.Broadcast(args.Player.Name + " has joined.", Color.Yellow); + if (!args.Player.SilentJoinInProgress) + TShock.Utils.Broadcast(args.Player.Name + " has joined.", Color.Yellow); } if (TShock.Config.DisplayIPToAdmins) @@ -1651,10 +1679,12 @@ namespace TShockAPI var tileX = args.Data.ReadInt32(); var tileY = args.Data.ReadInt32(); var tiletype = args.Data.ReadInt8(); - var fail = args.Data.ReadBoolean(); - if (OnTileEdit(args.Player, tileX, tileY, tiletype, type, fail)) + var fail = tiletype == 1; + var style = args.Data.ReadInt8(); + + if (OnTileEdit(args.Player, tileX, tileY, tiletype, type, fail, style)) return true; - if (!TShock.Utils.TileValid(tileX, tileY)) + if (!TShock.Utils.TilePlacementValid(tileX, tileY)) return false; if (args.Player.Dead && TShock.Config.PreventDeadModification) @@ -1662,18 +1692,63 @@ namespace TShockAPI if (args.Player.AwaitingName) { - var protectedregions = TShock.Regions.InAreaRegionName(tileX, tileY); - if (protectedregions.Count == 0) + Debug.Assert(args.Player.AwaitingNameParameters != null); + + bool includeUnprotected = false; + bool includeZIndexes = false; + bool persistentMode = false; + foreach (string parameter in args.Player.AwaitingNameParameters) { - args.Player.SendMessage("Region is not protected", Color.Yellow); + if (parameter.Equals("-u", StringComparison.InvariantCultureIgnoreCase)) + includeUnprotected = true; + if (parameter.Equals("-z", StringComparison.InvariantCultureIgnoreCase)) + includeZIndexes = true; + if (parameter.Equals("-p", StringComparison.InvariantCultureIgnoreCase)) + persistentMode = true; + } + + List outputRegions = new List(); + foreach (Region region in TShock.Regions.Regions.OrderBy(r => r.Z).Reverse()) + { + if (!includeUnprotected && !region.DisableBuild) + continue; + if (tileX < region.Area.Left || tileX > region.Area.Right) + continue; + if (tileY < region.Area.Top || tileY > region.Area.Bottom) + continue; + + string format = "{1}"; + if (includeZIndexes) + format = "{1} (z:{0})"; + + outputRegions.Add(string.Format(format, region.Z, region.Name)); + } + + if (outputRegions.Count == 0) + { + if (includeUnprotected) + args.Player.SendMessage("There are no regions at this point.", Color.Yellow); + else + args.Player.SendMessage("There are no regions at this point or they are not protected.", Color.Yellow); } else { - string regionlist = string.Join(",", protectedregions.ToArray()); - args.Player.SendMessage("Region Name(s): " + regionlist, Color.Yellow); + if (includeUnprotected) + args.Player.SendSuccessMessage("Regions at this point:"); + else + args.Player.SendSuccessMessage("Protected regions at this point:"); + + foreach (string line in PaginationTools.BuildLinesFromTerms(outputRegions)) + args.Player.SendMessage(line, Color.White); } + + if (!persistentMode) + { + args.Player.AwaitingName = false; + args.Player.AwaitingNameParameters = null; + } + args.Player.SendTileSquare(tileX, tileY); - args.Player.AwaitingName = false; return true; } @@ -1687,22 +1762,63 @@ namespace TShockAPI return true; } - if (type == 1 || type == 3) + byte[] rightClickKill = new byte[] { 4, 13, 33, 49, 50, 128}; + Item selectedItem = args.TPlayer.inventory[args.TPlayer.selectedItem]; + if (type == 0 && Main.tile[tileX, tileY].type != 127 && !Main.tileCut[Main.tile[tileX, tileY].type] && !rightClickKill.Contains(Main.tile[tileX, tileY].type)) { - if (tiletype >= ((type == 1) ? Main.maxTileSets : Main.maxWallTypes)) + // If the tile is an axe tile and they aren't selecting an axe, they're hacking. + if (Main.tileAxe[Main.tile[tileX, tileY].type] && selectedItem.axe == 0) { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + // If the tile is a hammer tile and they aren't selecting an hammer, they're hacking. + else if (Main.tileHammer[Main.tile[tileX, tileY].type] && selectedItem.hammer == 0) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + // If the tile is a pickaxe tile and they aren't selecting an pickaxe, they're hacking. + else if ((!Main.tileAxe[Main.tile[tileX, tileY].type] && !Main.tileHammer[Main.tile[tileX, tileY].type]) && selectedItem.pick == 0) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + } + else if (type == 2) + { + // If they aren't selecting an hammer, they're hacking. + if (selectedItem.hammer == 0) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + } + else if (type == 1 || type == 3) + { + if (type == 1 && TShock.Config.PreventInvalidPlaceStyle && ((tiletype == 4 && style > 8) || + (tiletype == 13 && style > 4) || (tiletype == 15 && style > 1) || (tiletype == 21 && style > 6) || + (tiletype == 82 && style > 5) || (tiletype == 91 && style > 3) || (tiletype == 105 && style > 42) || + (tiletype == 135 && style > 3) || (tiletype == 139 && style > 12) || (tiletype == 144 && style > 2) || + (tiletype == 149 && style > 2))) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + // If they aren't selecting the item which creates the tile or wall, they're hacking. + if (tiletype != 127 && tiletype != (type == 1 ? selectedItem.createTile : selectedItem.createWall)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + if (TShock.Itembans.ItemIsBanned(selectedItem.name, args.Player) || tiletype >= (type == 1 ? Main.maxTileSets : Main.maxWallTypes)) + { + args.Player.SendTileSquare(tileX, tileY); return true; } if (type == 1 && (tiletype == 29 || tiletype == 97) && TShock.Config.ServerSideInventory && TShock.Config.DisablePiggybanksOnSSI) { - args.Player.SendMessage("You cannot place this tile, server side inventory is enabled.", Color.Red); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - if (tiletype == 48 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Spike", args.Player)) - { - args.Player.Disable("Used banned spikes without permission."); + args.Player.SendMessage("You cannot place this tile because server side inventory is enabled.", Color.Red); args.Player.SendTileSquare(tileX, tileY); return true; } @@ -1710,21 +1826,32 @@ namespace TShockAPI { if (TShock.Utils.MaxChests()) { - args.Player.SendMessage("Reached the world's max chest limit, unable to place more.", Color.Red); + args.Player.SendMessage("The world's chest limit has been reached - unable to place more.", Color.Red); args.Player.SendTileSquare(tileX, tileY); return true; } - if ((TShock.Utils.TileValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == 138) || - (TShock.Utils.TileValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == 138)) + if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == 138) || + (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == 138)) { args.Player.SendTileSquare(tileX, tileY); return true; } } - if (tiletype == 141 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Explosives", args.Player)) + } + else if (type == 5) + { + // If they aren't selecting the wrench, they're hacking. + if (args.TPlayer.inventory[args.TPlayer.selectedItem].type != 509) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + } + else if (type == 6) + { + // If they aren't selecting the wire cutter, they're hacking. + if (args.TPlayer.inventory[args.TPlayer.selectedItem].type != 510) { - args.Player.Disable("Used banned explosives tile without permission."); args.Player.SendTileSquare(tileX, tileY); return true; } @@ -2040,17 +2167,18 @@ namespace TShockAPI return true; } - if (!TShock.Config.IgnoreProjUpdate && TShock.CheckProjectilePermission(args.Player, index, type)) + bool hasPermission = !TShock.CheckProjectilePermission(args.Player, index, type); + if (!TShock.Config.IgnoreProjUpdate && !hasPermission) { - if (type == 100) - { //fix for skele prime - Log.Debug("Skeletron Prime's death laser ignored for cheat detection.."); - } - else - { - args.Player.Disable("Does not have projectile permission to update projectile."); - args.Player.RemoveProjectile(ident, owner); - } + if (type == 100) + { //fix for skele prime + Log.Debug("Skeletron Prime's death laser ignored for cheat detection.."); + } + else + { + args.Player.Disable("Does not have projectile permission to update projectile."); + args.Player.RemoveProjectile(ident, owner); + } return true; } @@ -2069,14 +2197,22 @@ namespace TShockAPI if (!args.Player.Group.HasPermission(Permissions.ignoreprojectiledetection)) { - if ((type ==90) && (TShock.Config.ProjIgnoreShrapnel))// ignore shrapnel - { - Log.Debug("Ignoring shrapnel per config.."); - } - else - { - args.Player.ProjectileThreshold++; - } + if ((type == 90) && (TShock.Config.ProjIgnoreShrapnel))// ignore shrapnel + { + Log.Debug("Ignoring shrapnel per config.."); + } + else + { + args.Player.ProjectileThreshold++; + } + } + + // force all explosives server-side. + if (hasPermission && (type == 28 || type == 29 || type == 37)) + { + args.Player.RemoveProjectile(ident, owner); + Projectile.NewProjectile(pos.X, pos.Y, vel.X, vel.Y, type, dmg, knockback); + return true; } return false; @@ -2192,18 +2328,36 @@ namespace TShockAPI bucket = 2; } - if (lava && bucket != 2 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) - { - args.Player.Disable("Using banned lava bucket without permissions."); - args.Player.SendTileSquare(tileX, tileY); - return true; - } + if(lava && bucket != 2) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Spreading lava without holding a lava bucket"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if(lava && (!args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player))) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Using banned lava bucket without permissions"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } - if (!lava && bucket != 1 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) + if (!lava && bucket != 1) + { + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Spreading water without holding a water bucket"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (!lava && (!args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Water Bucket", args.Player))) { - args.Player.Disable("Using banned water bucket without permissions."); + args.Player.SendErrorMessage("You do not have permission to perform this action."); + args.Player.Disable("Using banned water bucket without permissions"); args.Player.SendTileSquare(tileX, tileY); return true; } @@ -2286,7 +2440,7 @@ namespace TShockAPI { if (TShock.Config.BanOnMediumcoreDeath) { - if (!TShock.Utils.Ban(args.Player, TShock.Config.MediumcoreBanReason)) + if (!TShock.Utils.Ban(args.Player, TShock.Config.MediumcoreBanReason, false, "mediumcore-death")) TShock.Utils.ForceKick(args.Player, "Death results in a ban, but can't ban you.", true); } else @@ -2782,7 +2936,7 @@ namespace TShockAPI break; } - TShock.Utils.SendLogs(string.Format("{0} summoned {1}", args.Player.Name, boss), Color.Red); + TShock.Utils.SendLogs(string.Format("{0} summoned {1}", args.Player.Name, boss), Color.PaleVioletRed, args.Player); return false; } } diff --git a/TShockAPI/Group.cs b/TShockAPI/Group.cs index d525ee54..08a2aa9b 100644 --- a/TShockAPI/Group.cs +++ b/TShockAPI/Group.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Linq; using System.Collections.Generic; @@ -28,7 +29,7 @@ namespace TShockAPI /// /// Default chat color. /// - public const string defaultChatColor = "255.255.255"; + public const string defaultChatColor = "255,255,255"; /// /// List of permissions available to the group. @@ -151,6 +152,7 @@ namespace TShockAPI public byte G = 255; public byte B = 255; + public static Group DefaultGroup = null; #if COMPAT_SIGS [Obsolete("This constructor is for signature compatibility for external code only")] public Group(string groupname, Group parentgroup, string chatcolor) @@ -204,7 +206,7 @@ namespace TShockAPI return true; if (traversed.Contains(cur)) { - throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name)); + throw new InvalidOperationException("Infinite group parenting ({0})".SFormat(cur.Name)); } traversed.Add(cur); cur = cur.Parent; @@ -271,6 +273,26 @@ namespace TShockAPI } permissions.Remove(permission); } + + /// + /// Assigns all fields of this instance to another. + /// + /// The other instance. + public void AssignTo(Group otherGroup) + { + otherGroup.Name = Name; + otherGroup.Parent = Parent; + otherGroup.Prefix = Prefix; + otherGroup.Suffix = Suffix; + otherGroup.R = R; + otherGroup.G = G; + otherGroup.B = B; + otherGroup.Permissions = Permissions; + } + + public override string ToString() { + return this.Name; + } } /// @@ -298,4 +320,4 @@ namespace TShockAPI return true; } } -} \ No newline at end of file +} diff --git a/TShockAPI/HandlerList.cs b/TShockAPI/HandlerList.cs index 12822962..128e6c5e 100644 --- a/TShockAPI/HandlerList.cs +++ b/TShockAPI/HandlerList.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,11 +15,11 @@ 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.ComponentModel; using System.Linq; -using System.Text; namespace TShockAPI { diff --git a/TShockAPI/Hooks/GeneralHooks.cs b/TShockAPI/Hooks/GeneralHooks.cs new file mode 100644 index 00000000..f84efabd --- /dev/null +++ b/TShockAPI/Hooks/GeneralHooks.cs @@ -0,0 +1,43 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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 . +*/ + +namespace TShockAPI.Hooks +{ + public class ReloadEventArgs + { + public TSPlayer Player { get; set; } + public ReloadEventArgs(TSPlayer ply) + { + Player = ply; + } + } + + public class GeneralHooks + { + public delegate void ReloadEventD(ReloadEventArgs e); + public static event ReloadEventD ReloadEvent; + + public static void OnReloadEvent(TSPlayer ply) + { + if(ReloadEvent == null) + return; + + ReloadEvent(new ReloadEventArgs(ply)); + } + } +} diff --git a/TShockAPI/Hooks/PlayerHooks.cs b/TShockAPI/Hooks/PlayerHooks.cs new file mode 100644 index 00000000..047fca50 --- /dev/null +++ b/TShockAPI/Hooks/PlayerHooks.cs @@ -0,0 +1,98 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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.Collections.Generic; +using System.ComponentModel; + +namespace TShockAPI.Hooks +{ + public class PlayerPostLoginEventArgs + { + public TSPlayer Player { get; set; } + public PlayerPostLoginEventArgs(TSPlayer ply) + { + Player = ply; + } + } + + public class PlayerPreLoginEventArgs : HandledEventArgs + { + public TSPlayer Player { get; set; } + public string LoginName { get; set; } + public string Password { get; set; } + } + + public class PlayerCommandEventArgs : HandledEventArgs + { + public TSPlayer Player { get; set; } + public string CommandName { get; set; } + public string CommandText { get; set; } + public List Parameters { get; set; } + } + + public static class PlayerHooks + { + public delegate void PlayerPostLoginD(PlayerPostLoginEventArgs e); + public static event PlayerPostLoginD PlayerPostLogin; + + public delegate void PlayerPreLoginD(PlayerPreLoginEventArgs e); + public static event PlayerPreLoginD PlayerPreLogin; + + public delegate void PlayerCommandD(PlayerCommandEventArgs e); + public static event PlayerCommandD PlayerCommand; + + public static void OnPlayerPostLogin(TSPlayer ply) + { + if(PlayerPostLogin == null) + { + return; + } + + PlayerPostLoginEventArgs args = new PlayerPostLoginEventArgs(ply); + PlayerPostLogin(args); + } + + public static bool OnPlayerCommand(TSPlayer player, string cmdName, string cmdText, List args) + { + if (PlayerCommand == null) + { + return false; + } + PlayerCommandEventArgs playerCommandEventArgs = new PlayerCommandEventArgs() + { + Player = player, + CommandName = cmdName, + CommandText = cmdText, + Parameters = args + + }; + PlayerCommand(playerCommandEventArgs); + return playerCommandEventArgs.Handled; + } + + public static bool OnPlayerPreLogin(TSPlayer ply, string name, string pass) + { + if (PlayerPreLogin == null) + return false; + + var args = new PlayerPreLoginEventArgs {Player = ply, LoginName = name, Password = pass}; + PlayerPreLogin(args); + return args.Handled; + } + } +} diff --git a/TShockAPI/Hooks/PlayerLoginEvent.cs b/TShockAPI/Hooks/PlayerLoginEvent.cs deleted file mode 100644 index b533720f..00000000 --- a/TShockAPI/Hooks/PlayerLoginEvent.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TShockAPI.Hooks -{ - class PlayerLoginEventArgs - { - public TSPlayer Player { get; set; } - public PlayerLoginEventArgs(TSPlayer ply) - { - Player = ply; - } - } - - class PlayerLoginEvent - { - public delegate void PlayerLoginD(PlayerLoginEventArgs e); - public static event PlayerLoginD PlayerLogin; - public static void OnPlayerLogin(TSPlayer ply) - { - if(PlayerLogin == null) - { - return; - } - - PlayerLoginEventArgs args = new PlayerLoginEventArgs(ply); - PlayerLogin(args); - } - } -} diff --git a/TShockAPI/IPackable.cs b/TShockAPI/IPackable.cs index 8523aeff..b44bedc8 100644 --- a/TShockAPI/IPackable.cs +++ b/TShockAPI/IPackable.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; namespace TShockAPI diff --git a/TShockAPI/Log.cs b/TShockAPI/Log.cs index 9e54c53a..1e286b09 100644 --- a/TShockAPI/Log.cs +++ b/TShockAPI/Log.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Diagnostics; using System.Globalization; @@ -72,6 +73,16 @@ namespace TShockAPI Write(message, LogLevel.Data); } + /// + /// Writes data to the log file. + /// + /// The format of the message to be written. + /// The format arguments. + public static void Data(String format, params String[] args) + { + Data(String.Format(format, args)); + } + /// /// Writes an error to the log file. /// @@ -81,6 +92,16 @@ namespace TShockAPI Write(message, LogLevel.Error); } + /// + /// Writes an error to the log file. + /// + /// The format of the message to be written. + /// The format arguments. + public static void Error(String format, params String[] args) + { + Error(String.Format(format, args)); + } + /// /// Writes an error to the log file. /// @@ -93,6 +114,16 @@ namespace TShockAPI Write(message, LogLevel.Error); } + /// + /// Writes an error to the log file. + /// + /// The format of the message to be written. + /// The format arguments. + public static void ConsoleError(String format, params String[] args) + { + ConsoleError(String.Format(format, args)); + } + /// /// Writes a warning to the log file. /// @@ -102,6 +133,16 @@ namespace TShockAPI Write(message, LogLevel.Warning); } + /// + /// Writes a warning to the log file. + /// + /// The format of the message to be written. + /// The format arguments. + public static void Warn(String format, params String[] args) + { + Warn(String.Format(format, args)); + } + /// /// Writes an informative string to the log file. /// @@ -111,6 +152,16 @@ namespace TShockAPI Write(message, LogLevel.Info); } + /// + /// Writes an informative string to the log file. + /// + /// The format of the message to be written. + /// The format arguments. + public static void Info(String format, params String[] args) + { + Info(String.Format(format, args)); + } + /// /// Writes an informative string to the log file. Also outputs to the console. /// @@ -123,6 +174,16 @@ namespace TShockAPI Write(message, LogLevel.Info); } + /// + /// Writes an informative string to the log file. Also outputs to the console. + /// + /// The format of the message to be written. + /// The format arguments. + public static void ConsoleInfo(String format, params String[] args) + { + ConsoleInfo(String.Format(format, args)); + } + /// /// Writes a debug string to the log file. /// @@ -132,6 +193,16 @@ namespace TShockAPI Write(message, LogLevel.Debug); } + /// + /// Writes a debug string to the log file. + /// + /// The format of the message to be written. + /// The format arguments. + public static void Debug(String format, params String[] args) + { + Debug(String.Format(format, args)); + } + /// /// Disposes objects that are being used. /// diff --git a/TShockAPI/Net/BaseMsg.cs b/TShockAPI/Net/BaseMsg.cs index 8e0ce232..5db8814e 100644 --- a/TShockAPI/Net/BaseMsg.cs +++ b/TShockAPI/Net/BaseMsg.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.IO.Streams; diff --git a/TShockAPI/Net/DisconnectMsg.cs b/TShockAPI/Net/DisconnectMsg.cs index d62775a4..eaaa6df8 100644 --- a/TShockAPI/Net/DisconnectMsg.cs +++ b/TShockAPI/Net/DisconnectMsg.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.IO.Streams; using System.Text; diff --git a/TShockAPI/Net/NetTile.cs b/TShockAPI/Net/NetTile.cs index 78d53be7..e3cc30f4 100644 --- a/TShockAPI/Net/NetTile.cs +++ b/TShockAPI/Net/NetTile.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.IO.Streams; diff --git a/TShockAPI/Net/ProjectileRemoveMsg.cs b/TShockAPI/Net/ProjectileRemoveMsg.cs index bc119dcc..d8775313 100644 --- a/TShockAPI/Net/ProjectileRemoveMsg.cs +++ b/TShockAPI/Net/ProjectileRemoveMsg.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.IO.Streams; diff --git a/TShockAPI/Net/SpawnMsg.cs b/TShockAPI/Net/SpawnMsg.cs index 36258396..849e5627 100644 --- a/TShockAPI/Net/SpawnMsg.cs +++ b/TShockAPI/Net/SpawnMsg.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.IO.Streams; diff --git a/TShockAPI/Net/WorldInfoMsg.cs b/TShockAPI/Net/WorldInfoMsg.cs index 7b64f2a8..d95a8bf9 100644 --- a/TShockAPI/Net/WorldInfoMsg.cs +++ b/TShockAPI/Net/WorldInfoMsg.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; using System.IO.Streams; diff --git a/TShockAPI/PacketBufferer.cs b/TShockAPI/PacketBufferer.cs index e2ea8e94..d18ac47f 100644 --- a/TShockAPI/PacketBufferer.cs +++ b/TShockAPI/PacketBufferer.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.ComponentModel; @@ -121,24 +122,24 @@ namespace TShockAPI public bool Flush(ServerSock socket) { - try - { - if (socket == null || !socket.active) - return false; + try + { + if (socket == null || !socket.active) + return false; - if (buffers[socket.whoAmI].Count < 1) - return false; + if (buffers[socket.whoAmI].Count < 1) + return false; - byte[] buff = buffers[socket.whoAmI].GetBytes(BytesPerUpdate); - if (buff == null) - return false; + byte[] buff = buffers[socket.whoAmI].GetBytes(BytesPerUpdate); + if (buff == null) + return false; - if (SendBytes(socket, buff)) - { - buffers[socket.whoAmI].Pop(buff.Length); - return true; - } - } + if (SendBytes(socket, buff)) + { + buffers[socket.whoAmI].Pop(buff.Length); + return true; + } + } catch (Exception e) { Log.ConsoleError(e.ToString()); @@ -200,7 +201,14 @@ namespace TShockAPI } catch (SocketException e) { - Log.Warn(e.ToString()); + switch ((uint)e.ErrorCode) + { + case 0x80004005: + break; + default: + Log.Warn(e.ToString()); + break; + } } catch (IOException e) { diff --git a/TShockAPI/PaginationTools.cs b/TShockAPI/PaginationTools.cs new file mode 100644 index 00000000..e64d3e2a --- /dev/null +++ b/TShockAPI/PaginationTools.cs @@ -0,0 +1,316 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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; +using System.Collections.Generic; +using System.Text; + +namespace TShockAPI +{ + public static class PaginationTools + { + public delegate Tuple LineFormatterDelegate(object lineData, int lineIndex, int pageNumber); + + #region [Nested: Settings Class] + public class Settings + { + public bool IncludeHeader { get; set; } + + private string headerFormat; + public string HeaderFormat + { + get { return this.headerFormat; } + set + { + if (value == null) + throw new ArgumentNullException(); + + this.headerFormat = value; + } + } + + public Color HeaderTextColor { get; set; } + public bool IncludeFooter { get; set; } + + private string footerFormat; + public string FooterFormat + { + get { return this.footerFormat; } + set + { + if (value == null) + throw new ArgumentNullException(); + + this.footerFormat = value; + } + } + + public Color FooterTextColor { get; set; } + public string NothingToDisplayString { get; set; } + public LineFormatterDelegate LineFormatter { get; set; } + public Color LineTextColor { get; set; } + + private int maxLinesPerPage; + + public int MaxLinesPerPage + { + get { return this.maxLinesPerPage; } + set + { + if (value <= 0) + throw new ArgumentException("The value has to be greater than zero."); + + this.maxLinesPerPage = value; + } + } + + private int pageLimit; + + public int PageLimit + { + get { return this.pageLimit; } + set + { + if (value < 0) + throw new ArgumentException("The value has to be greater than or equal to zero."); + + this.pageLimit = value; + } + } + + + public Settings() + { + this.IncludeHeader = true; + this.headerFormat = "Page {0} of {1}"; + this.HeaderTextColor = Color.Green; + this.IncludeFooter = true; + this.footerFormat = "Type / {0} for more."; + this.FooterTextColor = Color.Yellow; + this.NothingToDisplayString = null; + this.LineFormatter = null; + this.LineTextColor = Color.White; + this.maxLinesPerPage = 4; + this.pageLimit = 0; + } + } + #endregion + + public static void SendPage( + TSPlayer player, int pageNumber, IEnumerable dataToPaginate, int dataToPaginateCount, Settings settings = null) + { + if (settings == null) + settings = new Settings(); + + if (dataToPaginateCount == 0) + { + if (settings.NothingToDisplayString != null) + { + if (player is TSServerPlayer) + { + player.SendSuccessMessage(settings.NothingToDisplayString); + } + else + { + player.SendMessage(settings.NothingToDisplayString, settings.HeaderTextColor); + } + } + return; + } + + int pageCount = ((dataToPaginateCount - 1) / settings.MaxLinesPerPage) + 1; + if (settings.PageLimit > 0 && pageCount > settings.PageLimit) + pageCount = settings.PageLimit; + if (pageNumber > pageCount) + pageNumber = pageCount; + + if (settings.IncludeHeader) + { + if (player is TSServerPlayer) + { + player.SendSuccessMessage(string.Format(settings.HeaderFormat, pageNumber, pageCount)); + } + else + { + player.SendMessage(string.Format(settings.HeaderFormat, pageNumber, pageCount), settings.HeaderTextColor); + } + } + + int listOffset = (pageNumber - 1) * settings.MaxLinesPerPage; + int offsetCounter = 0; + int lineCounter = 0; + foreach (object lineData in dataToPaginate) + { + if (lineData == null) + continue; + if (offsetCounter++ < listOffset) + continue; + if (lineCounter++ == settings.MaxLinesPerPage) + break; + + string lineMessage; + Color lineColor = settings.LineTextColor; + if (lineData is Tuple) + { + var lineFormat = (Tuple)lineData; + lineMessage = lineFormat.Item1; + lineColor = lineFormat.Item2; + } + else if (settings.LineFormatter != null) + { + try + { + Tuple lineFormat = settings.LineFormatter(lineData, offsetCounter, pageNumber); + if (lineFormat == null) + continue; + + lineMessage = lineFormat.Item1; + lineColor = lineFormat.Item2; + } + catch (Exception ex) + { + throw new InvalidOperationException( + "The method referenced by LineFormatter has thrown an exception. See inner exception for details.", ex); + } + } + else + { + lineMessage = lineData.ToString(); + } + + if (lineMessage != null) + { + if (player is TSServerPlayer) + { + Console.WriteLine(lineMessage); + } + else + { + player.SendMessage(lineMessage, lineColor); + } + } + } + + if (lineCounter == 0) + { + if (settings.NothingToDisplayString != null) + { + if (player is TSServerPlayer) + { + player.SendSuccessMessage(settings.NothingToDisplayString); + } + else + { + player.SendMessage(settings.NothingToDisplayString, settings.HeaderTextColor); + } + } + } + else if (settings.IncludeFooter && pageNumber + 1 <= pageCount) + { + if (player is TSServerPlayer) + { + player.SendInfoMessage(string.Format(settings.FooterFormat, pageNumber + 1, pageNumber, pageCount)); + } + else + { + player.SendMessage(string.Format(settings.FooterFormat, pageNumber + 1, pageNumber, pageCount), settings.FooterTextColor); + } + } + } + + public static void SendPage(TSPlayer player, int pageNumber, IList dataToPaginate, Settings settings = null) + { + PaginationTools.SendPage(player, pageNumber, dataToPaginate, dataToPaginate.Count, settings); + } + + public static List BuildLinesFromTerms( + IEnumerable terms, Func termFormatter = null, string separator = ", ", int maxCharsPerLine = 80) + { + List lines = new List(); + StringBuilder lineBuilder = new StringBuilder(); + foreach (object term in terms) + { + if (term == null && termFormatter == null) + continue; + + string termString; + if (termFormatter != null) + { + try + { + termString = termFormatter(term); + + if (termString == null) + continue; + } + catch (Exception ex) + { + throw new ArgumentException( + "The method represented by termFormatter has thrown an exception. See inner exception for details.", ex); + } + } + else + { + termString = term.ToString(); + } + + bool goesOnNextLine = (lineBuilder.Length + termString.Length > maxCharsPerLine); + if (!goesOnNextLine) + { + if (lineBuilder.Length > 0) + lineBuilder.Append(separator); + lineBuilder.Append(termString); + } + else + { + // A separator should always be at the end of a line as we know it is followed by another line. + lineBuilder.Append(separator); + lines.Add(lineBuilder.ToString()); + lineBuilder.Clear(); + + lineBuilder.Append(termString); + } + } + if (lineBuilder.Length > 0) + lines.Add(lineBuilder.ToString()); + + return lines; + } + + public static bool TryParsePageNumber( + List commandParameters, int expectedParamterIndex, TSPlayer errorMessageReceiver, out int pageNumber) + { + pageNumber = 1; + if (commandParameters.Count <= expectedParamterIndex) + return true; + + string pageNumberRaw = commandParameters[expectedParamterIndex]; + if (!int.TryParse(pageNumberRaw, out pageNumber) || pageNumber < 1) + { + if (errorMessageReceiver != null) + errorMessageReceiver.SendErrorMessage(string.Format("\"{0}\" is not a valid page number.", pageNumberRaw)); + + pageNumber = 1; + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index f7c1b2f1..f4906747 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.ComponentModel; @@ -26,161 +27,258 @@ namespace TShockAPI { public static class Permissions { - //Permissions with blank descriptions basically means its described by the commands it gives access to. + // tshock.account nodes - [Description("Allows player to get user info")] public static readonly string userinfo; + [Description("User can register account in game")] + public static readonly string canregister = "tshock.account.register"; - [Description("")] public static readonly string causeevents; + [Description("User can login in game")] + public static readonly string canlogin = "tshock.account.login"; - [Description("Required to be able to build (modify tiles and liquid)")] public static readonly string canbuild; + [Description("User can change password in game")] + public static readonly string canchangepassword = "tshock.account.changepassword"; - [Description("")] public static readonly string kill; + // tshock.admin nodes - [Description("Allows you to use banned items")] public static readonly string usebanneditem; + [Description("Prevents you from being kicked.")] + public static readonly string immunetokick = "tshock.admin.nokick"; - [Description("Allows you to edit the spawn")] public static readonly string editspawn; + [Description("Prevents you from being banned.")] + public static readonly string immunetoban = "tshock.admin.noban"; - [Description("Prevents you from being kicked")] public static readonly string immunetokick; + [Description("Specific log messages are sent to users with this permission.")] + public static readonly string logs = "tshock.admin.viewlogs"; - [Description("Prevents you from being banned")] public static readonly string immunetoban; + [Description("User can kick others.")] + public static readonly string kick = "tshock.admin.kick"; - [Description("Prevents you from being reverted by kill tile abuse detection")] public static readonly string - ignorekilltiledetection; + [Description("User can ban others.")] + public static readonly string ban = "tshock.admin.ban"; - [Description("Prevents you from being reverted by place tile abuse detection")] public static readonly string - ignoreplacetiledetection; + [Description("User can manage warps.")] + public static readonly string managewarp = "tshock.admin.warp"; - [Description("Prevents you from being disabled by liquid set abuse detection")] public static readonly string - ignoreliquidsetdetection; + [Description("User can manage item bans.")] + public static readonly string manageitem = "tshock.admin.itemban"; - [Description("Prevents you from being disabled by liquid set abuse detection")] public static readonly string - ignoreprojectiledetection; + [Description("User can manage groups.")] + public static readonly string managegroup = "tshock.admin.group"; - [Description("Prevents you from being reverted by no clip detection")] public static readonly string - ignorenoclipdetection; + [Description("User can manage regions.")] + public static readonly string manageregion = "tshock.admin.region"; - [Description("Prevents you from being disabled by stack hack detection")] public static readonly string - ignorestackhackdetection; + [Description("User can mute and unmute users")] + public static readonly string mute = "tshock.admin.mute"; - [Description("Prevents you from being kicked by hacked health detection")] public static readonly string - ignorestathackdetection; + [Description("User can see the id of players with /who")] + public static readonly string seeids = "tshock.admin.seeplayerids"; - [Description("Prevents your actions from being ignored if damage is too high")] public static readonly string - ignoredamagecap; + [Description("User can save all the players SSI state.")] + public static readonly string savessi = "tshock.admin.savessi"; - [Description("Specific log messages are sent to users with this permission")] public static readonly string logs; + [Description("User can elevate other users' groups temporarily.")] + public static readonly string settempgroup = "tshock.admin.tempgroup"; - [Description("Allows you to bypass the max slots for up to 5 slots above your max")] public static readonly string - reservedslot; + [Description("User can broadcast messages.")] + public static readonly string broadcast = "tshock.admin.broadcast"; - [Description("User is notified when an update is available")] public static readonly string maintenance; + [Description("User can get other users' info.")] + public static readonly string userinfo = "tshock.admin.userinfo"; - [Description("User can kick others")] public static readonly string kick; + // tshock.buff nodes - [Description("User can ban others")] public static readonly string ban; + [Description("User can buff self.")] + public static readonly string buff = "tshock.buff.self"; - [Description("User can modify the whitelist")] public static readonly string whitelist; + [Description("User can buff other players.")] + public static readonly string buffplayer = "tshock.buff.others"; - [Description("User can spawn bosses")] public static readonly string spawnboss; + // tshock.cfg nodes - [Description("User can spawn npcs")] public static readonly string spawnmob; + [Description("User is notified when an update is available, user can turn off / restart the server.")] + public static readonly string maintenance = "tshock.cfg.maintenance"; - [Description("User can teleport")] public static readonly string tp; + [Description("User can modify the whitelist.")] + public static readonly string whitelist = "tshock.cfg.whitelist"; - [Description("User can teleport people to them")] public static readonly string tphere; + [Description("User can edit the server password.")] + public static readonly string cfgpassword = "tshock.cfg.password"; - [Description("User can use warps")] public static readonly string warp; + [Description("User can reload the configurations file.")] + public static readonly string cfgreload = "tshock.cfg.reload"; - [Description("User can manage warps")] public static readonly string managewarp; + [Description("User can edit the max spawns.")] + public static readonly string cfgmaxspawns = "tshock.cfg.maxspawns"; - [Description("User can manage item bans")] public static readonly string manageitem; + [Description("User can edit the spawnrate.")] + public static readonly string cfgspawnrate = "tshock.cfg.spawnrate"; - [Description("User can manage groups")] public static readonly string managegroup; + [Description("User can download updates to plugins that are currently running.")] + public static readonly string updateplugins = "tshock.cfg.updateplugins"; - [Description("User can edit sevrer configurations")] public static readonly string cfg; + // tshock.ignore nodes - [Description("")] public static readonly string time; + [Description("Prevents you from being reverted by kill tile abuse detection.")] + public static readonly string ignorekilltiledetection = "tshock.ignore.removetile"; - [Description("")] public static readonly string pvpfun; + [Description("Prevents you from being reverted by place tile abuse detection.")] + public static readonly string ignoreplacetiledetection = "tshock.ignore.placetile"; - [Description("User can edit regions")] public static readonly string manageregion; + [Description("Prevents you from being disabled by liquid set abuse detection.")] + public static readonly string ignoreliquidsetdetection = "tshock.ignore.liquid"; - [Description("Meant for super admins only")] public static readonly string rootonly; + [Description("Prevents you from being disabled by projectile abuse detection.")] + public static readonly string ignoreprojectiledetection = "tshock.ignore.projectile"; - [Description("User can whisper to others")] public static readonly string whisper; + [Description("Prevents you from being reverted by no clip detection.")] + public static readonly string ignorenoclipdetection = "tshock.ignore.noclip"; - [Description("")] public static readonly string annoy; + [Description("Prevents you from being disabled by stack hack detection.")] + public static readonly string ignorestackhackdetection = "tshock.ignore.itemstack"; - [Description("User can kill all enemy npcs")] public static readonly string butcher; + [Description("Prevents you from being kicked by hacked health detection.")] + public static readonly string ignorestathackdetection = "tshock.ignore.stats"; - [Description("User can spawn items")] public static readonly string item; + [Description("Prevents your actions from being ignored if damage is too high.")] + public static readonly string ignoredamagecap = "tshock.ignore.damage"; - [Description("User can clear item drops.")] public static readonly string clearitems; + [Description("Bypass server side inventory checks")] + public static readonly string bypassinventorychecks = "tshock.ignore.ssi"; - [Description("")] public static readonly string heal; + [Description("Allow unrestricted SendTileSquare usage, for client side world editing.")] + public static readonly string allowclientsideworldedit = "tshock.ignore.sendtilesquare"; - [Description("User can buff self")] public static readonly string buff; + // tshock.item nodes - [Description("User can buff other players")] public static readonly string buffplayer; + [Description("User can spawn items.")] + public static readonly string item = "tshock.item.spawn"; - [Description("")] public static readonly string grow; + [Description("User can clear items.")] + public static readonly string clearitems = "tshock.item.clear"; - [Description("User can change hardmode state.")] public static readonly string hardmode; + [Description("Allows you to use banned items.")] + public static readonly string usebanneditem = "tshock.item.usebanned"; - [Description("User can change the homes of NPCs.")] public static readonly string movenpc; + // tshock.npc nodes - [Description("Users can stop people from teleporting to them")] public static readonly string tpallow; + [Description("User can spawn bosses.")] + public static readonly string spawnboss = "tshock.npc.spawnboss"; - [Description("Users can tp to anyone")] public static readonly string tpall; + [Description("User can spawn npcs.")] + public static readonly string spawnmob = "tshock.npc.spawnmob"; - [Description("Users can tp to people without showing a notice")] public static readonly string tphide; + [Description("User can kill all enemy npcs.")] + public static readonly string butcher = "tshock.npc.butcher"; - [Description("User can convert hallow into corruption and vice-versa")] public static readonly string converthardmode; + [Description("User can summon bosses using items")] + public static readonly string summonboss = "tshock.npc.summonboss"; - [Description("User can mute and unmute users")] public static readonly string mute; + [Description("User can start invasions (Goblin/Snow Legion) using items")] + public static readonly string startinvasion = "tshock.npc.startinvasion"; - [Description("User can register account in game")] public static readonly string canregister; + // tshock.superadmin nodes - [Description("User can login in game")] public static readonly string canlogin; + [Description("Meant for super admins only.")] + public static readonly string authverify = "tshock.superadmin.authverify"; - [Description("User can change password in game")] public static readonly string canchangepassword; + [Description("Meant for super admins only.")] + public static readonly string user = "tshock.superadmin.user"; - [Description("User can use party chat in game")] public static readonly string canpartychat; + // tshock.tp nodes - [Description("User can talk in third person")] public static readonly string cantalkinthird; + [Description("User can teleport to others.")] + public static readonly string tp = "tshock.tp.self"; - [Description("Bypass server side inventory checks")] public static readonly string bypassinventorychecks; + [Description("User can teleport people to them.")] + public static readonly string tphere = "tshock.tp.others"; - [Description("Allow unrestricted SendTileSquare usage, for client side world editing.")] public static readonly - string allowclientsideworldedit; + [Description("Users can stop people from teleporting to them")] + public static readonly string tpallow = "tshock.tp.block"; - [Description("User can summon bosses using items")] - public static readonly string summonboss; + [Description("Users can tp to anyone")] + public static readonly string tpall = "tshock.tp.toall"; - [Description("User can start invasions (Goblin/Snow Legion) using items")] - public static readonly string startinvasion; + [Description("Users can tp to people without showing a notice")] + public static readonly string tphide = "tshock.tp.silent"; - [Description("User can see the id of players with /who")] - public static readonly string seeids; + [Description("User can use /home.")] + public static readonly string home = "tshock.tp.home"; - [Description("User can save all the players SSI state.")] - public static readonly string savessi; + [Description("User can use /spawn.")] + public static readonly string spawn = "tshock.tp.spawn"; - [Description("User can use rest api calls.")] - public static readonly string restapi; + // tshock.world nodes - [Description("User can force the server to Christmas mode.")] public static readonly string xmas; + [Description("Allows you to edit the spawn.")] + public static readonly string editspawn = "tshock.world.editspawn"; - static Permissions() - { - foreach (var field in typeof (Permissions).GetFields()) - { - field.SetValue(null, field.Name); - } + [Description("User can set the time.")] + public static readonly string time = "tshock.world.settime"; - //Backwards compatability. - restapi = "api"; - } + [Description("User can grow plants.")] + public static readonly string grow = "tshock.world.grow"; + + [Description("User can change hardmode state.")] + public static readonly string hardmode = "tshock.world.hardmode"; + + [Description("User can change the homes of NPCs.")] + public static readonly string movenpc = "tshock.world.movenpc"; + + [Description("User can convert hallow into corruption and vice-versa")] + public static readonly string converthardmode = "tshock.world.converthardmode"; + + [Description("User can force the server to Christmas mode.")] + public static readonly string xmas = "tshock.world.setxmas"; + + [Description("User can save the world.")] + public static readonly string worldsave = "tshock.world.save"; + + [Description("User can settle liquids.")] + public static readonly string worldsettle = "tshock.world.settleliquids"; + + [Description("User can get the world info.")] + public static readonly string worldinfo = "tshock.world.info"; + + [Description("User can set the world spawn.")] + public static readonly string worldspawn = "tshock.world.setspawn"; + + [Description("User can cause some events.")] + public static readonly string causeevents = "tshock.world.causeevents"; + + [Description("User can modify the world.")] + public static readonly string canbuild = "tshock.world.modify"; + + // Non-grouped + + [Description("User can kill others.")] + public static readonly string kill = "tshock.kill"; + + [Description("Allows you to bypass the max slots for up to 5 slots above your max.")] + public static readonly string reservedslot = "tshock.reservedslot"; + + [Description("User can use warps.")] + public static readonly string warp = "tshock.warp"; + + [Description("User can slap others.")] + public static readonly string slap = "tshock.slap"; + + [Description("User can whisper to others.")] + public static readonly string whisper = "tshock.whisper"; + + [Description("User can annoy others.")] + public static readonly string annoy = "tshock.annoy"; + + [Description("User can heal players.")] + public static readonly string heal = "tshock.heal"; + + [Description("User can use party chat in game")] + public static readonly string canpartychat = "tshock.partychat"; + + [Description("User can talk in third person")] + public static readonly string cantalkinthird = "tshock.thirdperson"; + + [Description("User can get the server info.")] + public static readonly string serverinfo = "tshock.info"; /// /// Lists all commands associated with a given permission @@ -191,7 +289,7 @@ namespace TShockAPI { if (Commands.ChatCommands.Count < 1) Commands.InitCommands(); - return Commands.ChatCommands.Where(c => c.Permission == perm).ToList(); + return Commands.ChatCommands.Where(c => c.Permissions.Contains(perm)).ToList(); } /// diff --git a/TShockAPI/PluginUpdater/PluginUpdaterThread.cs b/TShockAPI/PluginUpdater/PluginUpdaterThread.cs new file mode 100644 index 00000000..b77a1370 --- /dev/null +++ b/TShockAPI/PluginUpdater/PluginUpdaterThread.cs @@ -0,0 +1,87 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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.IO; +using System.Threading; +using Terraria; + +namespace TShockAPI.PluginUpdater +{ + class PluginUpdaterThread + { + private TSPlayer invoker; + public PluginUpdaterThread(TSPlayer player) + { + invoker = player; + PluginVersionCheck.PluginUpdate += PluginUpdate; + HandleUpdate(); + } + + private void HandleUpdate() + { + foreach(PluginContainer cont in ProgramServer.Plugins) + new Thread(PluginVersionCheck.CheckPlugin).Start(cont.Plugin); + } + + private int Updates = 0; + private void PluginUpdate(UpdateArgs args) + { + Updates++; + if(args.Success && String.IsNullOrEmpty(args.Error)) + { + invoker.SendSuccessMessage(String.Format("{0} was downloaded successfully.", args.Plugin.Name)); + } + else if(args.Success) + { + invoker.SendSuccessMessage(String.Format("{0} was skipped. Reason: {1}", args.Plugin.Name, args.Error)); + } + else + { + invoker.SendSuccessMessage(String.Format("{0} failed to downloaded. Error: {1}", args.Plugin.Name, args.Error)); + } + + if(Updates >= Terraria.ProgramServer.Plugins.Count) + { + PluginVersionCheck.PluginUpdate -= PluginUpdate; + + invoker.SendSuccessMessage("All plugins have been downloaded. Now copying them to the plugin folder..."); + + string folder = Path.Combine(TShock.SavePath, "UpdatedPlugins"); + string dest = Path.Combine(TShock.SavePath, "..", "ServerPlugins"); + + foreach (string dir in Directory.GetDirectories(folder, "*", System.IO.SearchOption.AllDirectories)) + { + string new_folder = dest + dir.Substring(folder.Length); + if (!Directory.Exists(new_folder)) + Directory.CreateDirectory(new_folder); + } + + foreach (string file_name in Directory.GetFiles(folder, "*.*", System.IO.SearchOption.AllDirectories)) + { + TSPlayer.Server.SendSuccessMessage(String.Format("Copied {0}", file_name)); + File.Copy(file_name, dest + file_name.Substring(folder.Length), true); + } + + + Directory.Delete(folder, true); + + invoker.SendSuccessMessage("All plugins have been processed. Restart the server to have access to the new plugins."); + } + } + } +} diff --git a/TShockAPI/PluginUpdater/PluginVersionCheck.cs b/TShockAPI/PluginUpdater/PluginVersionCheck.cs new file mode 100644 index 00000000..61858af8 --- /dev/null +++ b/TShockAPI/PluginUpdater/PluginVersionCheck.cs @@ -0,0 +1,127 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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.IO; +using System.Net; +using JsonLoader; +using Newtonsoft.Json; + +namespace TShockAPI.PluginUpdater +{ + public class PluginVersionCheck + { + public delegate void PluginUpdateD(UpdateArgs e); + public static event PluginUpdateD PluginUpdate; + public static void OnPluginUpdate(UpdateArgs args) + { + if (PluginUpdate == null) + { + return; + } + + PluginUpdate(args); + } + + public static void CheckPlugin(object p) + { + TerrariaPlugin plugin = (TerrariaPlugin)p; + UpdateArgs args = new UpdateArgs {Plugin = plugin, Success = true, Error = ""}; + List files = new List(); + + try + { + if (!String.IsNullOrEmpty(plugin.UpdateURL)) + { + var request = HttpWebRequest.Create(plugin.UpdateURL); + VersionInfo vi; + + request.Timeout = 5000; + using (var response = request.GetResponse()) + { + using (var reader = new StreamReader(response.GetResponseStream())) + { + vi = JsonConvert.DeserializeObject(reader.ReadToEnd()); + } + } + + System.Version v = System.Version.Parse((vi.version.ToString())); + + if (!v.Equals(plugin.Version)) + { + DownloadPackage pkg; + request = HttpWebRequest.Create(vi.url); + + request.Timeout = 5000; + using (var response = request.GetResponse()) + { + using (var reader = new StreamReader(response.GetResponseStream())) + { + pkg = JsonConvert.DeserializeObject(reader.ReadToEnd()); + } + } + + foreach (PluginFile f in pkg.files) + { + using (WebClient Client = new WebClient()) + { + string dir = Path.Combine(TShock.SavePath, "UpdatedPlugins"); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + Client.DownloadFile(f.url, + Path.Combine(dir, f.destination)); + + files.Add(Path.Combine(dir, f.destination)); + } + } + } + else + { + args.Error = "Plugin is up to date."; + } + } + else + { + args.Error = "Plugin has no updater recorded."; + } + } + catch(Exception e) + { + args.Success = false; + args.Error = e.Message; + if(files.Count > 0) + { + foreach(string s in files) + { + File.Delete(s); + } + } + } + + OnPluginUpdate(args); + } + } + + public class UpdateArgs + { + public TerrariaPlugin Plugin { get; set; } + public bool Success { get; set; } + public string Error { get; set; } + } +} diff --git a/TShockAPI/PluginUpdater/VersionInfo.cs b/TShockAPI/PluginUpdater/VersionInfo.cs new file mode 100644 index 00000000..4dd061b3 --- /dev/null +++ b/TShockAPI/PluginUpdater/VersionInfo.cs @@ -0,0 +1,87 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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; + +namespace JsonLoader +{ + class VersionInfo + { + public Version version; + public string url; + } + + public class Version + { + public int Major; + public int Minor; + public int Build; + public int Revision; + public int MajorRevision; + public int MinorRevision; + + public Version() + { + SetVersion(0,0,0,0); + } + + public Version(int m) + { + SetVersion(m, 0, 0, 0); + } + + public Version(int ma, int mi) + { + SetVersion(ma, mi, 0, 0); + } + + public Version(int ma, int mi, int b) + { + SetVersion(ma, mi, b, 0); + } + + public Version(int ma, int mi, int b, int r) + { + SetVersion(ma, mi, b, r); + } + + private void SetVersion(int ma, int mi, int b, int r) + { + Major = ma; + Minor = mi; + Build = b; + Revision = r; + } + + public string ToString() + { + return String.Format("{0}.{1}.{2}.{3}", Major, Minor, Build, Revision); + } + } + + class DownloadPackage + { + public List files; + } + + class PluginFile + { + public string url; + public string destination = ""; + } +} diff --git a/TShockAPI/Properties/AssemblyInfo.cs b/TShockAPI/Properties/AssemblyInfo.cs index 2705da44..16f27e6f 100644 --- a/TShockAPI/Properties/AssemblyInfo.cs +++ b/TShockAPI/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Reflection; using System.Runtime.InteropServices; @@ -25,9 +26,9 @@ using System.Runtime.InteropServices; [assembly: AssemblyTitle("TShock for Terraria")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Nyx Team")] +[assembly: AssemblyCompany("Nyx Studios & TShock Contributors")] [assembly: AssemblyProduct("TShockAPI")] -[assembly: AssemblyCopyright("Copyright © Nyx Team 2012")] +[assembly: AssemblyCopyright("Copyright © Nyx Studios 2011-2013")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -48,5 +49,5 @@ using System.Runtime.InteropServices; // Build Number // MMdd of the build -[assembly: AssemblyVersion("4.0.0.0923")] -[assembly: AssemblyFileVersion("4.0.0.0923")] +[assembly: AssemblyVersion("4.1.0.0926")] +[assembly: AssemblyFileVersion("4.1.0.0926")] diff --git a/TShockAPI/RconHandler.cs b/TShockAPI/RconHandler.cs index 3d314f24..3e83f856 100644 --- a/TShockAPI/RconHandler.cs +++ b/TShockAPI/RconHandler.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.IO; diff --git a/TShockAPI/Resources.Designer.cs b/TShockAPI/Resources.Designer.cs index 99b8a890..d22d260b 100644 --- a/TShockAPI/Resources.Designer.cs +++ b/TShockAPI/Resources.Designer.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -27,9 +27,6 @@ along with this program. If not, see . //------------------------------------------------------------------------------ namespace TShockAPI { - using System; - - /// /// A strongly-typed resource class, for looking up localized strings, etc. /// diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index f0578b7c..1d827a35 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.ComponentModel; @@ -38,6 +39,15 @@ namespace Rests /// Response object or null to not handle request public delegate object RestCommandD(RestVerbs verbs, IParameterCollection parameters); + /// + /// Secure Rest command delegate including token data. + /// + /// Parameters in the url + /// {x} in urltemplate + /// The data of stored for the provided token. + /// Response object or null to not handle request + public delegate object SecureRestCommandD(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData); + public class Rest : IDisposable { private readonly List commands = new List(); @@ -170,24 +180,47 @@ namespace Rests } catch (Exception exception) { - return new Dictionary + return new RestObject("500") { - {"status", "500"}, {"error", "Internal server error."}, {"errormsg", exception.Message}, {"stacktrace", exception.StackTrace}, }; } - return new Dictionary + return new RestObject("404") { - {"status", "404"}, {"error", "Specified API endpoint doesn't exist. Refer to the documentation for a list of valid endpoints."} }; } protected virtual object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) { - return cmd.Callback(verbs, parms); + object result = cmd.Execute(verbs, parms); + if (cmd.DoLog) + Log.ConsoleInfo("Anonymous requested REST endpoint: " + BuildRequestUri(cmd, verbs, parms, false)); + + return result; + } + + protected virtual string BuildRequestUri( + RestCommand cmd, RestVerbs verbs, IParameterCollection parms, bool includeToken = true + ) { + StringBuilder requestBuilder = new StringBuilder(cmd.UriTemplate); + char separator = '?'; + foreach (IParameter paramImpl in parms) + { + Parameter param = (paramImpl as Parameter); + if (param == null || (!includeToken && param.Name.Equals("token", StringComparison.InvariantCultureIgnoreCase))) + continue; + + requestBuilder.Append(separator); + requestBuilder.Append(param.Name); + requestBuilder.Append('='); + requestBuilder.Append(param.Value); + separator = '&'; + } + + return requestBuilder.ToString(); } #region Dispose diff --git a/TShockAPI/Rest/RestCommand.cs b/TShockAPI/Rest/RestCommand.cs index 334752de..c0db0a9a 100644 --- a/TShockAPI/Rest/RestCommand.cs +++ b/TShockAPI/Rest/RestCommand.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,8 +15,10 @@ 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.Linq; using System.Text.RegularExpressions; +using HttpServer; namespace Rests { @@ -26,8 +28,10 @@ namespace Rests public string UriTemplate { get; protected set; } public string UriVerbMatch { get; protected set; } public string[] UriVerbs { get; protected set; } - public RestCommandD Callback { get; protected set; } - public bool RequiresToken { get; set; } + public virtual bool RequiresToken { get { return false; } } + public bool DoLog { get; set; } + + private RestCommandD callback; /// /// @@ -42,8 +46,8 @@ namespace Rests UriVerbMatch = string.Format("^{0}$", string.Join("([^/]*)", Regex.Split(uritemplate, "\\{[^\\{\\}]*\\}"))); var matches = Regex.Matches(uritemplate, "\\{([^\\{\\}]*)\\}"); UriVerbs = (from Match match in matches select match.Groups[1].Value).ToArray(); - Callback = callback; - RequiresToken = true; + this.callback = callback; + DoLog = true; } /// @@ -60,5 +64,43 @@ namespace Rests { get { return UriVerbs.Length > 0; } } + + public virtual object Execute(RestVerbs verbs, IParameterCollection parameters) + { + return callback(verbs, parameters); + } + } + + public class SecureRestCommand: RestCommand + { + public override bool RequiresToken { get { return true; } } + public string[] Permissions { get; set; } + + private SecureRestCommandD callback; + + public SecureRestCommand(string name, string uritemplate, SecureRestCommandD callback, params string[] permissions) + : base(name, uritemplate, null) + { + this.callback = callback; + Permissions = permissions; + } + + public SecureRestCommand(string uritemplate, SecureRestCommandD callback, params string[] permissions) + : this(string.Empty, uritemplate, callback, permissions) + { + } + + public override object Execute(RestVerbs verbs, IParameterCollection parameters) + { + return new RestObject("401") { Error = "Not authorized. The specified API endpoint requires a token." }; + } + + public object Execute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + if (tokenData.Equals(SecureRest.TokenData.None)) + return new RestObject("401") { Error = "Not authorized. The specified API endpoint requires a token." }; + + return callback(verbs, parameters, tokenData); + } } } \ No newline at end of file diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 45cdb309..8d872fe2 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,9 +15,11 @@ 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; using System.Collections.Generic; +using System.IO; using System.Linq; using HttpServer; using Rests; @@ -38,78 +40,140 @@ namespace TShockAPI public void RegisterRestfulCommands() { // Server Commands - Rest.Register(new RestCommand("/v2/server/broadcast", ServerBroadcast)); - Rest.Register(new RestCommand("/v2/server/off", ServerOff)); - Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand)); - Rest.Register(new RestCommand("/v2/server/status", ServerStatusV2) { RequiresToken = false }); - Rest.Register(new RestCommand("/tokentest", ServerTokenTest)); - Rest.Register(new RestCommand("/status", ServerStatus) { RequiresToken = false }); + if (TShock.Config.EnableTokenEndpointAuthentication) + { + Rest.Register(new SecureRestCommand("/v2/server/status", ServerStatusV2)); + Rest.Register(new SecureRestCommand("/status", ServerStatus)); + Rest.Register(new SecureRestCommand("/v3/server/motd", ServerMotd)); + Rest.Register(new SecureRestCommand("/v3/server/rules", ServerRules)); + } + else + { + Rest.Register(new RestCommand("/v2/server/status", (a, b) => this.ServerStatusV2(a, b, SecureRest.TokenData.None))); + Rest.Register(new RestCommand("/status", (a, b) => this.ServerStatusV2(a, b, SecureRest.TokenData.None))); + Rest.Register(new RestCommand("/v3/server/motd", (a, b) => this.ServerMotd(a, b, SecureRest.TokenData.None))); + Rest.Register(new RestCommand("/v3/server/rules", (a, b) => this.ServerRules(a, b, SecureRest.TokenData.None))); + } + + Rest.Register(new SecureRestCommand("/v2/server/broadcast", ServerBroadcast)); + Rest.Register(new SecureRestCommand("/v3/server/reload", ServerReload, RestPermissions.restcfg)); + Rest.Register(new SecureRestCommand("/v2/server/off", ServerOff, RestPermissions.restmaintenance)); + Rest.Register(new SecureRestCommand("/v3/server/restart", ServerRestart, RestPermissions.restmaintenance)); + Rest.Register(new SecureRestCommand("/v2/server/rawcmd", ServerCommand, RestPermissions.restrawcommand)); + Rest.Register(new SecureRestCommand("/v3/server/rawcmd", ServerCommandV3, RestPermissions.restrawcommand)); + Rest.Register(new SecureRestCommand("/tokentest", ServerTokenTest)); // User Commands - Rest.Register(new RestCommand("/v2/users/activelist", UserActiveListV2)); - Rest.Register(new RestCommand("/v2/users/create", UserCreateV2)); - Rest.Register(new RestCommand("/v2/users/list", UserListV2)); - Rest.Register(new RestCommand("/v2/users/read", UserInfoV2)); - Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2)); - Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2)); + Rest.Register(new SecureRestCommand("/v2/users/activelist", UserActiveListV2, RestPermissions.restviewusers)); + Rest.Register(new SecureRestCommand("/v2/users/create", UserCreateV2, RestPermissions.restmanageusers) { DoLog = false }); + Rest.Register(new SecureRestCommand("/v2/users/list", UserListV2, RestPermissions.restviewusers)); + Rest.Register(new SecureRestCommand("/v2/users/read", UserInfoV2, RestPermissions.restviewusers)); + Rest.Register(new SecureRestCommand("/v2/users/destroy", UserDestroyV2, RestPermissions.restmanageusers)); + Rest.Register(new SecureRestCommand("/v2/users/update", UserUpdateV2, RestPermissions.restmanageusers) { DoLog = false }); // Ban Commands - Rest.Register(new RestCommand("/bans/create", BanCreate)); - Rest.Register(new RestCommand("/v2/bans/list", BanListV2)); - Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2)); - Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2)); + Rest.Register(new SecureRestCommand("/bans/create", BanCreate, RestPermissions.restmanagebans)); + Rest.Register(new SecureRestCommand("/v2/bans/list", BanListV2, RestPermissions.restviewbans)); + Rest.Register(new SecureRestCommand("/v2/bans/read", BanInfoV2, RestPermissions.restviewbans)); + Rest.Register(new SecureRestCommand("/v2/bans/destroy", BanDestroyV2, RestPermissions.restmanagebans)); // World Commands - Rest.Register(new RestCommand("/world/read", WorldRead)); - Rest.Register(new RestCommand("/world/meteor", WorldMeteor)); - Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon)); - Rest.Register(new RestCommand("/v2/world/save", WorldSave)); - Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings)); - Rest.Register(new RestCommand("/v2/world/butcher", WorldButcher)); + Rest.Register(new SecureRestCommand("/world/read", WorldRead)); + Rest.Register(new SecureRestCommand("/world/meteor", WorldMeteor, RestPermissions.restcauseevents)); + Rest.Register(new SecureRestCommand("/world/bloodmoon/{bool}", WorldBloodmoon, RestPermissions.restcauseevents)); + Rest.Register(new SecureRestCommand("/v2/world/save", WorldSave, RestPermissions.restcfg)); + Rest.Register(new SecureRestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings, RestPermissions.restcfg)); + Rest.Register(new SecureRestCommand("/v2/world/butcher", WorldButcher, RestPermissions.restbutcher)); // Player Commands - Rest.Register(new RestCommand("/lists/players", PlayerList)); - Rest.Register(new RestCommand("/v2/players/list", PlayerListV2)); - Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2)); - Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2)); - Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2)); - Rest.Register(new RestCommand("/v2/players/kill", PlayerKill)); - Rest.Register(new RestCommand("/v2/players/mute", PlayerMute)); - Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute)); + Rest.Register(new SecureRestCommand("/lists/players", PlayerList)); + Rest.Register(new SecureRestCommand("/v2/players/list", PlayerListV2)); + Rest.Register(new SecureRestCommand("/v2/players/read", PlayerReadV2, RestPermissions.restuserinfo)); + Rest.Register(new SecureRestCommand("/v2/players/kick", PlayerKickV2, RestPermissions.restkick)); + Rest.Register(new SecureRestCommand("/v2/players/ban", PlayerBanV2, RestPermissions.restban, RestPermissions.restmanagebans)); + Rest.Register(new SecureRestCommand("/v2/players/kill", PlayerKill, RestPermissions.restkill)); + Rest.Register(new SecureRestCommand("/v2/players/mute", PlayerMute, RestPermissions.restmute)); + Rest.Register(new SecureRestCommand("/v2/players/unmute", PlayerUnMute, RestPermissions.restmute)); // Group Commands - Rest.Register(new RestCommand("/v2/groups/list", GroupList)); - Rest.Register(new RestCommand("/v2/groups/read", GroupInfo)); - Rest.Register(new RestCommand("/v2/groups/destroy", GroupDestroy)); - Rest.Register(new RestCommand("/v2/groups/create", GroupCreate)); - Rest.Register(new RestCommand("/v2/groups/update", GroupUpdate)); + Rest.Register(new SecureRestCommand("/v2/groups/list", GroupList, RestPermissions.restviewgroups)); + Rest.Register(new SecureRestCommand("/v2/groups/read", GroupInfo, RestPermissions.restviewgroups)); + Rest.Register(new SecureRestCommand("/v2/groups/destroy", GroupDestroy, RestPermissions.restmanagegroups)); + Rest.Register(new SecureRestCommand("/v2/groups/create", GroupCreate, RestPermissions.restmanagegroups)); + Rest.Register(new SecureRestCommand("/v2/groups/update", GroupUpdate, RestPermissions.restmanagegroups)); } #region RestServerMethods - private object ServerCommand(RestVerbs verbs, IParameterCollection parameters) + private object ServerCommand(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { if (string.IsNullOrWhiteSpace(parameters["cmd"])) return RestMissingParam("cmd"); - TSRestPlayer tr = new TSRestPlayer(); + Group restPlayerGroup; + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestUseNewPermissionModel) + restPlayerGroup = TShock.Groups.GetGroupByName(tokenData.UserGroupName); + else + restPlayerGroup = new SuperAdminGroup(); + + TSRestPlayer tr = new TSRestPlayer(tokenData.Username, restPlayerGroup); Commands.HandleCommand(tr, parameters["cmd"]); return RestResponse(string.Join("\n", tr.GetCommandOutput())); } - private object ServerOff(RestVerbs verbs, IParameterCollection parameters) + private object ServerCommandV3(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + if (string.IsNullOrWhiteSpace(parameters["cmd"])) + return RestMissingParam("cmd"); + + Group restPlayerGroup; + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestUseNewPermissionModel) + restPlayerGroup = TShock.Groups.GetGroupByName(tokenData.UserGroupName); + else + restPlayerGroup = new SuperAdminGroup(); + + TSRestPlayer tr = new TSRestPlayer(tokenData.Username, restPlayerGroup); + Commands.HandleCommand(tr, parameters["cmd"]); + return new RestObject() + { + {"response", tr.GetCommandOutput()} + }; + } + + private object ServerOff(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { if (!GetBool(parameters["confirm"], false)) return RestInvalidParam("confirm"); // Inform players the server is shutting down - var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; - TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), msg); + var reason = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; + TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), reason); return RestResponse("The server is shutting down"); } - private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters) + private object ServerRestart(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + if (!GetBool(parameters["confirm"], false)) + return RestInvalidParam("confirm"); + + // Inform players the server is shutting down + var reason = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is restarting" : parameters["message"]; + TShock.Utils.RestartServer(!GetBool(parameters["nosave"], false), reason); + + return RestResponse("The server is shutting down and will attempt to restart"); + } + + private object ServerReload(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + TShock.Utils.Reload(new TSRestPlayer(tokenData.Username, TShock.Groups.GetGroupByName(tokenData.UserGroupName))); + + return RestResponse("Configuration, permissions, and regions reload complete. Some changes may require a server restart."); + } + + private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var msg = parameters["msg"]; if (string.IsNullOrWhiteSpace(msg)) @@ -118,11 +182,32 @@ namespace TShockAPI return RestResponse("The message was broadcasted successfully"); } - private object ServerStatus(RestVerbs verbs, IParameterCollection parameters) + private object ServerMotd(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { - if (TShock.Config.EnableTokenEndpointAuthentication) - return RestError("Server settings require a token for this API call"); + string motdFilePath = Path.Combine(TShock.SavePath, "motd.txt"); + if (!File.Exists(motdFilePath)) + return this.RestError("The motd.txt was not found.", "500"); + return new RestObject() + { + {"motd", File.ReadAllLines(motdFilePath)} + }; + } + + private object ServerRules(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + string rulesFilePath = Path.Combine(TShock.SavePath, "rules.txt"); + if (!File.Exists(rulesFilePath)) + return this.RestError("The rules.txt was not found.", "500"); + + return new RestObject() + { + {"rules", File.ReadAllLines(rulesFilePath)} + }; + } + + private object ServerStatus(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); return new RestObject() { @@ -133,18 +218,17 @@ namespace TShockAPI }; } - private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters) + private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { - if (TShock.Config.EnableTokenEndpointAuthentication) - return RestError("Server settings require a token for this API call"); - var ret = new RestObject() { {"name", TShock.Config.ServerName}, {"port", TShock.Config.ServerPort}, {"playercount", Main.player.Where(p => null != p && p.active).Count()}, {"maxplayers", TShock.Config.MaxSlots}, - {"world", Main.worldName} + {"world", Main.worldName}, + {"uptime", (DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime).ToString(@"d'.'hh':'mm':'ss")}, + {"serverpassword", !string.IsNullOrEmpty(TShock.Config.ServerPassword)} }; if (GetBool(parameters["players"], false)) @@ -174,52 +258,56 @@ namespace TShockAPI rules.Add("PvPMode", TShock.Config.PvPMode); rules.Add("SpawnProtection", TShock.Config.SpawnProtection); rules.Add("SpawnProtectionRadius", TShock.Config.SpawnProtectionRadius); + rules.Add("ServerSideInventory", TShock.Config.ServerSideInventory); ret.Add("rules", rules); } return ret; } - private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters) + private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { - return RestResponse("Token is valid and was passed through correctly"); + return new RestObject() + { + {"response", "Token is valid and was passed through correctly."}, + {"associateduser", tokenData.Username} + }; } #endregion #region RestUserMethods - private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters) + private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return new RestObject() { { "activeusers", string.Join("\t", TShock.Players.Where(p => null != p && null != p.UserAccountName && p.Active).Select(p => p.UserAccountName)) } }; } - private object UserListV2(RestVerbs verbs, IParameterCollection parameters) + private object UserListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return new RestObject() { { "users", TShock.Users.GetUsers().Select(p => new Dictionary(){ {"name", p.Name}, {"id", p.ID}, {"group", p.Group}, - {"ip", p.Address}, }) } }; } - private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters) + private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var username = parameters["user"]; if (string.IsNullOrWhiteSpace(username)) return RestMissingParam("user"); var group = parameters["group"]; - if (string.IsNullOrWhiteSpace(group)) - return RestMissingParam("group"); + if (string.IsNullOrWhiteSpace(group)) + group = TShock.Config.DefaultRegistrationGroupName; var password = parameters["password"]; if (string.IsNullOrWhiteSpace(password)) return RestMissingParam("password"); // NOTE: ip can be blank - User user = new User(parameters["ip"], username, password, group); + User user = new User(username, password, group, "", ""); try { TShock.Users.AddUser(user); @@ -232,7 +320,7 @@ namespace TShockAPI return RestResponse("User was successfully created"); } - private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters) + private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = UserFind(parameters); if (ret is RestObject) @@ -274,7 +362,7 @@ namespace TShockAPI return response; } - private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters) + private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = UserFind(parameters); if (ret is RestObject) @@ -292,7 +380,7 @@ namespace TShockAPI return RestResponse("User deleted successfully"); } - private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters) + private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = UserFind(parameters); if (ret is RestObject) @@ -306,7 +394,7 @@ namespace TShockAPI #region RestBanMethods - private object BanCreate(RestVerbs verbs, IParameterCollection parameters) + private object BanCreate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ip = parameters["ip"]; var name = parameters["name"]; @@ -316,7 +404,7 @@ namespace TShockAPI try { - TShock.Bans.AddBan(ip, name, parameters["reason"], true); + TShock.Bans.AddBan(ip, name, parameters["reason"], true, tokenData.Username); } catch (Exception e) { @@ -325,7 +413,7 @@ namespace TShockAPI return RestResponse("Ban created successfully"); } - private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters) + private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = BanFind(parameters); if (ret is RestObject) @@ -357,7 +445,7 @@ namespace TShockAPI return RestResponse("Ban deleted successfully"); } - private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters) + private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = BanFind(parameters); if (ret is RestObject) @@ -371,7 +459,7 @@ namespace TShockAPI }; } - private object BanListV2(RestVerbs verbs, IParameterCollection parameters) + private object BanListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var banList = new ArrayList(); foreach (var ban in TShock.Bans.GetBans()) @@ -393,7 +481,7 @@ namespace TShockAPI #region RestWorldMethods - private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters) + private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { bool autoSave; if (!bool.TryParse(verbs["bool"], out autoSave)) @@ -403,14 +491,14 @@ namespace TShockAPI return RestResponse("AutoSave has been set to " + autoSave); } - private object WorldSave(RestVerbs verbs, IParameterCollection parameters) + private object WorldSave(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { SaveManager.Instance.SaveWorld(); return RestResponse("World saved"); } - private object WorldButcher(RestVerbs verbs, IParameterCollection parameters) + private object WorldButcher(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { bool killFriendly; if (!bool.TryParse(parameters["killfriendly"], out killFriendly)) @@ -432,7 +520,7 @@ namespace TShockAPI return RestResponse(killcount + " NPCs have been killed"); } - private object WorldRead(RestVerbs verbs, IParameterCollection parameters) + private object WorldRead(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return new RestObject() { @@ -445,7 +533,7 @@ namespace TShockAPI }; } - private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters) + private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { if (null == WorldGen.genRand) WorldGen.genRand = new Random(); @@ -453,7 +541,7 @@ namespace TShockAPI return RestResponse("Meteor has been spawned"); } - private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters) + private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { bool bloodmoon; if (!bool.TryParse(verbs["bool"], out bloodmoon)) @@ -467,23 +555,23 @@ namespace TShockAPI #region RestPlayerMethods - private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters) + private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return PlayerSetMute(parameters, false); } - private object PlayerMute(RestVerbs verbs, IParameterCollection parameters) + private object PlayerMute(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { return PlayerSetMute(parameters, true); } - private object PlayerList(RestVerbs verbs, IParameterCollection parameters) + private object PlayerList(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; } - private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var playerList = new ArrayList(); foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) @@ -495,7 +583,7 @@ namespace TShockAPI return new RestObject() { { "players", playerList } }; } - private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -515,7 +603,7 @@ namespace TShockAPI }; } - private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -526,7 +614,7 @@ namespace TShockAPI return RestResponse("Player " + player.Name + " was kicked"); } - private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters) + private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -539,7 +627,7 @@ namespace TShockAPI return RestResponse("Player " + player.Name + " was banned"); } - private object PlayerKill(RestVerbs verbs, IParameterCollection parameters) + private object PlayerKill(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = PlayerFind(parameters); if (ret is RestObject) @@ -556,7 +644,7 @@ namespace TShockAPI #region RestGroupMethods - private object GroupList(RestVerbs verbs, IParameterCollection parameters) + private object GroupList(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var groups = new ArrayList(); foreach (Group group in TShock.Groups) @@ -566,7 +654,7 @@ namespace TShockAPI return new RestObject() { { "groups", groups } }; } - private object GroupInfo(RestVerbs verbs, IParameterCollection parameters) + private object GroupInfo(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = GroupFind(parameters); if (ret is RestObject) @@ -583,7 +671,7 @@ namespace TShockAPI }; } - private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters) + private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = GroupFind(parameters); if (ret is RestObject) @@ -602,7 +690,7 @@ namespace TShockAPI return RestResponse("Group '" + group.Name + "' deleted successfully"); } - private object GroupCreate(RestVerbs verbs, IParameterCollection parameters) + private object GroupCreate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var name = parameters["group"]; if (string.IsNullOrWhiteSpace(name)) @@ -619,7 +707,7 @@ namespace TShockAPI return RestResponse("Group '" + name + "' created successfully"); } - private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters) + private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var ret = GroupFind(parameters); if (ret is RestObject) @@ -713,10 +801,6 @@ namespace TShockAPI break; case "id": user = TShock.Users.GetUserByID(Convert.ToInt32(name)); - break; - case "ip": - user = TShock.Users.GetUserByIP(name); - break; default: return RestError("Invalid Type: '" + type + "'"); diff --git a/TShockAPI/Rest/RestObject.cs b/TShockAPI/Rest/RestObject.cs index 0133ff6f..4edfae9c 100644 --- a/TShockAPI/Rest/RestObject.cs +++ b/TShockAPI/Rest/RestObject.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; diff --git a/TShockAPI/Rest/RestPermissions.cs b/TShockAPI/Rest/RestPermissions.cs new file mode 100644 index 00000000..81664e7e --- /dev/null +++ b/TShockAPI/Rest/RestPermissions.cs @@ -0,0 +1,87 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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.ComponentModel; + +namespace Rests +{ + public static class RestPermissions + { + // tshock.rest.bans nodes + + [Description("REST user can list and get detailed information about bans.")] + public static readonly string restviewbans = "tshock.rest.bans.view"; + + [Description("REST user can alter bans.")] + public static readonly string restmanagebans = "tshock.rest.bans.manage"; + + // tshock.rest.groups nodes + + [Description("REST user can list and get detailed information about groups.")] + public static readonly string restviewgroups = "tshock.rest.groups.view"; + + [Description("REST user can alter groups.")] + public static readonly string restmanagegroups = "tshock.rest.groups.manage"; + + // tshock.rest.users nodes + + [Description("REST user can list and get detailed information about users.")] + public static readonly string restviewusers = "tshock.rest.users.view"; + + [Description("REST user can alter users.")] + public static readonly string restmanageusers = "tshock.rest.users.manage"; + + [Description("REST user can get user information.")] + public static readonly string restuserinfo = "tshock.rest.users.info"; + + // Non-grouped nodes + + [Description("User can create REST tokens.")] + public static readonly string restapi = "tshock.rest.useapi"; + + [Description("User or REST user can destroy all REST tokens.")] + public static readonly string restmanage = "tshock.rest.manage"; + + [Description("REST user can turn off / restart the server.")] + public static readonly string restmaintenance = "tshock.rest.maintenance"; + + [Description("REST user can reload configurations, save the world and set auto save settings.")] + public static readonly string restcfg = "tshock.rest.cfg"; + + [Description("REST user can kick players.")] + public static readonly string restkick = "tshock.rest.kick"; + + [Description("REST user can ban players.")] + public static readonly string restban = "tshock.rest.ban"; + + [Description("REST user can mute and unmute players.")] + public static readonly string restmute = "tshock.rest.mute"; + + [Description("REST user can kill players.")] + public static readonly string restkill = "tshock.rest.kill"; + + [Description("REST user can drop meteors or change bloodmoon.")] + public static readonly string restcauseevents = "tshock.rest.causeevents"; + + [Description("REST user can butcher npcs.")] + public static readonly string restbutcher = "tshock.rest.butcher"; + + [Description("REST user can run raw TShock commands (the raw command permissions are also checked though).")] + public static readonly string restrawcommand = "tshock.rest.command"; + } +} diff --git a/TShockAPI/Rest/RestVerbs.cs b/TShockAPI/Rest/RestVerbs.cs index ea155fcb..c63642d4 100644 --- a/TShockAPI/Rest/RestVerbs.cs +++ b/TShockAPI/Rest/RestVerbs.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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; diff --git a/TShockAPI/Rest/SecureRest.cs b/TShockAPI/Rest/SecureRest.cs index 987cd295..a3257f18 100644 --- a/TShockAPI/Rest/SecureRest.cs +++ b/TShockAPI/Rest/SecureRest.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,41 +15,76 @@ 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.Linq; using System.Net; using HttpServer; +using TShockAPI; +using TShockAPI.DB; namespace Rests { - /// - /// - /// - /// Username to verify - /// Password to verify - /// Returning a restobject with a null error means a successful verification. - public delegate RestObject VerifyD(string username, string password); - public class SecureRest : Rest { - public Dictionary Tokens { get; protected set; } - public event VerifyD Verify; + public struct TokenData + { + public static readonly TokenData None = default(TokenData); + + public string Username { get; set; } + public string UserGroupName { get; set; } + } + + public Dictionary Tokens { get; protected set; } + public Dictionary AppTokens { get; protected set; } public SecureRest(IPAddress ip, int port) : base(ip, port) { - Tokens = new Dictionary(); - Register(new RestCommand("/token/create/{username}/{password}", NewToken) {RequiresToken = false}); - Register(new RestCommand("/v2/token/create/{password}", NewTokenV2) { RequiresToken = false }); - Register(new RestCommand("/token/destroy/{token}", DestroyToken) {RequiresToken = true}); - foreach (KeyValuePair t in TShockAPI.TShock.RESTStartupTokens) + Tokens = new Dictionary(); + AppTokens = new Dictionary(); + + Register(new RestCommand("/token/create/{username}/{password}", NewToken) { DoLog = false }); + Register(new RestCommand("/v2/token/create/{password}", NewTokenV2) { DoLog = false }); + Register(new SecureRestCommand("/token/destroy/{token}", DestroyToken)); + Register(new SecureRestCommand("/v3/token/destroy/all", DestroyAllTokens, RestPermissions.restmanage)); + + foreach (KeyValuePair t in TShockAPI.TShock.RESTStartupTokens) { - Tokens.Add(t.Key, t.Value); + AppTokens.Add(t.Key, t.Value); + } + + foreach (KeyValuePair t in TShock.Config.ApplicationRestTokens) + { + AppTokens.Add(t.Key, t.Value); + } + + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestApiEnabled && !TShock.Config.RestUseNewPermissionModel) + { + string warningMessage = string.Concat( + "You're using the old REST permission model which is highly vulnerable in matter of security. ", + "The old model will be removed with the next maintenance release of TShock. In order to switch to the new model, ", + "change the config setting \"RestUseNewPermissionModel\" to true." + ); + Log.Warn(warningMessage); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(warningMessage); + Console.ForegroundColor = ConsoleColor.Gray; + } + else if (TShock.Config.RestApiEnabled) + { + string warningMessage = string.Concat( + "You're using the new more secure REST permission model which can lead to compatibility problems ", + "with existing REST services. If compatibility problems occur, you can switch back to the unsecure permission ", + "model by changing the config setting \"RestUseNewPermissionModel\" to false, which is not recommended." + ); + Log.ConsoleInfo(warningMessage); } } - private object DestroyToken(RestVerbs verbs, IParameterCollection parameters) + private object DestroyToken(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) { var token = verbs["token"]; try @@ -58,11 +93,19 @@ namespace Rests } catch (Exception) { - return new Dictionary - {{"status", "400"}, {"error", "The specified token queued for destruction failed to be deleted."}}; + return new RestObject("400") + { Error = "The specified token queued for destruction failed to be deleted." }; } - return new Dictionary - {{"status", "200"}, {"response", "Requested token was successfully destroyed."}}; + return new RestObject() + { Response = "Requested token was successfully destroyed." }; + } + + private object DestroyAllTokens(RestVerbs verbs, IParameterCollection parameters, SecureRest.TokenData tokenData) + { + Tokens.Clear(); + + return new RestObject() + { Response = "All tokens were successfully destroyed." }; } private object NewTokenV2(RestVerbs verbs, IParameterCollection parameters) @@ -70,29 +113,7 @@ namespace Rests var user = parameters["username"]; var pass = verbs["password"]; - RestObject obj = null; - if (Verify != null) - obj = Verify(user, pass); - - if (obj == null) - obj = new RestObject("401") { Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." }; - - if (obj.Error != null) - return obj; - - string hash; - var rand = new Random(); - var randbytes = new byte[32]; - do - { - rand.NextBytes(randbytes); - hash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); - } while (Tokens.ContainsKey(hash)); - - Tokens.Add(hash, user); - - obj["token"] = hash; - return obj; + return this.NewTokenInternal(user, pass); } private object NewToken(RestVerbs verbs, IParameterCollection parameters) @@ -100,55 +121,83 @@ namespace Rests var user = verbs["username"]; var pass = verbs["password"]; - RestObject obj = null; - if (Verify != null) - obj = Verify(user, pass); + RestObject response = this.NewTokenInternal(user, pass); + response["deprecated"] = "This endpoint is depracted and will be removed in the future."; + return response; + } - if (obj == null) - obj = new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; + private RestObject NewTokenInternal(string username, string password) + { + User userAccount = TShock.Users.GetUserByName(username); + if (userAccount == null) + return new RestObject("401") { Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." }; + + if (!TShock.Utils.HashPassword(password).Equals(userAccount.Password, StringComparison.InvariantCultureIgnoreCase)) + return new RestObject("401") + { Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair." }; - if (obj.Error != null) - return obj; - - string hash; + Group userGroup = TShock.Utils.GetGroup(userAccount.Group); + if (!userGroup.HasPermission(RestPermissions.restapi) && userAccount.Group != "superadmin") + return new RestObject("403") + { Error = "Although your account was successfully found and identified, your account lacks the permission required to use the API. (restapi)" }; + + string tokenHash; var rand = new Random(); var randbytes = new byte[32]; do { rand.NextBytes(randbytes); - hash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); - } while (Tokens.ContainsKey(hash)); + tokenHash = randbytes.Aggregate("", (s, b) => s + b.ToString("X2")); + } while (Tokens.ContainsKey(tokenHash)); - Tokens.Add(hash, user); + Tokens.Add(tokenHash, new TokenData { Username = userAccount.Name, UserGroupName = userGroup.Name }); - obj["token"] = hash; - obj["deprecated"] = "This method will be removed from TShock in 3.6."; - return obj; + RestObject response = new RestObject() { Response = "Successful login" }; + response["token"] = tokenHash; + return response; } - protected override object ExecuteCommand(RestCommand cmd, RestVerbs verbs, IParameterCollection parms) { - if (cmd.RequiresToken) - { - var strtoken = parms["token"]; - if (strtoken == null) - return new Dictionary - {{"status", "401"}, {"error", "Not authorized. The specified API endpoint requires a token."}}; + if (!cmd.RequiresToken) + return base.ExecuteCommand(cmd, verbs, parms); + + var token = parms["token"]; + if (token == null) + return new RestObject("401") + { Error = "Not authorized. The specified API endpoint requires a token." }; - object token; - if (!Tokens.TryGetValue(strtoken, out token)) - return new Dictionary - { - {"status", "403"}, - { - "error", - "Not authorized. The specified API endpoint requires a token, but the provided token was not valid." - } - }; + SecureRestCommand secureCmd = (SecureRestCommand)cmd; + TokenData tokenData; + if (!Tokens.TryGetValue(token, out tokenData) && !AppTokens.TryGetValue(token, out tokenData)) + return new RestObject("403") + { Error = "Not authorized. The specified API endpoint requires a token, but the provided token was not valid." }; + + // TODO: Get rid of this when the old REST permission model is removed. + if (TShock.Config.RestUseNewPermissionModel) { + Group userGroup = TShock.Groups.GetGroupByName(tokenData.UserGroupName); + if (userGroup == null) + { + Tokens.Remove(token); + + return new RestObject("403") + { Error = "Not authorized. The provided token became invalid due to group changes, please create a new token." }; + } + + if (secureCmd.Permissions.Length > 0 && secureCmd.Permissions.All(perm => !userGroup.HasPermission(perm))) + { + return new RestObject("403") + { Error = string.Format("Not authorized. User \"{0}\" has no access to use the specified API endpoint.", tokenData.Username) }; + } } - return base.ExecuteCommand(cmd, verbs, parms); + + object result = secureCmd.Execute(verbs, parms, tokenData); + if (cmd.DoLog) + TShock.Utils.SendLogs(string.Format( + "\"{0}\" requested REST endpoint: {1}", tokenData.Username, this.BuildRequestUri(cmd, verbs, parms, false)), + Color.PaleVioletRed); + + return result; } } } \ No newline at end of file diff --git a/TShockAPI/SaveManager.cs b/TShockAPI/SaveManager.cs index f411a2ef..7e0b0b57 100644 --- a/TShockAPI/SaveManager.cs +++ b/TShockAPI/SaveManager.cs @@ -1,8 +1,24 @@ -using System; +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2013 Nyx Studios (fka. 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.ComponentModel; using System.Threading; -using System.Diagnostics; using Terraria; namespace TShockAPI diff --git a/TShockAPI/StatTracker.cs b/TShockAPI/StatTracker.cs deleted file mode 100644 index 99364eae..00000000 --- a/TShockAPI/StatTracker.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* -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.IO; -using System.Net; -using System.Threading; -using Terraria; - -namespace TShockAPI -{ - public class StatTracker - { - private Utils Utils = TShock.Utils; - public DateTime lastcheck = DateTime.MinValue; - private readonly int checkinFrequency = 5; - - public void CheckIn() - { - if ((DateTime.Now - lastcheck).TotalMinutes >= checkinFrequency) - { - ThreadPool.QueueUserWorkItem(CallHome); - lastcheck = DateTime.Now; - } - } - - private void CallHome(object state) - { - string fp; - string lolpath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/.tshock/"; - if (!Directory.Exists(lolpath)) - { - Directory.CreateDirectory(lolpath); - } - if (!File.Exists(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint"))) - { - fp = ""; - int random = Utils.Random.Next(500000, 1000000); - fp += random; - - fp = Utils.HashPassword(Netplay.serverIP + fp + Netplay.serverPort + Netplay.serverListenIP); - TextWriter tw = new StreamWriter(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint")); - tw.Write(fp); - tw.Close(); - } - else - { - fp = ""; - TextReader tr = new StreamReader(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint")); - fp = tr.ReadToEnd(); - tr.Close(); - } - - using (var client = new WebClient()) - { - client.Headers.Add("user-agent", - "TShock (" + TShock.VersionNum + ")"); - try - { - string response; - if (TShock.Config.DisablePlayerCountReporting) - { - response = - client.DownloadString("http://tshock.co/tickto.php?do=log&fp=" + fp + "&ver=" + TShock.VersionNum + "&os=" + - Environment.OSVersion + "&mono=" + Main.runningMono + "&port=" + Netplay.serverPort + - "&plcount=0"); - } - else - { - response = - client.DownloadString("http://tshock.co/tickto.php?do=log&fp=" + fp + "&ver=" + TShock.VersionNum + "&os=" + - Environment.OSVersion + "&mono=" + Main.runningMono + "&port=" + Netplay.serverPort + - "&plcount=" + TShock.Utils.ActivePlayers()); - } - if (!TShock.Config.HideStatTrackerDebugMessages) - Log.ConsoleInfo("Stat Tracker: " + response); - } - catch (Exception e) - { - Log.Error(e.ToString()); - } - } - } - } -} \ No newline at end of file diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 96a4b9c9..0e1449d4 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.Diagnostics; @@ -71,10 +72,26 @@ namespace TShockAPI public int FirstMaxMP { get; set; } - /// - /// The player's group. - /// - public Group Group { get; set; } + /// + /// The player's group. + /// + public Group Group + { + get + { + if (tempGroup != null) + return tempGroup; + return group; + } + set { group = value; } + } + + /// + /// The player's temporary group. This overrides the user's actual group. + /// + public Group tempGroup = null; + + private Group group = null; public bool ReceivedInfo { get; set; } @@ -105,6 +122,8 @@ namespace TShockAPI public bool AwaitingName { get; set; } + public string[] AwaitingNameParameters { get; set; } + /// /// The last time a player broke a grief check. /// @@ -144,15 +163,21 @@ namespace TShockAPI public string UserAccountName { get; set; } /// - /// Unused can be removed. + /// Whether the player performed a valid login attempt (i.e. entered valid user name and password) but is still blocked + /// from logging in because of SSI. /// - public bool HasBeenSpammedWithBuildMessage; + public bool LoginFailsBySsi { get; set; } /// /// Whether the player is logged in or not. /// public bool IsLoggedIn; + /// + /// Whether the player has sent their whole inventory to the server while connecting. + /// + public bool HasSentInventory { get; set; } + /// /// The player's user id( from the db ). /// @@ -215,6 +240,8 @@ namespace TShockAPI public bool SilentKickInProgress; + public bool SilentJoinInProgress; + /// /// A list of points where ice tiles have been placed. /// @@ -381,7 +408,7 @@ namespace TShockAPI TilesDestroyed = new Dictionary(); TilesCreated = new Dictionary(); Index = index; - Group = new Group(TShock.Config.DefaultGuestGroupName); + Group = Group.DefaultGroup; IceTiles = new List(); AwaitingResponse = new Dictionary>(); } @@ -392,7 +419,7 @@ namespace TShockAPI TilesCreated = new Dictionary(); Index = -1; FakePlayer = new Player {name = playerName, whoAmi = -1}; - Group = new Group(TShock.Config.DefaultGuestGroupName); + Group = Group.DefaultGroup; AwaitingResponse = new Dictionary>(); } @@ -557,7 +584,8 @@ namespace TShockAPI public bool GiveItemCheck(int type, string name, int width, int height, int stack, int prefix = 0) { - if (TShock.Itembans.ItemIsBanned(name) && TShock.Config.PreventBannedItemSpawn) + if ((TShock.Itembans.ItemIsBanned(name) && TShock.Config.PreventBannedItemSpawn) && + (TShock.Itembans.ItemIsBanned(name, this) || !TShock.Config.AllowAllowedGroupsToSpawnBannedItems)) return false; GiveItem(type,name,width,height,stack,prefix); @@ -585,21 +613,41 @@ namespace TShockAPI SendMessage(msg, Color.Yellow); } + public void SendInfoMessage(string format, params object[] args) + { + SendInfoMessage(string.Format(format, args)); + } + public virtual void SendSuccessMessage(string msg) { SendMessage(msg, Color.Green); } + public void SendSuccessMessage(string format, params object[] args) + { + SendSuccessMessage(string.Format(format, args)); + } + public virtual void SendWarningMessage(string msg) { SendMessage(msg, Color.OrangeRed); } + public void SendWarningMessage(string format, params object[] args) + { + SendWarningMessage(string.Format(format, args)); + } + public virtual void SendErrorMessage(string msg) { SendMessage(msg, Color.Red); } + public void SendErrorMessage(string format, params object[] args) + { + SendErrorMessage(string.Format(format, args)); + } + [Obsolete("Use SendErrorMessage, SendInfoMessage, or SendWarningMessage, or a custom color instead.")] public virtual void SendMessage(string msg) { @@ -712,14 +760,14 @@ namespace TShockAPI } } - public class TSRestPlayer : TSServerPlayer + public class TSRestPlayer : TSPlayer { - internal List CommandReturn = new List(); + internal List CommandOutput = new List(); - public TSRestPlayer() + public TSRestPlayer(string playerName, Group playerGroup): base(playerName) { - Group = new SuperAdminGroup(); - AwaitingResponse = new Dictionary>(); + Group = playerGroup; + AwaitingResponse = new Dictionary>(); } public override void SendMessage(string msg) @@ -734,21 +782,43 @@ namespace TShockAPI public override void SendMessage(string msg, byte red, byte green, byte blue) { - CommandReturn.Add(msg); + this.CommandOutput.Add(msg); + } + + public override void SendInfoMessage(string msg) + { + SendMessage(msg, Color.Yellow); + } + + public override void SendSuccessMessage(string msg) + { + SendMessage(msg, Color.Green); + } + + public override void SendWarningMessage(string msg) + { + SendMessage(msg, Color.OrangeRed); + } + + public override void SendErrorMessage(string msg) + { + SendMessage(msg, Color.Red); } public List GetCommandOutput() { - return CommandReturn; + return this.CommandOutput; } } public class TSServerPlayer : TSPlayer { + public static string AccountName = "ServerConsole"; public TSServerPlayer() : base("Server") { Group = new SuperAdminGroup(); + UserAccountName = AccountName; } public override void SendErrorMessage(string msg) @@ -832,6 +902,10 @@ namespace TShockAPI public void StrikeNPC(int npcid, int damage, float knockBack, int hitDirection) { + // Main.rand is thread static. + if (Main.rand == null) + Main.rand = new Random(); + Main.npc[npcid].StrikeNPC(damage, knockBack, hitDirection); NetMessage.SendData((int) PacketTypes.NpcStrike, -1, -1, "", npcid, damage, knockBack, hitDirection); } diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index d1ccc3c4..b7ae6362 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1,6 +1,6 @@ /* TShock, a server mod for Terraria -Copyright (C) 2011-2012 The TShock Team +Copyright (C) 2011-2013 Nyx Studios (fka. 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 @@ -15,6 +15,7 @@ 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.ComponentModel; @@ -23,13 +24,14 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Reflection; -using System.Threading; using Hooks; using MaxMind; using Mono.Data.Sqlite; using MySql.Data.MySqlClient; +using Newtonsoft.Json; using Rests; using Terraria; using TShockAPI.DB; @@ -37,16 +39,18 @@ using TShockAPI.Net; namespace TShockAPI { - [APIVersion(1, 12)] + [APIVersion(1, 13)] public class TShock : TerrariaPlugin { - private const string LogFormatDefault = "yyyy-MM-dd_HH-mm-ss"; - private static string LogFormat = LogFormatDefault; - private static bool LogClear = false; public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; - public static readonly string VersionCodename = "Welcome to the future."; + public static readonly string VersionCodename = "And the great beast rose from its slumber, ready to take on the world again."; public static string SavePath = "tshock"; + private const string LogFormatDefault = "yyyy-MM-dd_HH-mm-ss"; + private static string LogFormat = LogFormatDefault; + private const string LogPathDefault = "tshock"; + private static string LogPath = LogPathDefault; + private static bool LogClear = false; public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers]; public static BanManager Bans; @@ -66,11 +70,10 @@ namespace TShockAPI public static SecureRest RestApi; public static RestManager RestManager; public static Utils Utils = Utils.Instance; - public static StatTracker StatTracker = new StatTracker(); /// /// Used for implementing REST Tokens prior to the REST system starting up. /// - public static Dictionary RESTStartupTokens = new Dictionary(); + public static Dictionary RESTStartupTokens = new Dictionary(); /// /// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. @@ -97,6 +100,10 @@ namespace TShockAPI get { return "The administration modification of the future."; } } + public override string UpdateURL + { + get { return ""; } + } public TShock(Main game) : base(game) { @@ -108,35 +115,57 @@ namespace TShockAPI [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] public override void Initialize() { - HandleCommandLine(Environment.GetCommandLineArgs()); - - if (!Directory.Exists(SavePath)) - Directory.CreateDirectory(SavePath); - - DateTime now = DateTime.Now; - string logFilename; try { - logFilename = Path.Combine(SavePath, now.ToString(LogFormat)+".log"); - } - catch(Exception) - { - // Problem with the log format use the default - logFilename = Path.Combine(SavePath, now.ToString(LogFormatDefault) + ".log"); - } + HandleCommandLine(Environment.GetCommandLineArgs()); + + if (Version.Major >= 4) + getTShockAscii(); + + if (!Directory.Exists(SavePath)) + Directory.CreateDirectory(SavePath); + + ConfigFile.ConfigRead += OnConfigRead; + FileTools.SetupConfig(); + + DateTime now = DateTime.Now; + string logFilename; + string logPathSetupWarning = null; + // Log path was not already set by the command line parameter? + if (LogPath == LogPathDefault) + LogPath = Config.LogPath; + try + { + logFilename = Path.Combine(LogPath, now.ToString(LogFormat)+".log"); + if (!Directory.Exists(LogPath)) + Directory.CreateDirectory(LogPath); + } + catch(Exception ex) + { + logPathSetupWarning = "Could not apply the given log path / log format, defaults will be used. Exception details:\n" + ex; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(logPathSetupWarning); + Console.ForegroundColor = ConsoleColor.Gray; + // Problem with the log path or format use the default + logFilename = Path.Combine(LogPathDefault, now.ToString(LogFormatDefault) + ".log"); + } #if DEBUG - Log.Initialize(logFilename, LogLevel.All, false); + Log.Initialize(logFilename, LogLevel.All, false); #else - Log.Initialize(logFilename, LogLevel.All & ~LogLevel.Debug, LogClear); + Log.Initialize(logFilename, LogLevel.All & ~LogLevel.Debug, LogClear); #endif - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - if (Version.Major >= 4) - { - getTShockAscii(); - } + if (logPathSetupWarning != null) + Log.Warn(logPathSetupWarning); + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + } + catch(Exception ex) + { + // Will be handled by the server api and written to its crashlog.txt. + throw new Exception("Fatal TShock initialization exception. See inner exception for details.", ex); + } + // Further exceptions are written to TShock's log from now on. try { if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) @@ -147,9 +176,6 @@ namespace TShockAPI } File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); - ConfigFile.ConfigRead += OnConfigRead; - FileTools.SetupConfig(); - HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); if (Config.StorageType.ToLower() == "sqlite") @@ -195,7 +221,6 @@ namespace TShockAPI RememberedPos = new RememberedPosManager(DB); InventoryDB = new InventoryManager(DB); RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort); - RestApi.Verify += RestApi_Verify; RestApi.Port = Config.RestApiPort; RestManager = new RestManager(RestApi); RestManager.RegisterRestfulCommands(); @@ -225,6 +250,7 @@ namespace TShockAPI WorldHooks.SaveWorld += SaveManager.Instance.OnSaveWorld; WorldHooks.ChristmasCheck += OnXmasCheck; NetHooks.NameCollision += NetHooks_NameCollision; + TShockAPI.Hooks.PlayerHooks.PlayerPostLogin += OnPlayerLogin; GetDataHandlers.InitGetDataHandler(); Commands.InitCommands(); @@ -269,33 +295,6 @@ namespace TShockAPI // ReSharper restore LocalizableElement } - private RestObject RestApi_Verify(string username, string password) - { - var userAccount = Users.GetUserByName(username); - if (userAccount == null) - { - return new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - } - - if (Utils.HashPassword(password).ToUpper() != userAccount.Password.ToUpper()) - { - return new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - } - - if (!Utils.GetGroup(userAccount.Group).HasPermission(Permissions.restapi) && userAccount.Group != "superadmin") - { - return new RestObject("403") - { - Error = - "Although your account was successfully found and identified, your account lacks the permission required to use the API. (api)" - }; - } - - return new RestObject("200") {Response = "Successful login"}; //Maybe return some user info too? - } - protected override void Dispose(bool disposing) { if (disposing) @@ -326,6 +325,7 @@ namespace TShockAPI WorldHooks.SaveWorld -= SaveManager.Instance.OnSaveWorld; WorldHooks.ChristmasCheck -= OnXmasCheck; NetHooks.NameCollision -= NetHooks_NameCollision; + TShockAPI.Hooks.PlayerHooks.PlayerPostLogin -= OnPlayerLogin; if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) { @@ -338,7 +338,31 @@ namespace TShockAPI base.Dispose(disposing); } - void NetHooks_NameCollision(int who, string name, HandledEventArgs e) + private void OnPlayerLogin(Hooks.PlayerPostLoginEventArgs args) + { + User u = Users.GetUserByName(args.Player.UserAccountName); + List KnownIps = new List(); + if (!string.IsNullOrWhiteSpace(u.KnownIps)) + { + KnownIps = JsonConvert.DeserializeObject>(u.KnownIps); + } + + bool found = KnownIps.Any(s => s.Equals(args.Player.IP)); + if (!found) + { + if (KnownIps.Count == 100) + { + KnownIps.RemoveAt(0); + } + + KnownIps.Add(args.Player.IP); + } + + u.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented); + Users.UpdateLogin(u); + } + + private void NetHooks_NameCollision(int who, string name, HandledEventArgs e) { string ip = TShock.Utils.GetRealIP(Netplay.serverSock[who].tcpClient.Client.RemoteEndPoint.ToString()); foreach (TSPlayer ply in TShock.Players) @@ -369,7 +393,7 @@ namespace TShockAPI return; } - void OnXmasCheck(ChristmasCheckEventArgs args) + private void OnXmasCheck(ChristmasCheckEventArgs args) { if (args.Handled) return; @@ -442,9 +466,13 @@ namespace TShockAPI } break; - case "-dump": - ConfigFile.DumpDescriptions(); - Permissions.DumpDescriptions(); + case "-logpath": + path = parms[++i]; + if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) + { + LogPath = path; + Log.ConsoleInfo("Log path has been set to " + path); + } break; case "-logformat": @@ -454,6 +482,11 @@ namespace TShockAPI case "-logclear": bool.TryParse(parms[++i], out LogClear); break; + + case "-dump": + ConfigFile.DumpDescriptions(); + Permissions.DumpDescriptions(); + break; } } } @@ -473,7 +506,7 @@ namespace TShockAPI break; case "-rest-token": string token = Convert.ToString(parms[++i]); - RESTStartupTokens.Add(token, "null"); + RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" }); Console.WriteLine("Startup parameter overrode REST token."); break; case "-rest-enabled": @@ -537,7 +570,7 @@ namespace TShockAPI Regions.ReloadAllRegions(); - StatTracker.CheckIn(); + Lighting.lightMode = 2; FixChestStacks(); @@ -545,6 +578,9 @@ namespace TShockAPI private void FixChestStacks() { + if (Config.IgnoreChestStacksOnLoad) + return; + foreach (Chest chest in Main.chest) { if (chest != null) @@ -564,7 +600,6 @@ namespace TShockAPI private void OnUpdate() { UpdateManager.UpdateProcedureCheck(); - StatTracker.CheckIn(); if (Backups.IsBackupTime) Backups.Backup(); //call these every second, not every update @@ -735,16 +770,8 @@ namespace TShockAPI private void OnConnect(int ply, HandledEventArgs handler) { var player = new TSPlayer(ply); - if (Config.EnableDNSHostResolution) - { - player.Group = Users.GetGroupForIPExpensive(player.IP); - } - else - { - player.Group = Users.GetGroupForIP(player.IP); - } - if (Utils.ActivePlayers() + 1 > Config.MaxSlots + 20) + if (Utils.ActivePlayers() + 1 > Config.MaxSlots + Config.ReservedSlots) { Utils.ForceKick(player, Config.ServerFullNoReservedReason, true, false); handler.Handled = true; @@ -758,9 +785,14 @@ namespace TShockAPI if (ban != null) { - Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason), true, false); - handler.Handled = true; - return; + if (!Utils.HasBanExpired(ban)) + { + DateTime exp; + string duration = DateTime.TryParse(ban.Expiration, out exp) ? String.Format("until {0}", exp.ToString("G")) : "forever"; + Utils.ForceKick(player, string.Format("You are banned {0}: {1}", duration, ban.Reason), true, false); + handler.Handled = true; + return; + } } if (!FileTools.OnWhitelist(player.IP)) @@ -811,9 +843,13 @@ namespace TShockAPI if (ban != null) { - Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason), true, false); - handler.Handled = true; - return; + if (!Utils.HasBanExpired(ban)) + { + DateTime exp; + string duration = DateTime.TryParse(ban.Expiration, out exp) ? String.Format("until {0}", exp.ToString("G")) : "forever"; + Utils.ForceKick(player, string.Format("You are banned {0}: {1}", duration, ban.Reason), true, false); + handler.Handled = true; + } } } @@ -825,12 +861,9 @@ namespace TShockAPI if (tsplr != null && tsplr.ReceivedInfo) { - if (!tsplr.SilentKickInProgress || tsplr.State > 1) + if (!tsplr.SilentKickInProgress && tsplr.State >= 3) { - if (tsplr.State >= 2) - { - Utils.Broadcast(tsplr.Name + " left", Color.Yellow); - } + Utils.Broadcast(tsplr.Name + " left", Color.Yellow); } Log.Info(string.Format("{0} disconnected.", tsplr.Name)); @@ -1451,12 +1484,12 @@ namespace TShockAPI return (float) Math.Sqrt(num3); } - public static bool HackedHealth(TSPlayer player) + public static bool HackedStats(TSPlayer player) { - return (player.TPlayer.statManaMax > 400) || - (player.TPlayer.statMana > 400) || - (player.TPlayer.statLifeMax > 400) || - (player.TPlayer.statLife > 400); + return (player.TPlayer.statManaMax > TShock.Config.MaxMana) || + (player.TPlayer.statMana > TShock.Config.MaxMana) || + (player.TPlayer.statLifeMax > TShock.Config.MaxHealth) || + (player.TPlayer.statLife > TShock.Config.MaxHealth); } public static bool HackedInventory(TSPlayer player) diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 42902da7..bba42947 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -61,32 +61,27 @@ ..\SqlBins\MySql.Data.dll True - - False - ..\SqlBins\MySql.Web.dll - True - .\Newtonsoft.Json.dll - - - - - - + False .exe ..\TerrariaServerBins\TerrariaServer.exe - False - + + + + + + + @@ -131,7 +126,6 @@ - @@ -185,11 +179,12 @@ - "$(ProjectDir)postbuild.bat" + + - +