Merge pull request #424 from TShock/general-devel

Merge 3.8.x.x
This commit is contained in:
Lucas Nicodemus 2012-03-05 00:01:57 -08:00
commit ed6b95c6a6
46 changed files with 14868 additions and 11997 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Terraria.vsmdi = Terraria.vsmdi Terraria.vsmdi = Terraria.vsmdi
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TShockRestTestPlugin", "TShockRestTestPlugin\TShockRestTestPlugin.csproj", "{F2FEDAFB-58DE-4611-9168-A86112C346C7}"
EndProject
Global Global
GlobalSection(TestCaseManagementSettings) = postSolution GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = Terraria.vsmdi CategoryFile = Terraria.vsmdi
@ -52,6 +54,16 @@ Global
{F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|x86.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|x86.ActiveCfg = Release|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|x86.ActiveCfg = Debug|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Any CPU.Build.0 = Release|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -63,11 +63,7 @@ namespace TShockAPI
TShock.Utils.Broadcast("Server map saving, potential lag spike"); TShock.Utils.Broadcast("Server map saving, potential lag spike");
Console.WriteLine("Backing up world..."); Console.WriteLine("Backing up world...");
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld); SaveManager.Instance.SaveWorld();
SaveWorld.Start();
while (SaveWorld.ThreadState == ThreadState.Running)
Thread.Sleep(50);
Console.WriteLine("World backed up"); Console.WriteLine("World backed up");
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
Log.Info(string.Format("World backed up ({0})", Main.worldPathName)); Log.Info(string.Format("World backed up ({0})", Main.worldPathName));

193
TShockAPI/Commands.cs Normal file → Executable file
View file

@ -1,4 +1,4 @@
/* /*
TShock, a server mod for Terraria TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team Copyright (C) 2011 The TShock Team
@ -407,8 +407,18 @@ namespace TShockAPI
TShock.InventoryDB.InsertPlayerData(args.Player); TShock.InventoryDB.InsertPlayerData(args.Player);
} }
args.Player.SendMessage("Authenticated as " + user.Name + " successfully.", Color.LimeGreen); args.Player.SendMessage("Authenticated as " + user.Name + " successfully.", Color.LimeGreen);
Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + user.Name); Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + user.Name);
} if ((args.Player.LoginHarassed) && (TShock.Config.RememberLeavePos)){
if (TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP) != Vector2.Zero)
{
Vector2 pos = TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP);
args.Player.Teleport((int) pos.X, (int) pos.Y + 3);
}
args.Player.LoginHarassed = false;
}}
else else
{ {
args.Player.SendMessage("Incorrect password", Color.LimeGreen); args.Player.SendMessage("Incorrect password", Color.LimeGreen);
@ -574,7 +584,10 @@ namespace TShockAPI
else if (subcmd == "del" && args.Parameters.Count == 2) else if (subcmd == "del" && args.Parameters.Count == 2)
{ {
var user = new User(); var user = new User();
if (args.Parameters[1].Contains(".")) if (args.Parameters[1].Split('.').Count() ==4)
// changed to support dot character in usernames
// if (args.Parameters[1].Contains("."))
user.Address = args.Parameters[1]; user.Address = args.Parameters[1];
else else
user.Name = args.Parameters[1]; user.Name = args.Parameters[1];
@ -620,7 +633,11 @@ namespace TShockAPI
else if (subcmd == "group") else if (subcmd == "group")
{ {
var user = new User(); var user = new User();
if (args.Parameters[1].Contains(".")) if (args.Parameters[1].Split('.').Count()==4)
//changed to support dot character in usernames
//if (args.Parameters[1].Contains("."))
user.Address = args.Parameters[1]; user.Address = args.Parameters[1];
else else
user.Name = args.Parameters[1]; user.Name = args.Parameters[1];
@ -769,7 +786,7 @@ namespace TShockAPI
string reason = args.Parameters.Count > 1 string reason = args.Parameters.Count > 1
? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)) ? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1))
: "Misbehaviour."; : "Misbehaviour.";
if (!TShock.Utils.Kick(players[0], reason)) if (!TShock.Utils.Kick(players[0], reason, !args.Player.RealPlayer, false, args.Player.Name))
{ {
args.Player.SendMessage("You can't kick another admin!", Color.Red); args.Player.SendMessage("You can't kick another admin!", Color.Red);
} }
@ -816,7 +833,7 @@ namespace TShockAPI
string reason = args.Parameters.Count > 1 string reason = args.Parameters.Count > 1
? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)) ? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1))
: "Misbehaviour."; : "Misbehaviour.";
if (!TShock.Utils.Ban(players[0], reason)) if (!TShock.Utils.Ban(players[0], reason, !args.Player.RealPlayer, args.Player.Name))
{ {
args.Player.SendMessage("You can't ban another admin!", Color.Red); args.Player.SendMessage("You can't ban another admin!", Color.Red);
} }
@ -860,25 +877,14 @@ namespace TShockAPI
var ban = TShock.Bans.GetBanByName(plStr); var ban = TShock.Bans.GetBanByName(plStr);
if (ban != null) if (ban != null)
{ {
if (TShock.Bans.RemoveBan(ban.IP)) if (TShock.Bans.RemoveBan(ban.IP, true))
args.Player.SendMessage(string.Format("Unbanned {0} ({1})!", ban.Name, ban.IP), Color.Red);
else
args.Player.SendMessage(string.Format("Failed to unban {0} ({1})!", ban.Name, ban.IP), Color.Red);
}
else if (!TShock.Config.EnableBanOnUsernames)
{
ban = TShock.Bans.GetBanByIp(plStr);
if (ban == null)
args.Player.SendMessage(string.Format("Failed to unban {0}, not found.", args.Parameters[0]), Color.Red);
else if (TShock.Bans.RemoveBan(ban.IP))
args.Player.SendMessage(string.Format("Unbanned {0} ({1})!", ban.Name, ban.IP), Color.Red); args.Player.SendMessage(string.Format("Unbanned {0} ({1})!", ban.Name, ban.IP), Color.Red);
else else
args.Player.SendMessage(string.Format("Failed to unban {0} ({1})!", ban.Name, ban.IP), Color.Red); args.Player.SendMessage(string.Format("Failed to unban {0} ({1})!", ban.Name, ban.IP), Color.Red);
} }
else else
{ {
args.Player.SendMessage("Invalid player!", Color.Red); args.Player.SendMessage(string.Format("No bans for player {0} exist", plStr), Color.Red);
} }
} }
@ -937,8 +943,8 @@ namespace TShockAPI
return; return;
} }
string plStr = args.Parameters[0]; var ip = args.Parameters[0];
var ban = TShock.Bans.GetBanByIp(plStr); var ban = TShock.Bans.GetBanByIp(ip);
if (ban != null) if (ban != null)
{ {
if (TShock.Bans.RemoveBan(ban.IP)) if (TShock.Bans.RemoveBan(ban.IP))
@ -948,7 +954,7 @@ namespace TShockAPI
} }
else else
{ {
args.Player.SendMessage("Invalid player!", Color.Red); args.Player.SendMessage(string.Format("No bans for ip {0} exist", ip), Color.Red);
} }
} }
@ -1001,38 +1007,37 @@ namespace TShockAPI
} }
} }
TShock.Utils.ForceKickAll("Server shutting down!"); TShock.Utils.StopServer();
WorldGen.saveWorld();
Netplay.disconnect = true;
} }
//Added restart command //Added restart command
private static void Restart(CommandArgs args) private static void Restart(CommandArgs args)
{ {
if (Main.runningMono){ if (Main.runningMono)
Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono"); {
}else{ Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono");
if (TShock.Config.ServerSideInventory) }
{ else
foreach (TSPlayer player in TShock.Players) {
{ if (TShock.Config.ServerSideInventory)
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) {
{ foreach (TSPlayer player in TShock.Players)
TShock.InventoryDB.InsertPlayerData(player); {
} if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
} {
} TShock.InventoryDB.InsertPlayerData(player);
}
}
}
TShock.Utils.ForceKickAll("Server restarting!"); TShock.Utils.StopServer();
WorldGen.saveWorld(); System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Netplay.disconnect = true; Environment.Exit(0);
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); }
Environment.Exit(0); }
}}
private static void OffNoSave(CommandArgs args) private static void OffNoSave(CommandArgs args)
{ {
TShock.Utils.ForceKickAll("Server shutting down!"); TShock.Utils.StopServer(false);
Netplay.disconnect = true;
} }
private static void CheckUpdates(CommandArgs args) private static void CheckUpdates(CommandArgs args)
@ -1085,7 +1090,7 @@ namespace TShockAPI
if (args.Parameters.Count < 1) if (args.Parameters.Count < 1)
{ {
ply.SendMessage("Picking a random ore!", Color.Green); ply.SendMessage("Picking a random ore!", Color.Green); //should this be a help message instead?
num = WorldGen.genRand.Next(6); num = WorldGen.genRand.Next(6);
} }
else if (args.Parameters[0] == "cobalt") else if (args.Parameters[0] == "cobalt")
@ -1112,7 +1117,34 @@ namespace TShockAPI
{ {
num = 5; num = 5;
} }
else if (args.Parameters[0] == "demonite")
{
num = 7;
}
else if (args.Parameters[0] == "sapphire")
{
num = 8;
}
else if (args.Parameters[0] == "ruby")
{
num = 9;
}
else if (args.Parameters[0] == "emerald")
{
num = 10;
}
else if (args.Parameters[0] == "topaz")
{
num = 11;
}
else if (args.Parameters[0] == "amethyst")
{
num = 12;
}
else if (args.Parameters[0] == "diamond")
{
num = 13;
}
else else
{ {
num = 2; num = 2;
@ -1147,7 +1179,41 @@ namespace TShockAPI
num = 9; num = 9;
num3 *= 1.1f; num3 *= 1.1f;
} }
else if (num == 7)
{
num = 22;
num3 *= 1;
}
else if (num == 8)
{
num = 63;
num3 *= .80f;
}
else if (num == 9)
{
num = 64;
num3 *=1;
}
else if (num == 10)
{
num = 65;
num3 *= 1;
}
else if (num == 11)
{
num = 66;
num3 *= 1;
}
else if (num == 12)
{
num = 67;
num3 *= 1;
}
else if (num == 13)
{
num = 68;
num3 *= 1;
}
else else
{ {
num = 111; num = 111;
@ -1165,11 +1231,11 @@ namespace TShockAPI
{ {
int i2 = WorldGen.genRand.Next(100, Main.maxTilesX - 100); int i2 = WorldGen.genRand.Next(100, Main.maxTilesX - 100);
double num6 = Main.worldSurface; double num6 = Main.worldSurface;
if ((num == 108) || (num == 6) || (num == 7) || (num == 8) || (num == 9)) if ((num == 108) || (num == 6) || (num == 7) || (num == 8) || (num == 9) ||((num > 62) && (num < 69)))
{ {
num6 = Main.rockLayer; num6 = Main.rockLayer;
} }
if (num == 111) if ((num == 111) || (num == 22) || (num == 68))
{ {
num6 = (Main.rockLayer + Main.rockLayer + (double)Main.maxTilesY) / 3.0; num6 = (Main.rockLayer + Main.rockLayer + (double)Main.maxTilesY) / 3.0;
} }
@ -1544,6 +1610,8 @@ namespace TShockAPI
Main.tile[x, y].type = 2; Main.tile[x, y].type = 2;
break; break;
case 32: case 32:
case 113:
case 110:
Main.tile[x, y].type = 0; Main.tile[x, y].type = 0;
Main.tile[x, y].active = false; Main.tile[x, y].active = false;
break; break;
@ -1552,11 +1620,14 @@ namespace TShockAPI
break; break;
case 112: case 112:
case 116: case 116:
Main.tile[x, y].type = 169; Main.tile[x, y].type = 53;
break; break;
case 113: case 118:
Main.tile[x, y].type = 38; Main.tile[x, y].type = 38;
break; break;
case 115:
Main.tile[x, y].type = 52;
break;
default: default:
continue; continue;
} }
@ -2186,15 +2257,13 @@ namespace TShockAPI
{ {
Main.spawnTileX = args.Player.TileX + 1; Main.spawnTileX = args.Player.TileX + 1;
Main.spawnTileY = args.Player.TileY + 3; Main.spawnTileY = args.Player.TileY + 3;
SaveManager.Instance.SaveWorld(false);
TShock.Utils.Broadcast("Server map saving, potential lag spike");
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
SaveWorld.Start();
} }
private static void Reload(CommandArgs args) private static void Reload(CommandArgs args)
{ {
FileTools.SetupConfig(); FileTools.SetupConfig();
TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
TShock.Groups.LoadPermisions(); TShock.Groups.LoadPermisions();
TShock.Regions.ReloadAllRegions(); TShock.Regions.ReloadAllRegions();
args.Player.SendMessage( args.Player.SendMessage(
@ -2215,9 +2284,7 @@ namespace TShockAPI
private static void Save(CommandArgs args) private static void Save(CommandArgs args)
{ {
TShock.Utils.Broadcast("Server map saving, potential lag spike"); SaveManager.Instance.SaveWorld(false);
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
SaveWorld.Start();
} }
private static void Settle(CommandArgs args) private static void Settle(CommandArgs args)

View file

@ -1,4 +1,4 @@
/* /*
TShock, a server mod for Terraria TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team Copyright (C) 2011 The TShock Team
@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Collections.Generic;
using System.Data; using System.Data;
using System.IO; using System.IO;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
@ -39,15 +40,15 @@ namespace TShockAPI.DB
db.GetSqlType() == SqlType.Sqlite db.GetSqlType() == SqlType.Sqlite
? (IQueryBuilder) new SqliteQueryCreator() ? (IQueryBuilder) new SqliteQueryCreator()
: new MysqlQueryCreator()); : new MysqlQueryCreator());
try{ try
creator.EnsureExists(table); {
} creator.EnsureExists(table);
catch (DllNotFoundException ex) }
{ catch (DllNotFoundException)
System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?"); {
throw new Exception("Could not find a database library (probably Sqlite3.dll)"); System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?");
} throw new Exception("Could not find a database library (probably Sqlite3.dll)");
}
} }
public Ban GetBanByIp(string ip) public Ban GetBanByIp(string ip)
@ -67,12 +68,30 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)");
return null; return null;
} }
public List<Ban> GetBans()
{
List<Ban> banlist = new List<Ban>();
try
{
using (var reader = database.QueryReader("SELECT * FROM Bans"))
{
while (reader.Read())
{
banlist.Add(new Ban(reader.Get<string>("IP"), reader.Get<string>("Name"), reader.Get<string>("Reason")));
}
return banlist;
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
Console.WriteLine(ex.StackTrace);
}
return null;
}
public Ban GetBanByName(string name, bool casesensitive = true) public Ban GetBanByName(string name, bool casesensitive = true)
{ {
if (!TShock.Config.EnableBanOnUsernames)
{
return null;
}
try try
{ {
var namecol = casesensitive ? "Name" : "UPPER(Name)"; var namecol = casesensitive ? "Name" : "UPPER(Name)";
@ -91,7 +110,14 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)");
return null; return null;
} }
public bool AddBan(string ip, string name = "", string reason = "") #if COMPAT_SIGS
[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);
}
#endif
public bool AddBan(string ip, string name = "", string reason = "", bool exceptions = false)
{ {
try try
{ {
@ -99,19 +125,34 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)");
} }
catch (Exception ex) catch (Exception ex)
{ {
if (exceptions)
throw ex;
Log.Error(ex.ToString()); Log.Error(ex.ToString());
} }
return false; return false;
} }
#if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
public bool RemoveBan(string ip) public bool RemoveBan(string ip)
{
return RemoveBan(ip, false, true, false);
}
#endif
public bool RemoveBan(string match, bool byName = false, bool casesensitive = true, bool exceptions = false)
{ {
try try
{ {
return database.Query("DELETE FROM Bans WHERE IP=@0", ip) != 0; if (!byName)
return database.Query("DELETE FROM Bans WHERE IP=@0", match) != 0;
var namecol = casesensitive ? "Name" : "UPPER(Name)";
return database.Query("DELETE FROM Bans WHERE " + namecol + "=@0", casesensitive ? match : match.ToUpper()) != 0;
} }
catch (Exception ex) catch (Exception ex)
{ {
if (exceptions)
throw ex;
Log.Error(ex.ToString()); Log.Error(ex.ToString());
} }
return false; return false;

View file

@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.IO; using System.IO;
@ -24,11 +25,10 @@ using MySql.Data.MySqlClient;
namespace TShockAPI.DB namespace TShockAPI.DB
{ {
public class GroupManager public class GroupManager : IEnumerable<Group>
{ {
private IDbConnection database; private IDbConnection database;
public readonly List<Group> groups = new List<Group>();
public List<Group> groups = new List<Group>();
public GroupManager(IDbConnection db) public GroupManager(IDbConnection db)
{ {
@ -48,14 +48,23 @@ namespace TShockAPI.DB
: new MysqlQueryCreator()); : new MysqlQueryCreator());
creator.EnsureExists(table); creator.EnsureExists(table);
//Add default groups // Load Permissions from the DB
AddGroup("guest", "canbuild,canregister,canlogin,canpartychat,cantalkinthird"); LoadPermisions();
AddGroup("default", "guest", "warp,canchangepassword");
AddGroup("newadmin", "default", "kick,editspawn,reservedslot"); // Add default groups if they don't exist
AddGroup("admin", "newadmin", AddDefaultGroup("guest", "", "canbuild,canregister,canlogin,canpartychat,cantalkinthird");
AddDefaultGroup("default", "guest", "warp,canchangepassword");
AddDefaultGroup("newadmin", "default", "kick,editspawn,reservedslot");
AddDefaultGroup("admin", "newadmin",
"ban,unban,whitelist,causeevents,spawnboss,spawnmob,managewarp,time,tp,pvpfun,kill,logs,immunetokick,tphere"); "ban,unban,whitelist,causeevents,spawnboss,spawnmob,managewarp,time,tp,pvpfun,kill,logs,immunetokick,tphere");
AddGroup("trustedadmin", "admin", "maintenance,cfg,butcher,item,heal,immunetoban,usebanneditem,manageusers"); AddDefaultGroup("trustedadmin", "admin", "maintenance,cfg,butcher,item,heal,immunetoban,usebanneditem,manageusers");
AddGroup("vip", "default", "reservedslot"); AddDefaultGroup("vip", "default", "reservedslot");
}
private void AddDefaultGroup(string name, string parent, string permissions)
{
if (!GroupExists(name))
AddGroup(name, parent, permissions);
} }
@ -64,30 +73,52 @@ namespace TShockAPI.DB
if (group == "superadmin") if (group == "superadmin")
return true; return true;
return groups.Any(g => g.Name.Equals(group)); return groups.Any(g => g.Name.Equals(group));
} }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<Group> GetEnumerator()
{
return groups.GetEnumerator();
}
public Group GetGroupByName(string name)
{
var ret = groups.Where(g => g.Name == name);
return 1 == ret.Count() ? ret.ElementAt(0) : null;
}
/// <summary> /// <summary>
/// Adds group with name and permissions if it does not exist. /// Adds group with name and permissions if it does not exist.
/// </summary> /// </summary>
/// <param name="name">name of group</param> /// <param name="name">name of group</param>
/// <param name="parentname">parent of group</param> /// <param name="parentname">parent of group</param>
/// <param name="permissions">permissions</param> /// <param name="permissions">permissions</param>
public String AddGroup(String name, string parentname, String permissions, String chatcolor) /// <param name="chatcolor">chatcolor</param>
/// <param name="exceptions">exceptions true indicates use exceptions for errors false otherwise</param>
public String AddGroup(String name, string parentname, String permissions, String chatcolor = Group.defaultChatColor, bool exceptions = false)
{ {
String message = "";
if (GroupExists(name)) if (GroupExists(name))
{
if (exceptions)
throw new GroupExistsException(name);
return "Error: Group already exists. Use /modGroup to change permissions."; return "Error: Group already exists. Use /modGroup to change permissions.";
}
var group = new Group(name, null, chatcolor); var group = new Group(name, null, chatcolor);
group.permissions.Add(permissions); group.Permissions = permissions;
if (!string.IsNullOrWhiteSpace(parentname)) if (!string.IsNullOrWhiteSpace(parentname))
{ {
var parent = groups.FirstOrDefault(gp => gp.Name == parentname); var parent = groups.FirstOrDefault(gp => gp.Name == parentname);
if (parent == null) if (parent == null)
{ {
var error = "Invalid parent {0} for group {1}".SFormat(group.Name, parentname); var error = "Invalid parent {0} for group {1}".SFormat(parentname, group.Name);
if (exceptions)
throw new GroupManagerException(error);
Log.ConsoleError(error); Log.ConsoleError(error);
return error; return error;
} }
@ -98,81 +129,136 @@ namespace TShockAPI.DB
? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);" ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);"
: "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3"; : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3";
if (database.Query(query, name, parentname, permissions, chatcolor) == 1) if (database.Query(query, name, parentname, permissions, chatcolor) == 1)
message = "Group " + name + " has been created successfully."; {
groups.Add(group);
return "Group " + name + " has been created successfully.";
}
else if (exceptions)
throw new GroupManagerException("Failed to add group '" + name + "'");
groups.Add(group); return "";
return message;
} }
public String AddGroup(String name, String permissions) public String AddGroup(String name, String permissions)
{ {
return AddGroup(name, "", permissions, "255,255,255"); return AddGroup(name, null, permissions, Group.defaultChatColor, false);
} }
public String AddGroup(String name, string parent, String permissions) #if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
public String AddGroup(String name, string parentname, String permissions)
{ {
return AddGroup(name, parent, permissions, "255,255,255"); return AddGroup(name, parentname, permissions, Group.defaultChatColor, false);
} }
[Obsolete("This method is for signature compatibility for external code only")]
public String AddGroup(String name, string parentname, String permissions, String chatcolor)
{
return AddGroup(name, parentname, permissions, chatcolor, false);
}
#endif
/// <summary>
/// Updates a group including permissions
/// </summary>
/// <param name="name">name of the group to update</param>
/// <param name="parentname">parent of group</param>
/// <param name="permissions">permissions</param>
/// <param name="chatcolor">chatcolor</param>
public void UpdateGroup(string name, string parentname, string permissions, string chatcolor)
{
if (!GroupExists(name))
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));
}
// NOTE: we use newgroup.XYZ to ensure any 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, newgroup.ChatColor, name) != 1)
throw new GroupManagerException("Failed to update group '" + name + "'");
groups.Remove(TShock.Utils.GetGroup(name));
groups.Add(newgroup);
}
#if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
public String DeleteGroup(String name) public String DeleteGroup(String name)
{ {
String message = ""; return DeleteGroup(name, false);
}
#endif
public String DeleteGroup(String name, bool exceptions = false)
{
if (!GroupExists(name)) if (!GroupExists(name))
{
if (exceptions)
throw new GroupNotExistException(name);
return "Error: Group doesn't exists."; return "Error: Group doesn't exists.";
}
if (database.Query("DELETE FROM GroupList WHERE GroupName=@0", name) == 1) if (database.Query("DELETE FROM GroupList WHERE GroupName=@0", name) == 1)
message = "Group " + name + " has been deleted successfully."; {
groups.Remove(TShock.Utils.GetGroup(name)); groups.Remove(TShock.Utils.GetGroup(name));
return "Group " + name + " has been deleted successfully.";
}
else if (exceptions)
throw new GroupManagerException("Failed to delete group '" + name + "'");
return message; return "";
} }
public String AddPermissions(String name, List<String> permissions) public String AddPermissions(String name, List<String> permissions)
{ {
String message = "";
if (!GroupExists(name)) if (!GroupExists(name))
return "Error: Group doesn't exists."; return "Error: Group doesn't exists.";
var group = TShock.Utils.GetGroup(name); var group = TShock.Utils.GetGroup(name);
//Add existing permissions (without duplicating) var oldperms = group.Permissions; // Store old permissions in case of error
permissions.AddRange(group.permissions.Where(s => !permissions.Contains(s))); permissions.ForEach(p => group.AddPermission(p));
if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", String.Join(",", permissions), name) != 0) if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", group.Permissions, name) == 1)
{ return "Group " + name + " has been modified successfully.";
message = "Group " + name + " has been modified successfully.";
group.SetPermission(permissions); // Restore old permissions so DB and internal object are in a consistent state
} group.Permissions = oldperms;
return message; return "";
} }
public String DeletePermissions(String name, List<String> permissions) public String DeletePermissions(String name, List<String> permissions)
{ {
String message = "";
if (!GroupExists(name)) if (!GroupExists(name))
return "Error: Group doesn't exists."; return "Error: Group doesn't exists.";
var group = TShock.Utils.GetGroup(name); var group = TShock.Utils.GetGroup(name);
var oldperms = group.Permissions; // Store old permissions in case of error
permissions.ForEach(p => group.RemovePermission(p));
//Only get permissions that exist in the group. if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", group.Permissions, name) == 1)
var newperms = group.permissions.Except(permissions); return "Group " + name + " has been modified successfully.";
if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", String.Join(",", newperms), name) != 0) // Restore old permissions so DB and internal object are in a consistent state
{ group.Permissions = oldperms;
message = "Group " + name + " has been modified successfully."; return "";
group.SetPermission(newperms.ToList());
}
return message;
} }
public void LoadPermisions() public void LoadPermisions()
{ {
//Create a temporary list so if there is an error it doesn't override the currently loaded groups with broken groups. // 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<Group>(); var tempgroups = new List<Group>();
tempgroups.Add(new SuperAdminGroup()); tempgroups.Add(new SuperAdminGroup());
if (groups == null || groups.Count < 2) if (groups == null || groups.Count < 2)
groups = tempgroups; {
groups.Clear();
groups.AddRange(tempgroups);
}
try try
{ {
@ -181,34 +267,9 @@ namespace TShockAPI.DB
{ {
while (reader.Read()) while (reader.Read())
{ {
string groupname = reader.Get<String>("GroupName"); var group = new Group(reader.Get<String>("GroupName"), null, reader.Get<String>("ChatColor"), reader.Get<String>("Commands"));
var group = new Group(groupname);
group.Prefix = reader.Get<String>("Prefix"); group.Prefix = reader.Get<String>("Prefix");
group.Suffix = reader.Get<String>("Suffix"); group.Suffix = reader.Get<String>("Suffix");
//Inherit Given commands
String[] commands = reader.Get<String>("Commands").Split(',');
foreach (var t in commands)
{
var str = t.Trim();
if (str.StartsWith("!"))
{
group.NegatePermission(str.Substring(1));
}
else
{
group.AddPermission(str);
}
}
String[] chatcolour = (reader.Get<String>("ChatColor") ?? "").Split(',');
if (chatcolour.Length == 3)
{
byte.TryParse(chatcolour[0], out group.R);
byte.TryParse(chatcolour[1], out group.G);
byte.TryParse(chatcolour[2], out group.B);
}
groupsparents.Add(Tuple.Create(group, reader.Get<string>("Parent"))); groupsparents.Add(Tuple.Create(group, reader.Get<string>("Parent")));
} }
} }
@ -222,7 +283,7 @@ namespace TShockAPI.DB
var parent = groupsparents.FirstOrDefault(gp => gp.Item1.Name == parentname); var parent = groupsparents.FirstOrDefault(gp => gp.Item1.Name == parentname);
if (parent == null) if (parent == null)
{ {
Log.ConsoleError("Invalid parent {0} for group {1}".SFormat(group.Name, parentname)); Log.ConsoleError("Invalid parent {0} for group {1}".SFormat(parentname, group.Name));
return; return;
} }
group.Parent = parent.Item1; group.Parent = parent.Item1;
@ -230,8 +291,8 @@ namespace TShockAPI.DB
tempgroups.Add(group); tempgroups.Add(group);
} }
groups.Clear();
groups = tempgroups; groups.AddRange(tempgroups);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -239,4 +300,36 @@ namespace TShockAPI.DB
} }
} }
} }
[Serializable]
public class GroupManagerException : Exception
{
public GroupManagerException(string message)
: base(message)
{
}
public GroupManagerException(string message, Exception inner)
: base(message, inner)
{
}
}
[Serializable]
public class GroupExistsException : GroupManagerException
{
public GroupExistsException(string name)
: base("Group '" + name + "' already exists")
{
}
}
[Serializable]
public class GroupNotExistException : GroupManagerException
{
public GroupNotExistException(string name)
: base("Group '" + name + "' does not exist")
{
}
}
} }

View file

@ -33,141 +33,25 @@ namespace TShockAPI.DB
string InsertValues(string table, List<SqlValue> values); string InsertValues(string table, List<SqlValue> values);
string ReadColumn(string table, List<SqlValue> wheres); string ReadColumn(string table, List<SqlValue> wheres);
string DeleteRow(string table, List<SqlValue> wheres); string DeleteRow(string table, List<SqlValue> wheres);
string RenameTable(string from, string to);
} }
public class SqliteQueryCreator : IQueryBuilder public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder
{ {
public string CreateTable(SqlTable table) public override string CreateTable(SqlTable table)
{ {
var columns = var columns =
table.Columns.Select( table.Columns.Select(
c => c =>
"'{0}' {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", "'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "",
c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "",
c.Unique ? "UNIQUE" : "")); c.Unique ? "UNIQUE" : ""));
return "CREATE TABLE '{0}' ({1})".SFormat(table.Name, string.Join(", ", columns)); return "CREATE TABLE {0} ({1})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns));
} }
private static Random rand = new Random(); public override string RenameTable(string from, string to)
/// <summary>
/// Alter a table from source to destination
/// </summary>
/// <param name="from">Must have name and column names. Column types are not required</param>
/// <param name="to">Must have column names and column types.</param>
/// <returns></returns>
public string AlterTable(SqlTable from, SqlTable to)
{ {
var rstr = rand.NextString(20); return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
var alter = "ALTER TABLE '{0}' RENAME TO '{1}_{0}'".SFormat(from.Name, rstr);
var create = CreateTable(to);
//combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable.
//exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data?
var insert = "INSERT INTO '{0}' ({1}) SELECT {1} FROM {2}_{0}".SFormat(from.Name,
string.Join(", ",
from.Columns.Where(
c =>
to.Columns.Any(
c2 => c2.Name == c.Name)).Select
(c => c.Name)), rstr);
var drop = "DROP TABLE '{0}_{1}'".SFormat(rstr, from.Name);
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
/*
ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans"
CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT)
INSERT INTO "main"."Bans" SELECT "IP","Name" FROM "main"."oXHFcGcd04oXHFcGcd04_Bans"
DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans"
*
* Twitchy - Oh. I get it!
*/
}
public string DeleteRow(string table, List<SqlValue> wheres)
{
var sbwheres = new StringBuilder();
int count = 0;
foreach (SqlValue where in wheres)
{
sbwheres.Append(where.Name + "=" + where.Value);
if (count != wheres.Count - 1)
sbwheres.Append(" AND ");
count++;
}
if (wheres.Count > 0)
return "DELETE FROM '{0}' WHERE {1} ".SFormat(table, sbwheres.ToString());
else
return "DELETE FROM '{0}'".SFormat(table, sbwheres.ToString());
}
public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres)
{
var sbvalues = new StringBuilder();
var sbwheres = new StringBuilder();
int count = 0;
foreach (SqlValue value in values)
{
sbvalues.Append(value.Name + "=" + value.Value);
if (count != values.Count - 1)
sbvalues.Append(",");
count++;
}
count = 0;
foreach (SqlValue where in wheres)
{
sbwheres.Append(where.Name + "=" + where.Value);
if (count != wheres.Count - 1)
sbwheres.Append(" AND ");
count++;
}
if (wheres.Count > 0)
return "UPDATE '{0}' SET {1} WHERE {2}".SFormat(table, sbvalues.ToString(), sbwheres.ToString());
else
return "UPDATE '{0}' SET {1}".SFormat(table, sbvalues.ToString());
}
public string InsertValues(string table, List<SqlValue> values)
{
var sbnames = new StringBuilder();
var sbvalues = new StringBuilder();
int count = 0;
foreach (SqlValue name in values)
{
sbnames.Append(name.Name);
if (count != values.Count - 1)
sbnames.Append(", ");
count++;
}
count = 0;
foreach (SqlValue value in values)
{
sbvalues.Append(value.Value.ToString());
if (count != values.Count - 1)
sbvalues.Append(", ");
count++;
}
return "INSERT INTO '{0}' ({1}) VALUES ({2})".SFormat(table, sbnames.ToString(), sbvalues.ToString());
}
public string ReadColumn(string table, List<SqlValue> wheres)
{
var sbwheres = new StringBuilder();
int count = 0;
foreach (SqlValue where in wheres)
{
sbwheres.Append(where.Name + "=" + where.Value);
if (count != wheres.Count - 1)
sbwheres.Append(" AND ");
count++;
}
if (wheres.Count > 0)
return "SELECT * FROM {0} WHERE {1}".SFormat(table, sbwheres.ToString());
else
return "SELECT * FROM {0}".SFormat(table);
} }
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string> private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string>
@ -189,137 +73,32 @@ namespace TShockAPI.DB
return ret; return ret;
throw new NotImplementedException(Enum.GetName(typeof (MySqlDbType), type)); throw new NotImplementedException(Enum.GetName(typeof (MySqlDbType), type));
} }
protected override string EscapeTableName(string table)
{
return table.SFormat("'{0}'", table);
}
} }
public class MysqlQueryCreator : IQueryBuilder public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder
{ {
public string CreateTable(SqlTable table) public override string CreateTable(SqlTable table)
{ {
var columns = var columns =
table.Columns.Select( table.Columns.Select(
c => c =>
"{0} {1} {2} {3}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", "{0} {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "",
c.AutoIncrement ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : "")); c.AutoIncrement ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : ""));
var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name);
return "CREATE TABLE {0} ({1} {2})".SFormat(table.Name, string.Join(", ", columns), return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns),
uniques.Count() > 0 uniques.Count() > 0
? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques))
: ""); : "");
} }
private static Random rand = new Random(); public override string RenameTable(string from, string to)
/// <summary>
/// Alter a table from source to destination
/// </summary>
/// <param name="from">Must have name and column names. Column types are not required</param>
/// <param name="to">Must have column names and column types.</param>
/// <returns></returns>
public string AlterTable(SqlTable from, SqlTable to)
{ {
var rstr = rand.NextString(20); return "RENAME TABLE {0} TO {1}".SFormat(from, to);
var alter = "RENAME TABLE {0} TO {1}_{0}".SFormat(from.Name, rstr);
var create = CreateTable(to);
//combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable.
//exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data?
var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}_{0}".SFormat(from.Name,
string.Join(", ",
from.Columns.Where(
c =>
to.Columns.Any(
c2 => c2.Name == c.Name)).Select(
c => c.Name)), rstr);
var drop = "DROP TABLE {0}_{1}".SFormat(rstr, from.Name);
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
}
public string DeleteRow(string table, List<SqlValue> wheres)
{
var sbwheres = new StringBuilder();
int count = 0;
foreach (SqlValue where in wheres)
{
sbwheres.Append(where.Name + "=" + where.Value);
if (count != wheres.Count - 1)
sbwheres.Append(" AND ");
count++;
}
if (wheres.Count > 0)
return "DELETE FROM {0} WHERE {1} ".SFormat(table, sbwheres.ToString());
else
return "DELETE FROM {0}".SFormat(table, sbwheres.ToString());
}
public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres)
{
var sbvalues = new StringBuilder();
var sbwheres = new StringBuilder();
int count = 0;
foreach (SqlValue value in values)
{
sbvalues.Append(value.Name + "=" + value.Value);
if (count != values.Count - 1)
sbvalues.Append("AND");
count++;
}
count = 0;
foreach (SqlValue where in wheres)
{
sbwheres.Append(where.Name + "=" + where.Value);
if (count != wheres.Count - 1)
sbwheres.Append(" AND ");
count++;
}
if (wheres.Count > 0)
return "UPDATE {0} SET {1} WHERE {2}".SFormat(table, sbvalues.ToString(), sbwheres.ToString());
else
return "UPDATE {0} SET {1}".SFormat(table, sbvalues.ToString());
}
public string InsertValues(string table, List<SqlValue> values)
{
var sbnames = new StringBuilder();
var sbvalues = new StringBuilder();
int count = 0;
foreach (SqlValue name in values)
{
sbnames.Append(name.Name);
if (count != values.Count - 1)
sbnames.Append(", ");
count++;
}
count = 0;
foreach (SqlValue value in values)
{
sbvalues.Append(value.Value.ToString());
if (count != values.Count - 1)
sbvalues.Append(", ");
count++;
}
return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(table, sbnames.ToString(), sbvalues.ToString());
}
public string ReadColumn(string table, List<SqlValue> wheres)
{
var sbwheres = new StringBuilder();
int count = 0;
foreach (SqlValue where in wheres)
{
sbwheres.Append(where.Name + "=" + where.Value);
if (count != wheres.Count - 1)
sbwheres.Append(" AND ");
count++;
}
if (wheres.Count > 0)
return "SELECT * FROM {0} WHERE {1}".SFormat(table, sbwheres.ToString());
else
return "SELECT * FROM {0}".SFormat(table);
} }
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string> private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string>
@ -340,5 +119,95 @@ namespace TShockAPI.DB
return ret + (length != null ? "({0})".SFormat((int) length) : ""); return ret + (length != null ? "({0})".SFormat((int) length) : "");
throw new NotImplementedException(Enum.GetName(typeof (MySqlDbType), type)); throw new NotImplementedException(Enum.GetName(typeof (MySqlDbType), type));
} }
protected override string EscapeTableName(string table)
{
return table.SFormat("`{0}`", table);
}
}
public abstract class GenericQueryCreator
{
protected static Random rand = new Random();
protected abstract string EscapeTableName(string table);
public abstract string CreateTable(SqlTable table);
public abstract string RenameTable(string from, string to);
/// <summary>
/// Alter a table from source to destination
/// </summary>
/// <param name="from">Must have name and column names. Column types are not required</param>
/// <param name="to">Must have column names and column types.</param>
/// <returns></returns>
public string AlterTable(SqlTable from, SqlTable to)
{
/*
* Any example outpuf from this looks like:-
ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans"
CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT)
INSERT INTO "main"."Bans" SELECT "IP","Name" FROM "main"."oXHFcGcd04oXHFcGcd04_Bans"
DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans"
*
* Twitchy - Oh. I get it!
*/
var rstr = rand.NextString(20);
var escapedTable = EscapeTableName(from.Name);
var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, from.Name));
var alter = RenameTable(escapedTable, tmpTable);
var create = CreateTable(to);
// combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable.
// exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data?
var columns = string.Join(", ", from.Columns.Where(c => to.Columns.Any(c2 => c2.Name == c.Name)).Select(c => c.Name));
var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}".SFormat(escapedTable, columns, tmpTable);
var drop = "DROP TABLE {0}".SFormat(tmpTable);
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
}
public string DeleteRow(string table, List<SqlValue> wheres)
{
return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
}
public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres)
{
if (0 == values.Count)
throw new ArgumentException("No values supplied");
return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres));
}
public string ReadColumn(string table, List<SqlValue> wheres)
{
return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
}
public string InsertValues(string table, List<SqlValue> values)
{
var sbnames = new StringBuilder();
var sbvalues = new StringBuilder();
int count = 0;
foreach (SqlValue value in values)
{
sbnames.Append(value.Name);
sbvalues.Append(value.Value.ToString());
if (count != values.Count - 1)
{
sbnames.Append(", ");
sbvalues.Append(", ");
}
count++;
}
return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues);
}
protected static string BuildWhere(List<SqlValue> wheres)
{
if (0 == wheres.Count)
return string.Empty;
return "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => v.Name + " = " + v.Value)));
}
} }
} }

View file

@ -1,4 +1,4 @@
/* /*
TShock, a server mod for Terraria TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team Copyright (C) 2011 The TShock Team

View file

@ -18,7 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using System; using System;
using System.Data; using System.Data;
using System.IO; using System.IO;
using System.Collections.Generic;
using System.Linq;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
using System.Text.RegularExpressions;
namespace TShockAPI.DB namespace TShockAPI.DB
{ {
@ -50,20 +53,25 @@ namespace TShockAPI.DB
/// <param name="user">User user</param> /// <param name="user">User user</param>
public void AddUser(User user) public void AddUser(User user)
{ {
if (!TShock.Groups.GroupExists(user.Group))
throw new GroupNotExistsException(user.Group);
int ret;
try try
{ {
if (!TShock.Groups.GroupExists(user.Group)) ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name,
throw new GroupNotExistsException(user.Group); TShock.Utils.HashPassword(user.Password), user.Group, user.Address);
if (
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) < 1)
throw new UserExistsException(user.Name);
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new UserManagerException("AddUser SQL returned an error", ex); // Detect duplicate user using a regexp as Sqlite doesn't have well structured exceptions
if (Regex.IsMatch(ex.Message, "Username.*not unique"))
throw new UserExistsException(user.Name);
throw new UserManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex);
} }
if (1 > ret)
throw new UserExistsException(user.Name);
} }
/// <summary> /// <summary>
@ -123,11 +131,18 @@ namespace TShockAPI.DB
{ {
try try
{ {
if (!TShock.Groups.GroupExists(group)) Group grp = TShock.Groups.GetGroupByName(group);
if (null == grp)
throw new GroupNotExistsException(group); throw new GroupNotExistsException(group);
if (database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0) if (database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0)
throw new UserNotExistException(user.Name); throw new UserNotExistException(user.Name);
// Update player group reference for any logged in player
foreach (var player in TShock.Players.Where(p => null != p && p.UserAccountName == user.Name))
{
player.Group = grp;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -239,37 +254,83 @@ namespace TShockAPI.DB
public User GetUser(User user) public User GetUser(User user)
{ {
bool multiple = false;
string query;
string type;
object arg;
if (0 != user.ID)
{
query = "SELECT * FROM Users WHERE ID=@0";
arg = user.ID;
type = "id";
}
else if (string.IsNullOrEmpty(user.Address))
{
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 try
{ {
QueryResult result; using (var result = database.QueryReader(query, arg))
if (string.IsNullOrEmpty(user.Address))
{ {
result = database.QueryReader("SELECT * FROM Users WHERE Username=@0", user.Name); if (result.Read())
}
else
{
result = database.QueryReader("SELECT * FROM Users WHERE IP=@0", user.Address);
}
using (var reader = result)
{
if (reader.Read())
{ {
user.ID = reader.Get<int>("ID"); user = LoadUserFromResult(user, result);
user.Group = reader.Get<string>("Usergroup"); // Check for multiple matches
user.Password = reader.Get<string>("Password"); if (!result.Read())
user.Name = reader.Get<string>("Username"); return user;
user.Address = reader.Get<string>("IP"); multiple = true;
return user;
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new UserManagerException("GetUserID SQL returned an error", ex); throw new UserManagerException("GetUser SQL returned an error (" + ex.Message + ")", ex);
} }
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(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address);
} }
public List<User> GetUsers()
{
try
{
List<User> users = new List<User>();
using (var reader = database.QueryReader("SELECT * FROM Users"))
{
while (reader.Read())
{
users.Add(LoadUserFromResult(new User(), reader));
}
return users;
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
return null;
}
private User LoadUserFromResult(User user, QueryResult result)
{
user.ID = result.Get<int>("ID");
user.Group = result.Get<string>("Usergroup");
user.Password = result.Get<string>("Password");
user.Name = result.Get<string>("Username");
user.Address = result.Get<string>("IP");
return user;
}
} }
public class User public class User

View file

@ -135,6 +135,38 @@ namespace TShockAPI
return args.Handled; return args.Handled;
} }
/// <summary>
/// For use in a PlayerTeam event
/// </summary>
public class PlayerTeamEventArgs : HandledEventArgs
{
/// <summary>
/// The Terraria player ID of the player
/// </summary>
public byte PlayerId { get; set; }
/// <summary>
/// Enable/disable pvp?
/// </summary>
public byte Team { get; set; }
}
/// <summary>
/// TogglePvp - called when a player toggles pvp
/// </summary>
public static HandlerList<PlayerTeamEventArgs> PlayerTeam;
private static bool OnPlayerTeam(byte _id, byte _team)
{
if (PlayerTeam == null)
return false;
var args = new PlayerTeamEventArgs
{
PlayerId = _id,
Team = _team,
};
PlayerTeam.Invoke(null, args);
return args.Handled;
}
/// <summary> /// <summary>
/// For use in a PlayerSlot event /// For use in a PlayerSlot event
/// </summary> /// </summary>
@ -444,7 +476,7 @@ namespace TShockAPI
{ {
for (int j = num3; j < num4; j++) for (int j = num3; j < num4; j++)
{ {
if (Main.tile[i, j] != null && Main.tile[i, j].active && Main.tileSolid[(int)Main.tile[i, j].type] && !Main.tileSolidTop[(int)Main.tile[i, j].type] &&(((int)Main.tile[i,j].type !=53) && ((int)Main.tile[i,j].type !=112) && ((int)Main.tile[i,j].type !=116) && ((int)Main.tile[i,j].type !=123))) if (Main.tile[i, j] != null && Main.tile[i, j].active && Main.tileSolid[(int)Main.tile[i, j].type] && !Main.tileSolidTop[(int)Main.tile[i, j].type] &&(((int)Main.tile[i,j].type !=53) && ((int)Main.tile[i,j].type !=112) && ((int)Main.tile[i,j].type !=116) && ((int)Main.tile[i,j].type !=123)) && ((Main.tile[i,j].liquid == 0 )&& !Main.tile[i,j].lava))
{ {
Vector2 vector; Vector2 vector;
vector.X = (float)(i * 16); vector.X = (float)(i * 16);
@ -1091,6 +1123,7 @@ namespace TShockAPI
{PacketTypes.TileSendSquare, HandleSendTileSquare}, {PacketTypes.TileSendSquare, HandleSendTileSquare},
{PacketTypes.ProjectileNew, HandleProjectileNew}, {PacketTypes.ProjectileNew, HandleProjectileNew},
{PacketTypes.TogglePvp, HandleTogglePvp}, {PacketTypes.TogglePvp, HandleTogglePvp},
{PacketTypes.PlayerTeam, HandlePlayerTeam},
{PacketTypes.TileKill, HandleTileKill}, {PacketTypes.TileKill, HandleTileKill},
{PacketTypes.PlayerKillMe, HandlePlayerKillMe}, {PacketTypes.PlayerKillMe, HandlePlayerKillMe},
{PacketTypes.LiquidSet, HandleLiquidSet}, {PacketTypes.LiquidSet, HandleLiquidSet},
@ -1774,6 +1807,25 @@ namespace TShockAPI
return true; return true;
} }
private static bool HandlePlayerTeam(GetDataHandlerArgs args)
{
byte id = args.Data.ReadInt8();
byte team = args.Data.ReadInt8();
if (OnPlayerTeam(id, team))
return true;
if (id != args.Player.Index)
{
return true;
}
args.TPlayer.team = team;
NetMessage.SendData((int)PacketTypes.PlayerTeam, -1, -1, "", args.Player.Index);
return true;
}
private static bool HandlePlayerUpdate(GetDataHandlerArgs args) private static bool HandlePlayerUpdate(GetDataHandlerArgs args)
{ {
var plr = args.Data.ReadInt8(); var plr = args.Data.ReadInt8();

View file

@ -16,12 +16,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
namespace TShockAPI namespace TShockAPI
{ {
public class Group public class Group
{ {
// NOTE: Using a const still suffers from needing to recompile to change the default
// ideally we would use a static but this means it can't be used for the default parameter :(
public const string defaultChatColor = "255.255.255";
public readonly List<string> permissions = new List<string>(); public readonly List<string> permissions = new List<string>();
public readonly List<string> negatedpermissions = new List<string>(); public readonly List<string> negatedpermissions = new List<string>();
@ -30,28 +34,106 @@ namespace TShockAPI
public int Order { get; set; } public int Order { get; set; }
public string Prefix { get; set; } public string Prefix { get; set; }
public string Suffix { get; set; } public string Suffix { get; set; }
public string ParentName { get { return (null == Parent) ? "" : Parent.Name; } }
public string ChatColor
{
get { return string.Format("{0}{1}{2}", R.ToString("X2"), G.ToString("X2"), B.ToString("X2")); }
set
{
if (null != value)
{
string[] parts = value.Split(',');
if (3 == parts.Length)
{
byte r, g, b;
if (byte.TryParse(parts[0], out r) && byte.TryParse(parts[1], out g) && byte.TryParse(parts[2], out b))
{
R = r;
G = g;
B = b;
return;
}
}
}
}
}
public string Permissions
{
get
{
List<string> all = new List<string>(permissions);
negatedpermissions.ForEach(p => all.Add("!" + p));
return string.Join(",", all);
}
set
{
permissions.Clear();
negatedpermissions.Clear();
if (null != value)
value.Split(',').ForEach(p => AddPermission(p.Trim()));
}
}
public List<string> TotalPermissions
{
get
{
var cur = this;
var traversed = new List<Group>();
HashSet<string> all = new HashSet<string>();
while (cur != null)
{
foreach (var perm in cur.permissions)
{
all.Add(perm);
}
foreach (var perm in cur.negatedpermissions)
{
all.Remove(perm);
}
if (traversed.Contains(cur))
{
throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name));
}
traversed.Add(cur);
cur = cur.Parent;
}
return all.ToList();
}
}
public byte R = 255; public byte R = 255;
public byte G = 255; public byte G = 255;
public byte B = 255; public byte B = 255;
public Group(string groupname, Group parentgroup = null, string chatcolor = "255,255,255") #if COMPAT_SIGS
[Obsolete("This constructor is for signature compatibility for external code only")]
public Group(string groupname, Group parentgroup, string chatcolor)
: this(groupname, parentgroup, chatcolor, null)
{
}
#endif
public Group(string groupname, Group parentgroup = null, string chatcolor = "255,255,255", string permissions = null)
{ {
Name = groupname; Name = groupname;
Parent = parentgroup; Parent = parentgroup;
byte.TryParse(chatcolor.Split(',')[0], out R); ChatColor = chatcolor;
byte.TryParse(chatcolor.Split(',')[1], out G); Permissions = permissions;
byte.TryParse(chatcolor.Split(',')[2], out B);
} }
public virtual bool HasPermission(string permission) public virtual bool HasPermission(string permission)
{ {
if (string.IsNullOrEmpty(permission))
return true;
var cur = this; var cur = this;
var traversed = new List<Group>(); var traversed = new List<Group>();
while (cur != null) while (cur != null)
{ {
if (string.IsNullOrEmpty(permission))
return true;
if (cur.negatedpermissions.Contains(permission)) if (cur.negatedpermissions.Contains(permission))
return false; return false;
if (cur.permissions.Contains(permission)) if (cur.permissions.Contains(permission))
@ -68,21 +150,44 @@ namespace TShockAPI
public void NegatePermission(string permission) public void NegatePermission(string permission)
{ {
negatedpermissions.Add(permission); // Avoid duplicates
if (!negatedpermissions.Contains(permission))
{
negatedpermissions.Add(permission);
permissions.Remove(permission); // Ensure we don't have conflicting definitions for a permissions
}
} }
public void AddPermission(string permission) public void AddPermission(string permission)
{ {
permissions.Add(permission); if (permission.StartsWith("!"))
{
NegatePermission(permission.Substring(1));
return;
}
// Avoid duplicates
if (!permissions.Contains(permission))
{
permissions.Add(permission);
negatedpermissions.Remove(permission); // Ensure we don't have conflicting definitions for a permissions
}
} }
public void SetPermission(List<string> permission) public void SetPermission(List<string> permission)
{ {
permissions.Clear(); permissions.Clear();
foreach (string s in permission) negatedpermissions.Clear();
permission.ForEach(p => AddPermission(p));
}
public void RemovePermission(string permission)
{
if (permission.StartsWith("!"))
{ {
permissions.Add(s); negatedpermissions.Remove(permission.Substring(1));
return;
} }
permissions.Remove(permission);
} }
} }

View file

@ -200,7 +200,7 @@ namespace TShockAPI
c => c =>
c.Name + (c.Names.Count > 1 ? "({0})".SFormat(string.Join(" ", c.Names.ToArray(), 1, c.Names.Count - 1)) : "")); c.Name + (c.Names.Count > 1 ? "({0})".SFormat(string.Join(" ", c.Names.ToArray(), 1, c.Names.Count - 1)) : ""));
sb.AppendLine("## <a name=\"{0}\">{0}</a> ".SFormat(name)); sb.AppendLine("## <a id=\"{0}\">{0}</a> ".SFormat(name));
sb.AppendLine("**Description:** {0} ".SFormat(desc)); sb.AppendLine("**Description:** {0} ".SFormat(desc));
sb.AppendLine("**Commands:** {0} ".SFormat(strs.Count() > 0 ? string.Join(" ", strs) : "None")); sb.AppendLine("**Commands:** {0} ".SFormat(strs.Count() > 0 ? string.Join(" ", strs) : "None"));
sb.AppendLine(); sb.AppendLine();

View file

@ -48,5 +48,5 @@ using System.Runtime.InteropServices;
// Build Number // Build Number
// MMdd of the build // MMdd of the build
[assembly: AssemblyVersion("3.7.0.0204")] [assembly: AssemblyVersion("3.8.0.0304")]
[assembly: AssemblyFileVersion("3.7.0.0204")] [assembly: AssemblyFileVersion("3.8.0.0304")]

View file

@ -253,9 +253,7 @@ namespace TShockAPI
WorldGen.genRand = new Random(); WorldGen.genRand = new Random();
if (text.StartsWith("exit")) if (text.StartsWith("exit"))
{ {
TShock.Utils.ForceKickAll("Server shutting down!"); TShock.Utils.StopServer();
WorldGen.saveWorld(false);
Netplay.disconnect = true;
return "Server shutting down."; return "Server shutting down.";
} }
else if (text.StartsWith("playing") || text.StartsWith("/playing")) else if (text.StartsWith("playing") || text.StartsWith("/playing"))

View file

@ -21,6 +21,7 @@ using System.ComponentModel;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Reflection;
using HttpServer; using HttpServer;
using HttpServer.Headers; using HttpServer.Headers;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -41,6 +42,7 @@ namespace Rests
{ {
private readonly List<RestCommand> commands = new List<RestCommand>(); private readonly List<RestCommand> commands = new List<RestCommand>();
private HttpListener listener; private HttpListener listener;
private StringHeader serverHeader;
public IPAddress Ip { get; set; } public IPAddress Ip { get; set; }
public int Port { get; set; } public int Port { get; set; }
@ -48,6 +50,9 @@ namespace Rests
{ {
Ip = ip; Ip = ip;
Port = port; Port = port;
string appName = this.GetType().Assembly.GetName().Version.ToString();
AssemblyName ass = this.GetType().Assembly.GetName();
serverHeader = new StringHeader("Server", String.Format("{0}/{1}", ass.Name, ass.Version));
} }
public virtual void Start() public virtual void Start()
@ -117,13 +122,14 @@ namespace Rests
throw new NullReferenceException("obj"); throw new NullReferenceException("obj");
if (OnRestRequestCall(e)) if (OnRestRequestCall(e))
return; return;
var str = JsonConvert.SerializeObject(obj, Formatting.Indented); var str = JsonConvert.SerializeObject(obj, Formatting.Indented);
e.Response.Connection.Type = ConnectionType.Close; e.Response.Connection.Type = ConnectionType.Close;
e.Response.ContentType = new ContentTypeHeader("application/json");
e.Response.Add(serverHeader);
e.Response.Body.Write(Encoding.ASCII.GetBytes(str), 0, str.Length); e.Response.Body.Write(Encoding.ASCII.GetBytes(str), 0, str.Length);
e.Response.Status = HttpStatusCode.OK; e.Response.Status = HttpStatusCode.OK;
return;
} }
protected virtual object ProcessRequest(object sender, RequestEventArgs e) protected virtual object ProcessRequest(object sender, RequestEventArgs e)

View file

@ -1,4 +1,4 @@
/* /*
TShock, a server mod for Terraria TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team Copyright (C) 2011 The TShock Team

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
/* /*
TShock, a server mod for Terraria TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team Copyright (C) 2011 The TShock Team
@ -41,7 +41,14 @@ namespace Rests
set { this["response"] = value; } set { this["response"] = value; }
} }
public RestObject(string status) // Parameterless constructor for deseralisation required by JavaScriptSerializer.Deserialize in TShockRestTestPlugin
// Note: The constructor with all defaults isn't good enough :(
public RestObject()
{
Status = "200";
}
public RestObject(string status = "200")
{ {
Status = status; Status = status;
} }

View file

@ -1,4 +1,4 @@
/* /*
TShock, a server mod for Terraria TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team Copyright (C) 2011 The TShock Team

143
TShockAPI/SaveManager.cs Normal file
View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;
using Terraria;
namespace TShockAPI
{
class SaveManager : IDisposable
{
// Singleton
private static readonly SaveManager instance = new SaveManager();
private SaveManager()
{
_saveThread = new Thread(SaveWorker);
_saveThread.Name = "TShock SaveManager Worker";
_saveThread.Start();
}
public static SaveManager Instance { get { return instance; } }
// Producer Consumer
private EventWaitHandle _wh = new AutoResetEvent(false);
private Object _saveLock = new Object();
private Queue<SaveTask> _saveQueue = new Queue<SaveTask>();
private Thread _saveThread;
private int saveQueueCount { get { lock (_saveLock) return _saveQueue.Count; } }
/// <summary>
/// SaveWorld event handler which notifies users that the server may lag
/// </summary>
public void OnSaveWorld(bool resettime = false, HandledEventArgs e = null)
{
// Protect against internal errors causing save failures
// These can be caused by an unexpected error such as a bad or out of date plugin
try
{
TShock.Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red);
}
catch (Exception ex)
{
Log.Error("World saved notification failed");
Log.Error(ex.ToString());
}
}
/// <summary>
/// Saves the map data
/// </summary>
/// <param name="wait">wait for all pending saves to finish (default: true)</param>
/// <param name="resetTime">reset the last save time counter (default: false)</param>
/// <param name="direct">use the realsaveWorld method instead of saveWorld event (default: false)</param>
public void SaveWorld(bool wait = true, bool resetTime = false, bool direct = false)
{
EnqueueTask(new SaveTask(resetTime, direct));
if (!wait)
return;
// Wait for all outstanding saves to complete
int count = saveQueueCount;
while (0 != count)
{
Thread.Sleep(50);
count = saveQueueCount;
}
}
/// <summary>
/// Processes any outstanding saves, shutsdown the save thread and returns
/// </summary>
public void Dispose()
{
EnqueueTask(null);
_saveThread.Join();
_wh.Close();
}
private void EnqueueTask(SaveTask task)
{
lock (_saveLock)
{
_saveQueue.Enqueue(task);
}
_wh.Set();
}
private void SaveWorker()
{
while (true)
{
lock (_saveLock)
{
// NOTE: lock for the entire process so wait works in SaveWorld
if (_saveQueue.Count > 0)
{
SaveTask task = _saveQueue.Dequeue();
if (null == task)
return;
else
{
// Ensure that save handler errors don't bubble up and cause a recursive call
// These can be caused by an unexpected error such as a bad or out of date plugin
try
{
if (task.direct)
{
OnSaveWorld();
WorldGen.realsaveWorld(task.resetTime);
}
else
WorldGen.saveWorld(task.resetTime);
TShock.Utils.Broadcast("World saved.", Color.Yellow);
Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
}
catch (Exception e)
{
Log.Error("World saved failed");
Log.Error(e.ToString());
}
}
}
}
_wh.WaitOne();
}
}
class SaveTask
{
public bool resetTime { get; set; }
public bool direct { get; set; }
public SaveTask(bool resetTime, bool direct)
{
this.resetTime = resetTime;
this.direct = direct;
}
public override string ToString()
{
return string.Format("resetTime {0}, direct {1}", resetTime, direct);
}
}
}
}

View file

@ -76,12 +76,12 @@ namespace TShockAPI
public bool RequiresPassword; public bool RequiresPassword;
public bool SilentKickInProgress; public bool SilentKickInProgress;
public List<Point> IceTiles; public List<Point> IceTiles;
public long RPm=1; public long RPm = 1;
public long WPm=1; public long WPm = 1;
public long SPm=1; public long SPm = 1;
public long BPm=1; public long BPm = 1;
public long LoginMS; public long LoginMS;
public bool LoginHarassed = false;
public bool RealPlayer public bool RealPlayer
{ {
get { return Index >= 0 && Index < Main.maxNetPlayers && Main.player[Index] != null; } get { return Index >= 0 && Index < Main.maxNetPlayers && Main.player[Index] != null; }
@ -257,6 +257,10 @@ namespace TShockAPI
//The error occurs when a tile trys to update which the client hasnt load yet, Clients only update tiles withen 150 blocks //The error occurs when a tile trys to update which the client hasnt load yet, Clients only update tiles withen 150 blocks
//Try 300 if it does not work (Higher number - Longer load times - Less chance of error) //Try 300 if it does not work (Higher number - Longer load times - Less chance of error)
//Should we properly send sections so that clients don't get tiles twice? //Should we properly send sections so that clients don't get tiles twice?
SendTileSquare(tilex, tiley, 150);
/* //We shouldn't need this section anymore -it can prevent otherwise acceptable teleportation under some circumstances.
if (!SendTileSquare(tilex, tiley, 150)) if (!SendTileSquare(tilex, tiley, 150))
{ {
InitSpawn = true; InitSpawn = true;
@ -264,6 +268,7 @@ namespace TShockAPI
return false; return false;
} }
*/
Spawn(-1, -1); Spawn(-1, -1);
SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false);
@ -272,7 +277,7 @@ namespace TShockAPI
TPlayer.position.Y = (float)(tiley * 16 - TPlayer.height); TPlayer.position.Y = (float)(tiley * 16 - TPlayer.height);
//We need to send the tile data again to prevent clients from thinking they *really* destroyed blocks just now. //We need to send the tile data again to prevent clients from thinking they *really* destroyed blocks just now.
SendTileSquare(tilex, tiley, 150); SendTileSquare(tilex, tiley, 10);
return true; return true;
} }

189
TShockAPI/TShock.cs Normal file → Executable file
View file

@ -40,6 +40,9 @@ namespace TShockAPI
[APIVersion(1, 11)] [APIVersion(1, 11)]
public class TShock : TerrariaPlugin public class TShock : TerrariaPlugin
{ {
private const string LogFormatDefault = "yyyyMMddHHmmss";
private static string LogFormat = LogFormatDefault;
private static bool LogClear = false;
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
public static readonly string VersionCodename = "Squashing bugs, and adding suggestions"; public static readonly string VersionCodename = "Squashing bugs, and adding suggestions";
@ -62,7 +65,7 @@ namespace TShockAPI
public static GeoIPCountry Geo; public static GeoIPCountry Geo;
public static SecureRest RestApi; public static SecureRest RestApi;
public static RestManager RestManager; public static RestManager RestManager;
public static Utils Utils = new Utils(); public static Utils Utils = Utils.Instance;
public static StatTracker StatTracker = new StatTracker(); public static StatTracker StatTracker = new StatTracker();
/// <summary> /// <summary>
/// Used for implementing REST Tokens prior to the REST system starting up. /// Used for implementing REST Tokens prior to the REST system starting up.
@ -111,20 +114,30 @@ namespace TShockAPI
if (!Directory.Exists(SavePath)) if (!Directory.Exists(SavePath))
Directory.CreateDirectory(SavePath); Directory.CreateDirectory(SavePath);
DateTime now = DateTime.Now; 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");
}
#if DEBUG #if DEBUG
Log.Initialize(Path.Combine(SavePath, now.ToString("yyyyMMddHHmmss")+".log"), LogLevel.All, false); Log.Initialize(logFilename, LogLevel.All, false);
#else #else
Log.Initialize(Path.Combine(SavePath, now.ToString("yyyyMMddHHmmss")+".log"), LogLevel.All & ~LogLevel.Debug, false); Log.Initialize(logFilename, LogLevel.All & ~LogLevel.Debug, LogClear);
#endif #endif
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
try try
{ {
if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
{ {
Log.ConsoleInfo( Log.ConsoleInfo(
"TShock was improperly shut down. Please avoid this in the future, world corruption may result from this."); "TShock was improperly shut down. Please use the exit command in the future to prevent this.");
File.Delete(Path.Combine(SavePath, "tshock.pid")); File.Delete(Path.Combine(SavePath, "tshock.pid"));
} }
File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));
@ -172,7 +185,6 @@ namespace TShockAPI
Warps = new WarpManager(DB); Warps = new WarpManager(DB);
Users = new UserManager(DB); Users = new UserManager(DB);
Groups = new GroupManager(DB); Groups = new GroupManager(DB);
Groups.LoadPermisions();
Regions = new RegionManager(DB); Regions = new RegionManager(DB);
Itembans = new ItemManager(DB); Itembans = new ItemManager(DB);
RememberedPos = new RemeberedPosManager(DB); RememberedPos = new RemeberedPosManager(DB);
@ -187,7 +199,6 @@ namespace TShockAPI
if (Config.EnableGeoIP && File.Exists(geoippath)) if (Config.EnableGeoIP && File.Exists(geoippath))
Geo = new GeoIPCountry(geoippath); Geo = new GeoIPCountry(geoippath);
Console.Title = string.Format("TerrariaShock Version {0} ({1})", Version, VersionCodename);
Log.ConsoleInfo(string.Format("TerrariaShock Version {0} ({1}) now running.", Version, VersionCodename)); Log.ConsoleInfo(string.Format("TerrariaShock Version {0} ({1}) now running.", Version, VersionCodename));
GameHooks.PostInitialize += OnPostInit; GameHooks.PostInitialize += OnPostInit;
@ -204,12 +215,15 @@ namespace TShockAPI
NpcHooks.SetDefaultsInt += OnNpcSetDefaults; NpcHooks.SetDefaultsInt += OnNpcSetDefaults;
ProjectileHooks.SetDefaults += OnProjectileSetDefaults; ProjectileHooks.SetDefaults += OnProjectileSetDefaults;
WorldHooks.StartHardMode += OnStartHardMode; WorldHooks.StartHardMode += OnStartHardMode;
WorldHooks.SaveWorld += OnSaveWorld; WorldHooks.SaveWorld += SaveManager.Instance.OnSaveWorld;
GetDataHandlers.InitGetDataHandler(); GetDataHandlers.InitGetDataHandler();
Commands.InitCommands(); Commands.InitCommands();
//RconHandler.StartThread(); //RconHandler.StartThread();
if (Config.RestApiEnabled)
RestApi.Start();
if (Config.BufferPackets) if (Config.BufferPackets)
PacketBuffer = new PacketBufferer(); PacketBuffer = new PacketBufferer();
@ -258,10 +272,13 @@ namespace TShockAPI
{ {
if (disposing) if (disposing)
{ {
// NOTE: order is important here
if (Geo != null) if (Geo != null)
{ {
Geo.Dispose(); Geo.Dispose();
} }
SaveManager.Instance.Dispose();
GameHooks.PostInitialize -= OnPostInit; GameHooks.PostInitialize -= OnPostInit;
GameHooks.Update -= OnUpdate; GameHooks.Update -= OnUpdate;
ServerHooks.Connect -= OnConnect; ServerHooks.Connect -= OnConnect;
@ -276,15 +293,16 @@ namespace TShockAPI
NpcHooks.SetDefaultsInt -= OnNpcSetDefaults; NpcHooks.SetDefaultsInt -= OnNpcSetDefaults;
ProjectileHooks.SetDefaults -= OnProjectileSetDefaults; ProjectileHooks.SetDefaults -= OnProjectileSetDefaults;
WorldHooks.StartHardMode -= OnStartHardMode; WorldHooks.StartHardMode -= OnStartHardMode;
WorldHooks.SaveWorld -= OnSaveWorld; WorldHooks.SaveWorld -= SaveManager.Instance.OnSaveWorld;
if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
{ {
File.Delete(Path.Combine(SavePath, "tshock.pid")); File.Delete(Path.Combine(SavePath, "tshock.pid"));
} }
RestApi.Dispose(); RestApi.Dispose();
Log.Dispose(); Log.Dispose();
} }
base.Dispose(disposing); base.Dispose(disposing);
} }
@ -320,70 +338,83 @@ namespace TShockAPI
if (Main.worldPathName != null && Config.SaveWorldOnCrash) if (Main.worldPathName != null && Config.SaveWorldOnCrash)
{ {
Main.worldPathName += ".crash"; Main.worldPathName += ".crash";
WorldGen.saveWorld(); SaveManager.Instance.SaveWorld();
} }
} }
} }
private void HandleCommandLine(string[] parms) private void HandleCommandLine(string[] parms)
{ {
string path;
for (int i = 0; i < parms.Length; i++) for (int i = 0; i < parms.Length; i++)
{ {
if (parms[i].ToLower() == "-configpath") switch(parms[i].ToLower())
{ {
var path = parms[++i]; case "-configpath":
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) path = parms[++i];
{ if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
SavePath = path; {
Log.ConsoleInfo("Config path has been set to " + path); SavePath = path;
} Log.ConsoleInfo("Config path has been set to " + path);
} }
if (parms[i].ToLower() == "-worldpath") break;
{
var path = parms[++i]; case "-worldpath":
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) path = parms[++i];
{ if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
Main.WorldPath = path; {
Log.ConsoleInfo("World path has been set to " + path); Main.WorldPath = path;
} Log.ConsoleInfo("World path has been set to " + path);
} }
if (parms[i].ToLower() == "-dump") break;
{
ConfigFile.DumpDescriptions(); case "-dump":
Permissions.DumpDescriptions(); ConfigFile.DumpDescriptions();
Permissions.DumpDescriptions();
break;
case "-logformat":
LogFormat = parms[++i];
break;
case "-logclear":
bool.TryParse(parms[++i], out LogClear);
break;
} }
} }
} }
private void HandleCommandLinePostConfigLoad(string[] parms) public static void HandleCommandLinePostConfigLoad(string[] parms)
{ {
for (int i = 0; i < parms.Length; i++) for (int i = 0; i < parms.Length; i++)
{ {
if (parms[i].ToLower() == "-port") switch(parms[i].ToLower())
{ {
int port = Convert.ToInt32(parms[++i]); case "-port":
Netplay.serverPort = port; int port = Convert.ToInt32(parms[++i]);
Config.ServerPort = port; Netplay.serverPort = port;
OverridePort = true; Config.ServerPort = port;
Log.ConsoleInfo("Port overridden by startup argument. Set to " + port); OverridePort = true;
} Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
if (parms[i].ToLower() == "-rest-token") break;
{ case "-rest-token":
string token = Convert.ToString(parms[++i]); string token = Convert.ToString(parms[++i]);
RESTStartupTokens.Add(token, "null"); RESTStartupTokens.Add(token, "null");
Console.WriteLine("Startup parameter overrode REST token."); Console.WriteLine("Startup parameter overrode REST token.");
} break;
if (parms[i].ToLower() == "-rest-enabled") case "-rest-enabled":
{ Config.RestApiEnabled = Convert.ToBoolean(parms[++i]);
Config.RestApiEnabled = Convert.ToBoolean(parms[++i]); Console.WriteLine("Startup parameter overrode REST enable.");
Console.WriteLine("Startup parameter overrode REST enable."); break;
case "-rest-port":
} Config.RestApiPort = Convert.ToInt32(parms[++i]);
if (parms[i].ToLower() == "-rest-port") Console.WriteLine("Startup parameter overrode REST port.");
{ break;
Config.RestApiPort = Convert.ToInt32(parms[++i]); case "-maxplayers":
Console.WriteLine("Startup parameter overrode REST port."); case "-players":
Config.MaxSlots = Convert.ToInt32(parms[++i]);
Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
break;
} }
} }
} }
@ -397,6 +428,7 @@ namespace TShockAPI
private void OnPostInit() private void OnPostInit()
{ {
SetConsoleTitle();
if (!File.Exists(Path.Combine(SavePath, "auth.lck")) && !File.Exists(Path.Combine(SavePath, "authcode.txt"))) if (!File.Exists(Path.Combine(SavePath, "auth.lck")) && !File.Exists(Path.Combine(SavePath, "authcode.txt")))
{ {
var r = new Random((int) DateTime.Now.ToBinary()); var r = new Random((int) DateTime.Now.ToBinary());
@ -429,8 +461,6 @@ namespace TShockAPI
AuthToken = 0; AuthToken = 0;
} }
Regions.ReloadAllRegions(); Regions.ReloadAllRegions();
if (Config.RestApiEnabled)
RestApi.Start();
StatTracker.CheckIn(); StatTracker.CheckIn();
FixChestStacks(); FixChestStacks();
@ -460,7 +490,6 @@ namespace TShockAPI
StatTracker.CheckIn(); StatTracker.CheckIn();
if (Backups.IsBackupTime) if (Backups.IsBackupTime)
Backups.Backup(); Backups.Backup();
//call these every second, not every update //call these every second, not every update
if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1) if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1)
{ {
@ -585,8 +614,13 @@ namespace TShockAPI
} }
} }
} }
Console.Title = string.Format("TerrariaShock Version {0} ({1}) ({2}/{3})", Version, VersionCodename, count, SetConsoleTitle();
Config.MaxSlots); }
private void SetConsoleTitle()
{
Console.Title = string.Format("{0} - {1}/{2} @ {3}:{4} (TerrariaShock v{5})", Config.ServerName, Utils.ActivePlayers(),
Config.MaxSlots, Netplay.serverListenIP, Config.ServerPort, Version);
} }
private void OnConnect(int ply, HandledEventArgs handler) private void OnConnect(int ply, HandledEventArgs handler)
@ -653,10 +687,18 @@ namespace TShockAPI
return; return;
} }
var nameban = Bans.GetBanByName(player.Name);
Ban ban = null; Ban ban = null;
if (nameban != null && Config.EnableBanOnUsernames) if (Config.EnableBanOnUsernames)
ban = nameban; {
var newban = Bans.GetBanByName(player.Name);
if (null != newban)
ban = newban;
}
if (Config.EnableIPBans && null == ban)
{
ban = Bans.GetBanByIp(player.IP);
}
if (ban != null) if (ban != null)
{ {
@ -686,7 +728,7 @@ namespace TShockAPI
InventoryDB.InsertPlayerData(tsplr); InventoryDB.InsertPlayerData(tsplr);
} }
if (Config.RememberLeavePos) if ((Config.RememberLeavePos) &&(!tsplr.LoginHarassed))
{ {
RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int) (tsplr.X/16), (int) (tsplr.Y/16)); RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int) (tsplr.X/16), (int) (tsplr.Y/16));
} }
@ -860,10 +902,12 @@ namespace TShockAPI
player.SendMessage( player.SendMessage(
player.IgnoreActionsForInventory = "Server Side Inventory is enabled! Please /register or /login to play!", player.IgnoreActionsForInventory = "Server Side Inventory is enabled! Please /register or /login to play!",
Color.Red); Color.Red);
player.LoginHarassed = true;
} }
else if (Config.RequireLogin) else if (Config.RequireLogin)
{ {
player.SendMessage("Please /register or /login to play!", Color.Red); player.SendMessage("Please /register or /login to play!", Color.Red);
player.LoginHarassed = true;
} }
} }
@ -997,17 +1041,6 @@ namespace TShockAPI
e.Handled = true; e.Handled = true;
} }
void OnSaveWorld(bool resettime, HandledEventArgs e)
{
if (!Utils.saving)
{
Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red);
var SaveWorld = new Thread(Utils.SaveWorld);
SaveWorld.Start();
}
e.Handled = true;
}
/* /*
* Useful stuff: * Useful stuff:
* */ * */

View file

@ -43,7 +43,7 @@
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE;COMPAT_SIGS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -85,6 +85,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="BackupManager.cs" /> <Compile Include="BackupManager.cs" />
<Compile Include="SaveManager.cs" />
<Compile Include="DB\BanManager.cs" /> <Compile Include="DB\BanManager.cs" />
<Compile Include="DB\InventoryManager.cs" /> <Compile Include="DB\InventoryManager.cs" />
<Compile Include="DB\IQueryBuilder.cs" /> <Compile Include="DB\IQueryBuilder.cs" />

View file

@ -29,11 +29,12 @@ namespace TShockAPI
{ {
public class Utils public class Utils
{ {
public static bool saving = false; private readonly static int firstItemPrefix = 1;
private readonly static int lastItemPrefix = 83;
public Utils() // Utils is a Singleton
{ private static readonly Utils instance = new Utils();
} private Utils() {}
public static Utils Instance { get { return instance; } }
public Random Random = new Random(); public Random Random = new Random();
//private static List<Group> groups = new List<Group>(); //private static List<Group> groups = new List<Group>();
@ -135,11 +136,7 @@ namespace TShockAPI
/// </summary> /// </summary>
public void SaveWorld() public void SaveWorld()
{ {
saving = true; SaveManager.Instance.SaveWorld();
WorldGen.realsaveWorld();
Broadcast("World saved.", Color.Yellow);
Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
saving = false;
} }
/// <summary> /// <summary>
@ -186,15 +183,7 @@ namespace TShockAPI
/// <returns>int playerCount</returns> /// <returns>int playerCount</returns>
public int ActivePlayers() public int ActivePlayers()
{ {
int num = 0; return Main.player.Where(p => null != p && p.active).Count();
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player.Active)
{
num++;
}
}
return num;
} }
/// <summary> /// <summary>
@ -205,6 +194,9 @@ namespace TShockAPI
public List<TSPlayer> FindPlayer(string ply) public List<TSPlayer> FindPlayer(string ply)
{ {
var found = new List<TSPlayer>(); var found = new List<TSPlayer>();
// Avoid errors caused by null search
if (null == ply)
return found;
ply = ply.ToLower(); ply = ply.ToLower();
foreach (TSPlayer player in TShock.Players) foreach (TSPlayer player in TShock.Players)
{ {
@ -452,22 +444,32 @@ namespace TShockAPI
{ {
Item item = new Item(); Item item = new Item();
item.SetDefaults(0); item.SetDefaults(0);
for (int i = 1; i < 83; i++) string lowerName = name.ToLower();
{
item.prefix = (byte) i;
if (item.AffixName().Trim() == name)
return new List<int> {i};
}
var found = new List<int>(); var found = new List<int>();
for (int i = 1; i < 83; i++) for (int i = firstItemPrefix; i <= lastItemPrefix; i++)
{ {
try try
{ {
item.prefix = (byte) i; item.prefix = (byte)i;
if (item.AffixName().Trim().ToLower() == name.ToLower()) string trimmed = item.AffixName().Trim();
return new List<int> {i}; if (trimmed == name)
if (item.AffixName().Trim().ToLower().StartsWith(name.ToLower())) {
// Exact match
found.Add(i); found.Add(i);
return found;
}
else
{
string trimmedLower = trimmed.ToLower();
if (trimmedLower == lowerName)
{
// Exact match (caseinsensitive)
found.Add(i);
return found;
}
else if (trimmedLower.StartsWith(lowerName)) // Partial match
found.Add(i);
}
} }
catch catch
{ {
@ -484,7 +486,7 @@ namespace TShockAPI
public List<int> GetPrefixByIdOrName(string idOrName) public List<int> GetPrefixByIdOrName(string idOrName)
{ {
int type = -1; int type = -1;
if (int.TryParse(idOrName, out type) && type > 0 && type < 84) if (int.TryParse(idOrName, out type) && type >= firstItemPrefix && type <= lastItemPrefix)
{ {
return new List<int> {type}; return new List<int> {type};
} }
@ -507,71 +509,110 @@ namespace TShockAPI
} }
} }
/// <summary>
/// Stops the server after kicking all players with a reason message, and optionally saving the world
/// </summary>
/// <param name="save">bool perform a world save before stop (default: true)</param>
/// <param name="reason">string reason (default: "Server shutting down!")</param>
public void StopServer(bool save = true, string reason = "Server shutting down!")
{
ForceKickAll(reason);
if (save)
SaveManager.Instance.SaveWorld();
// Save takes a while so kick again
ForceKickAll(reason);
// Broadcast so console can see we are shutting down as well
TShock.Utils.Broadcast(reason, Color.Red);
// Disconnect after kick as that signifies server is exiting and could cause a race
Netplay.disconnect = true;
}
#if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
public void ForceKick(TSPlayer player, string reason)
{
Kick(player, reason, true, false, string.Empty);
}
#endif
/// <summary> /// <summary>
/// Kicks a player from the server without checking for immunetokick permission. /// Kicks a player from the server without checking for immunetokick permission.
/// </summary> /// </summary>
/// <param name="ply">int player</param> /// <param name="ply">int player</param>
/// <param name="reason">string reason</param> /// <param name="reason">string reason</param>
public void ForceKick(TSPlayer player, string reason) /// <param name="silent">bool silent (default: false)</param>
public void ForceKick(TSPlayer player, string reason, bool silent = false)
{ {
if (!player.ConnectionAlive) Kick(player, reason, true, silent);
return;
player.Disconnect(reason);
Log.ConsoleInfo(string.Format("{0} was force kicked for : {1}", player.IP, reason));
} }
public void ForceKick(TSPlayer player, string reason, bool silent) #if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
public bool Kick(TSPlayer player, string reason, string adminUserName)
{ {
player.SilentKickInProgress = true; return Kick(player, reason, false, false, adminUserName);
if (!player.ConnectionAlive)
return;
player.Disconnect(reason);
Log.ConsoleInfo(string.Format("{0} was force kicked for : {1}", player.IP, reason));
} }
#endif
/// <summary> /// <summary>
/// Kicks a player from the server. /// Kicks a player from the server.
/// </summary> /// </summary>
/// <param name="ply">int player</param> /// <param name="ply">int player</param>
/// <param name="reason">string reason</param> /// <param name="reason">string reason</param>
public bool Kick(TSPlayer player, string reason, string adminUserName = "") /// <param name="force">bool force (default: false)</param>
/// <param name="silent">bool silent (default: false)</param>
/// <param name="adminUserName">bool silent (default: null)</param>
public bool Kick(TSPlayer player, string reason, bool force = false, bool silent = false, string adminUserName = null)
{ {
if (!player.ConnectionAlive) if (!player.ConnectionAlive)
return true; return true;
if (!player.Group.HasPermission(Permissions.immunetokick)) if (force || !player.Group.HasPermission(Permissions.immunetokick))
{ {
string playerName = player.Name; string playerName = player.Name;
player.SilentKickInProgress = silent;
player.Disconnect(string.Format("Kicked: {0}", reason)); player.Disconnect(string.Format("Kicked: {0}", reason));
Log.ConsoleInfo(string.Format("Kicked {0} for : {1}", playerName, reason)); Log.ConsoleInfo(string.Format("Kicked {0} for : {1}", playerName, reason));
if (adminUserName.Length == 0) string verb = force ? "force " : "";
Broadcast(string.Format("{0} was kicked for {1}", playerName, reason.ToLower())); if (string.IsNullOrWhiteSpace(adminUserName))
Broadcast(string.Format("{0} was {1}kicked for {2}", playerName, verb, reason.ToLower()));
else else
Broadcast(string.Format("{0} kicked {1} for {2}", adminUserName, playerName, reason.ToLower())); Broadcast(string.Format("{0} {1}kicked {2} for {3}", adminUserName, verb, playerName, reason.ToLower()));
return true; return true;
} }
return false; return false;
} }
#if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
public bool Ban(TSPlayer player, string reason, string adminUserName)
{
return Ban(player, reason, false, adminUserName);
}
#endif
/// <summary> /// <summary>
/// Bans and kicks a player from the server. /// Bans and kicks a player from the server.
/// </summary> /// </summary>
/// <param name="ply">int player</param> /// <param name="ply">int player</param>
/// <param name="reason">string reason</param> /// <param name="reason">string reason</param>
public bool Ban(TSPlayer player, string reason, string adminUserName = "") /// <param name="force">bool force (default: false)</param>
/// <param name="adminUserName">bool silent (default: null)</param>
public bool Ban(TSPlayer player, string reason, bool force = false, string adminUserName = null)
{ {
if (!player.ConnectionAlive) if (!player.ConnectionAlive)
return true; return true;
if (!player.Group.HasPermission(Permissions.immunetoban)) if (force || !player.Group.HasPermission(Permissions.immunetoban))
{ {
string ip = player.IP; string ip = player.IP;
string playerName = player.Name; string playerName = player.Name;
TShock.Bans.AddBan(ip, playerName, reason); TShock.Bans.AddBan(ip, playerName, reason);
player.Disconnect(string.Format("Banned: {0}", reason)); player.Disconnect(string.Format("Banned: {0}", reason));
Log.ConsoleInfo(string.Format("Banned {0} for : {1}", playerName, reason)); Log.ConsoleInfo(string.Format("Banned {0} for : {1}", playerName, reason));
if (adminUserName.Length == 0) string verb = force ? "force " : "";
Broadcast(string.Format("{0} was banned for {1}", playerName, reason.ToLower())); if (string.IsNullOrWhiteSpace(adminUserName))
Broadcast(string.Format("{0} was {1}banned for {1}", playerName, verb, reason.ToLower()));
else else
Broadcast(string.Format("{0} banned {1} for {2}", adminUserName, playerName, reason.ToLower())); Broadcast(string.Format("{0} {1}banned {1} for {2}", adminUserName, verb, playerName, reason.ToLower()));
return true; return true;
} }
return false; return false;

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ClassLibrary1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Multiplay")]
[assembly: AssemblyProduct("ClassLibrary1")]
[assembly: AssemblyCopyright("Copyright © Multiplay 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c6aed7ee-6282-49a2-8177-b79cad20d6d3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.WebTesting;
using Microsoft.VisualStudio.TestTools.WebTesting.Rules;
using Rests;
namespace TshockRestTestPlugin
{
[DisplayName("JSON Status")]
[Description("Checks to see the that the JSON response has the specified status response")]
public class JsonValidateStatus : JsonValidate
{
public override void Validate(object sender, ValidationEventArgs e)
{
if (null != ValidateJson(sender, e))
e.IsValid = true;
}
}
[DisplayName("JSON Regexp Property")]
[Description("Checks to see the that the JSON response contains the specified property and is matches the specified regexp")]
public class JsonValidateRegexpProperty : JsonValidateProperty
{
// The name of the desired JSON property
[DisplayName("Regexp")]
[DefaultValue(true)]
public new bool UseRegularExpression { get { return base.UseRegularExpression; } set { base.UseRegularExpression = value; } }
}
[DisplayName("JSON Error")]
[Description("Checks to see the that the JSON response contains the specified error")]
public class JsonValidateError : JsonValidateProperty
{
// The status of the JSON request
[DisplayName("JSON Status")]
[DefaultValue("400")]
public new string JSonStatus { get { return base.JSonStatus; } set { base.JSonStatus = value; } }
// The name of the desired JSON property
[DisplayName("Property")]
[DefaultValue("error")]
public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } }
}
[DisplayName("JSON Missing Parameter")]
[Description("Checks to see the that the JSON response indicates a missing or invalid parameter")]
public class JsonValidateMissingParameter : JsonValidateError
{
// The value of the desired JSON property
[DisplayName("Missing Value")]
public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or empty {0} parameter", value); } }
}
[DisplayName("JSON Invalid Parameter")]
[Description("Checks to see the that the JSON response indicates a missing or invalid parameter")]
public class JsonValidateInvalidParameter : JsonValidateError
{
// The value of the desired JSON property
[DisplayName("Invalid Value")]
public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or invalid {0} parameter", value); } }
}
[DisplayName("JSON Response")]
[Description("Checks to see the that the JSON response contains the specified message")]
public class JsonValidateResponse : JsonValidateProperty
{
// The name of the desired JSON property
[DisplayName("Response")]
[DefaultValue("response")]
public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } }
}
[DisplayName("JSON Property")]
[Description("Checks to see the that the JSON response contains the specified property and is set to the specified value")]
public class JsonValidateProperty : JsonValidate
{
// The name of the desired JSON property
[DisplayName("Property")]
public string PropertyName { get; set; }
// The value of the desired JSON property
[DisplayName("Value")]
public string PropertyValue { get; set; }
// Is the value a regexp of the desired JSON property
[DisplayName("Regexp")]
[DefaultValue(false)]
public bool UseRegularExpression { get; set; }
public override void Validate(object sender, ValidationEventArgs e)
{
RestObject response = ValidateJson(sender, e);
if (null == response)
return;
if (null == response[PropertyName])
{
e.Message = String.Format("{0} Not Found", PropertyName);
e.IsValid = false;
return;
}
if (UseRegularExpression)
{
var re = new Regex(PropertyValue);
if (!re.IsMatch((string)response[PropertyName]))
{
e.Message = String.Format("{0} => '{1}' !~ '{2}'", PropertyName, response[PropertyName], PropertyValue);
e.IsValid = false;
return;
}
}
else
{
if (PropertyValue != (string)response[PropertyName])
{
e.Message = String.Format("{0} => '{1}' != '{2}'", PropertyName, response[PropertyName], PropertyValue);
e.IsValid = false;
return;
}
}
e.IsValid = true;
//e.WebTest.Context.Add(ContextParameterName, propertyValue);
}
}
[DisplayName("JSON Has Properties")]
[Description("Checks to see the that the JSON response contains the specified properties (comma seperated)")]
public class JsonHasProperties : JsonValidate
{
// The name of the desired JSON properties to check
[DisplayName("Properties")]
[Description("A comma seperated list of property names to check exist")]
public string PropertyNames { get; set; }
//---------------------------------------------------------------------
public override void Validate(object sender, ValidationEventArgs e)
{
RestObject response = ValidateJson(sender, e);
if (null == response)
return;
foreach (var p in PropertyNames.Split(','))
{
if (null == response[p])
{
e.Message = String.Format("'{0}' Not Found", p);
e.IsValid = false;
return;
}
}
e.IsValid = true;
//e.WebTest.Context.Add(ContextParameterName, propertyValue);
}
}
public abstract class JsonValidate : ValidationRule
{
// The status of the JSON request
[DisplayName("JSON Status")]
[DefaultValue("200")]
public string JSonStatus { get; set; }
public RestObject ValidateJson(object sender, ValidationEventArgs e)
{
if (string.IsNullOrWhiteSpace(e.Response.BodyString))
{
e.IsValid = false;
e.Message = String.Format("Empty or null response {0}", e.Response.StatusCode);
return null;
}
JavaScriptSerializer serialiser = new JavaScriptSerializer();
//dynamic data = serialiser.Deserialize<dynamic>(e.Response.BodyString);
RestObject response = serialiser.Deserialize<RestObject>(e.Response.BodyString);
if (JSonStatus != response.Status)
{
e.IsValid = false;
e.Message = String.Format("Response Status '{0}' not '{1}'", response.Status, JSonStatus);
return null;
}
return response;
}
}
}

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{F2FEDAFB-58DE-4611-9168-A86112C346C7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TshockRestTestPlugin</RootNamespace>
<AssemblyName>TshockRestTestPlugin</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.WebTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="TShockRestTestPlugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TShockAPI\TShockAPI.csproj">
<Project>{49606449-072B-4CF5-8088-AA49DA586694}</Project>
<Name>TShockAPI</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -61,10 +61,7 @@ namespace UnitTests
public void FindBanTest() public void FindBanTest()
{ {
Assert.IsNotNull(Bans.GetBanByIp("127.0.0.1")); Assert.IsNotNull(Bans.GetBanByIp("127.0.0.1"));
TShock.Config.EnableBanOnUsernames = true;
Assert.IsNotNull(Bans.GetBanByName("BanTest")); Assert.IsNotNull(Bans.GetBanByName("BanTest"));
TShock.Config.EnableBanOnUsernames = false;
Assert.IsNull(Bans.GetBanByName("BanTest"));
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -49,6 +49,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Microsoft.VisualStudio.QualityTools.WebTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Mono.Data.Sqlite"> <Reference Include="Mono.Data.Sqlite">
<HintPath>..\SqlBins\Mono.Data.Sqlite.dll</HintPath> <HintPath>..\SqlBins\Mono.Data.Sqlite.dll</HintPath>
</Reference> </Reference>
@ -87,6 +88,10 @@
<Project>{49606449-072B-4CF5-8088-AA49DA586694}</Project> <Project>{49606449-072B-4CF5-8088-AA49DA586694}</Project>
<Name>TShockAPI</Name> <Name>TShockAPI</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\TShockRestTestPlugin\TShockRestTestPlugin.csproj">
<Project>{F2FEDAFB-58DE-4611-9168-A86112C346C7}</Project>
<Name>TShockRestTestPlugin</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="BanManagerTest.orderedtest"> <None Include="BanManagerTest.orderedtest">
@ -102,6 +107,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Include="UnitTests.licenseheader" /> <None Include="UnitTests.licenseheader" />
<None Include="RestApiTests.webtest">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0"> <BootstrapperPackage Include=".NETFramework,Version=v4.0">

6
docs/generate.bat Normal file
View file

@ -0,0 +1,6 @@
@echo off
:main
echo Generating Pandoc docs
cd src
for %%F in (*.md) do pandoc %%F > %%F.html
pause

15
docs/readme.md Normal file
View file

@ -0,0 +1,15 @@
# Building
1. Install [Pandoc](http://johnmacfarlane.net/pandoc).
2. cd to docs/src
3. pandoc index.md -o ../index.html
4. pandoc permissions.md -o ../permissions.html
# Adding stuff
1. Everything is [Markdown](http://daringfireball.net/projects/markdown/)
2. If you add a new page, include [Markdown-CSS](https://github.com/clownfart/Markdown-CSS) for best results.
# Demo
You can access the docs at the [TShock Github Page](http://tshock.github.com/).

110
docs/src/config.md Normal file
View file

@ -0,0 +1,110 @@
<link href="https://raw.github.com/clownfart/Markdown-CSS/master/markdown.css" rel="stylesheet"></link>
# The config file
[Back to index](index.md.html)
----
Each TShock installation automatically generates a configuration file which can edit basic settings when the server starts.
The file "config.json" is located in the *tshock* folder, in the same directory as TerrariaServer.exe.
Being a JSON file, it is extremely critical that you edit the file in a standard text editor. Use [Notepad++](http://notepad-plus-plus.org/) to edit your file, or another text editor used for programming. Before restarting TShock, check the syntax of your file using [JSONLint](http://jsonlint.com/), and verify that it contains no errors.
An example configuration file is below.
{
"InvasionMultiplier": 1,
"DefaultMaximumSpawns": 0,
"DefaultSpawnRate": 600,
"ServerPort": 7777,
"EnableWhitelist": false,
"InfiniteInvasion": false,
"PvPMode": "normal",
"SpawnProtection": true,
"SpawnProtectionRadius": 5,
"MaxSlots": 8,
"RangeChecks": true,
"DisableBuild": false,
"SuperAdminChatRGB": [
255.0,
0.0,
0.0
],
"SuperAdminChatPrefix": "(Admin) ",
"SuperAdminChatSuffix": "",
"BackupInterval": 0,
"BackupKeepFor": 60,
"RememberLeavePos": false,
"HardcoreOnly": false,
"MediumcoreOnly": false,
"KickOnMediumcoreDeath": false,
"BanOnMediumcoreDeath": false,
"AutoSave": true,
"MaximumLoginAttempts": 3,
"RconPassword": "",
"RconPort": 7777,
"ServerName": "",
"MasterServer": "127.0.0.1",
"StorageType": "sqlite",
"MySqlHost": "localhost:3306",
"MySqlDbName": "",
"MySqlUsername": "",
"MySqlPassword": "",
"MediumcoreBanReason": "Death results in a ban",
"MediumcoreKickReason": "Death results in a kick",
"EnableDNSHostResolution": false,
"EnableIPBans": true,
"EnableBanOnUsernames": false,
"DefaultRegistrationGroupName": "default",
"DefaultGuestGroupName": "guest",
"DisableSpewLogs": true,
"HashAlgorithm": "sha512",
"BufferPackets": true,
"ServerFullReason": "Server is full",
"ServerFullNoReservedReason": "Server is full. No reserved slots open.",
"SaveWorldOnCrash": true,
"EnableGeoIP": false,
"EnableTokenEndpointAuthentication": false,
"ServerNickname": "TShock Server",
"RestApiEnabled": false,
"RestApiPort": 7878,
"DisableTombstones": true,
"DisplayIPToAdmins": false,
"EnableInsecureTileFixes": true,
"KickProxyUsers": true,
"DisableHardmode": false,
"DisableDungeonGuardian": false,
"ServerSideInventory": false,
"ServerSideInventorySave": 15,
"LogonDiscardThreshold": 250,
"DisablePlayerCountReporting": false,
"DisableClownBombs": false,
"DisableSnowBalls": false,
"ChatFormat": "{1}{2}{3}: {4}",
"ForceTime": "normal",
"TileKillThreshold": 60,
"TilePlaceThreshold": 20,
"TileLiquidThreshold": 15,
"ProjectileThreshold": 50,
"ProjIgnoreShrapnel": true,
"RequireLogin": true,
"DisableInvisPvP": false,
"MaxRangeForDisabled": 10,
"ServerPassword": "",
"RegionProtectChests": false,
"DisableLoginBeforeJoin": false,
"AllowRegisterAnyUsername": false,
"AllowLoginAnyUsername": true,
"MaxDamage": 175,
"MaxProjDamage": 175,
"IgnoreProjUpdate": false,
"IgnoreProjKill": false,
"IgnoreNoClip": false,
"AllowIce": true
}
In this file, if you wanted to change the maximum players to 64, you would edit that the file so that the line referring to max players looked like so:
"MaxSlots": 64,

43
docs/src/index.md Normal file
View file

@ -0,0 +1,43 @@
<link href="https://raw.github.com/clownfart/Markdown-CSS/master/markdown.css" rel="stylesheet"></link>
# TShock Downloaded Documentation
*Created for TShock version: 3.5.x.x*
*Last updated: 2/25/2012*
----
## Preface
Welcome to the official TShock for Terraria downloaded documentation. This guide will walk through the installation and basic configuration of your newly downloaded TShock server, and should provide a basic knowledge as to how to get help from outside resources if needed.
## Resources
* [The Confluence wiki](http://develop.tshock.co:8080/) contains the most up to date information compiled by the community members. If your question isn't answered here, you might find it there.
* [The forums](http://tshock.co/xf/) provide an excellent place to ask other TShock users and developers questions. Please refrain from making posts about questions that may be answered here, however.
* [Our Github page](http://github.com/TShock/TShock) is where you'll be able to find the source code and the bug tracker.
* [IRC](irc://irc.shankshock.com/terraria) is our IRC channel, if you prefer that medium for support.
* Lastly, we can be found in the "Nyx" channel on the Teamspeak 3 server: ts3.shankshock.com, port 9987.
----
## Table of contents
1. [Installation & basic usage](install.md.html)
2. [Permissions](perms.md.html)
3. [The config file](config.md.html)
4. [Credits](#Credits)
----
## Credits<a id="Credits"></a>
TShock wouldn't be possible without:
* [Xenon Servers](http://xns.cc/)
* [Kerplunc Gaming](http://kerpluncgaming.com/)
* [Multiplay UK](http://multiplay.co.uk/)
* [Atlassian](http://www.atlassian.com/)
* [Github](http://github.com/)
* You :)

35
docs/src/install.md Normal file
View file

@ -0,0 +1,35 @@
<link href="https://raw.github.com/clownfart/Markdown-CSS/master/markdown.css" rel="stylesheet"></link>
# Install instructions & basic usage
[Back to index](index.md.html)
----
1. Assuming you've extracted TShock, you're done as far as files go. Run the TerrariaServer.exe file in the folder you've extracted TShock into.
2. Check to verify that the server window states that some version of TerrariaShock is now running. If this is not the case, stop. Re-download all files and extract them to a new folder, being sure to keep the file structure in tact.
3. Select a world and port to start the server. *TShock now uses its configuration file to control the number of players on the server. You can edit this value in the configuration file, discussed later. If you are generating a new world, you may experience a significantly longer time as the world creates itself. This is normal.
4. Once the server is finished starting, you will be greeted with TShock's main console window. You will see a message in yellow that states "*To become superadmin, join the game and type /auth*" preceding a series of numbers. This set of numbers we will refer to as the "authcode" in succeeding steps.
5. Connect to the server. Your IP address is 127.0.0.1, and the port will by default be on what you entered in the server console.
6. Immediately chat the following: "**/auth [authcode]**". Replace [authcode] with the code given in the server console. Example: /auth 123456.
7. Next, we will create a user account that you can login to. In the game, chat the command "**/user add [username]:[password] superadmin**". Replace [username] and [password] respectively with your appropriate details. You should be able to remeber your password, and it shouldn't be easily guessed. From now on, the phrase "run the command" is synonymous with "chat in the game chat box". In addition, where brackets ([]) are, we will assume you will replace those brackets with information that you have created.
8. Assuming the last step was a success, login. Run the command "**/login [username] [password]**".
9. To finalize installation, run the command "**/auth-verify**". This will disable the authcode, enable any previously disabled functionality, and allow the server to be used in production.
----
### Basic Usage<a id="Basics"></a>
Now that TShock is running, you may be interested in a few other features prior to playing Terraria.
* You can add admins through two methods. If the user is already registered, you can use "**/user group [username] [group-to-change-to]**". By default, TShock comes with the "vip" group, the "trustedadmin" group, and the "newadmin" group. If the user has yet to register, you can use "**/user add [username]:[password] [group]**" to generate an account with elevated permissions for them.
* When you join the server and already have an account, the server will ask for your account password, even if the server has no password setup. In the event that you set a password on the server, unregistered users will be required to enter it. Users that already have an account must enter their own password.
* If a user wishes to change accounts but retain their group, a config option exists that will allow you to allow users to login to accounts with any username.
----
## Closing remarks<a id="Closing"></a>
Thanks for downloading TShock. Your continued support helps make TShock what it is today. We wouldn't be here without you.
From everyone at TShock, thank-you.

248
docs/src/permissions.md Normal file
View file

@ -0,0 +1,248 @@
<link href="https://raw.github.com/clownfart/Markdown-CSS/master/markdown.css" rel="stylesheet"></link>
# Permission Nodes
These are the permissions that TShock currently supports, with associated commands. [Back to permissions](perms.md.html)
----
## <a id="allowclientsideworldedit">allowclientsideworldedit</a>
**Description:** Allow unrestricted Send Tile Square usage, for client side world editing
**Commands:** None
## <a id="annoy">annoy</a>
**Description:** None
**Commands:** /annoy
## <a id="ban">ban</a>
**Description:** User can ban others
**Commands:** /ban /banip /unban /unbanip
## <a id="buff">buff</a>
**Description:** User can buff self
**Commands:** /buff
## <a id="buffplayer">buffplayer</a>
**Description:** User can buff other players
**Commands:** /gbuff(/buffplayer)
## <a id="butcher">butcher</a>
**Description:** User can kill all enemy npcs
**Commands:** /butcher
## <a id="bypassinventorychecks">bypassinventorychecks</a>
**Description:** Bypass Server Side Inventory checks
**Commands:** None
## <a id="canbuild">canbuild</a>
**Description:** Required to be able to build (modify tiles and liquid)
**Commands:** None
## <a id="canchangepassword">canchangepassword</a>
**Description:** User can change password in game
**Commands:** /password
## <a id="canlogin">canlogin</a>
**Description:** User can login in game
**Commands:** /login
## <a id="canpartychat">canpartychat</a>
**Description:** User can use party chat in game
**Commands:** /p
## <a id="canregister">canregister</a>
**Description:** User can register account in game
**Commands:** /register
## <a id="cantalkinthird">cantalkinthird</a>
**Description:** User can talk in third person
**Commands:** /me
## <a id="causeevents">causeevents</a>
**Description:** None
**Commands:** /dropmeteor /star /genore /fullmoon /bloodmoon /invade
## <a id="cfg">cfg</a>
**Description:** User can edit sevrer configurations
**Commands:** /setspawn /reload /serverpassword /save /settle /maxspawns /spawnrate /broadcast(/bc /say) /stats /world
## <a id="clearitems">clearitems</a>
**Description:** User can clear item drops.
**Commands:** /clear(/clearitems)
## <a id="converthardmode">converthardmode</a>
**Description:** User can convert hallow into corruption and vice-versa
**Commands:** /convertcorruption /converthallow /removespecial
## <a id="editspawn">editspawn</a>
**Description:** Allows you to edit the spawn
**Commands:** /antibuild /protectspawn
## <a id="grow">grow</a>
**Description:** None
**Commands:** /grow
## <a id="hardmode">hardmode</a>
**Description:** User can change hardmode state.
**Commands:** /hardmode /stophardmode(/disablehardmode)
## <a id="heal">heal</a>
**Description:** None
**Commands:** /heal
## <a id="ignoredamagecap">ignoredamagecap</a>
**Description:** Prevents your actions from being ignored if damage is too high
**Commands:** None
## <a id="ignorekilltiledetection">ignorekilltiledetection</a>
**Description:** Prevents you from being reverted by kill tile abuse detection
**Commands:** None
## <a id="ignoreliquidsetdetection">ignoreliquidsetdetection</a>
**Description:** Prevents you from being disabled by liquid set abuse detection
**Commands:** None
## <a id="ignorenoclipdetection">ignorenoclipdetection</a>
**Description:** Prevents you from being reverted by no clip detection
**Commands:** None
## <a id="ignoreplacetiledetection">ignoreplacetiledetection</a>
**Description:** Prevents you from being reverted by place tile abuse detection
**Commands:** None
## <a id="ignoreprojectiledetection">ignoreprojectiledetection</a>
**Description:** Prevents you from being disabled by liquid set abuse detection
**Commands:** None
## <a id="ignorestackhackdetection">ignorestackhackdetection</a>
**Description:** Prevents you from being disabled by stack hack detection
**Commands:** None
## <a id="ignorestathackdetection">ignorestathackdetection</a>
**Description:** Prevents you from being kicked by hacked health detection
**Commands:** None
## <a id="immunetoban">immunetoban</a>
**Description:** Prevents you from being banned
**Commands:** None
## <a id="immunetokick">immunetokick</a>
**Description:** Prevents you from being kicked
**Commands:** None
## <a id="item">item</a>
**Description:** User can spawn items
**Commands:** /item(/i) /give(/g)
## <a id="kick">kick</a>
**Description:** User can kick others
**Commands:** /kick
## <a id="kill">kill</a>
**Description:** None
**Commands:** /kill
## <a id="logs">logs</a>
**Description:** Specific log messages are sent to users with this permission
**Commands:** /displaylogs
## <a id="maintenance">maintenance</a>
**Description:** User is notified when an update is available
**Commands:** /clearbans /off(/exit) /restart /off-nosave(/exit-nosave) /checkupdates
## <a id="managegroup">managegroup</a>
**Description:** User can manage groups
**Commands:** /addgroup /delgroup /modgroup /group
## <a id="manageitem">manageitem</a>
**Description:** User can manage item bans
**Commands:** /additem(/banitem) /delitem(/unbanitem) /listitems(/listbanneditems) /additemgroup /delitemgroup
## <a id="manageregion">manageregion</a>
**Description:** User can edit regions
**Commands:** /region /debugreg
## <a id="managewarp">managewarp</a>
**Description:** User can manage warps
**Commands:** /setwarp /delwarp /hidewarp
## <a id="movenpc">movenpc</a>
**Description:** User can change the homes of NPCs.
**Commands:** None
## <a id="mute">mute</a>
**Description:** User can mute and unmute users
**Commands:** /mute(/unmute)
## <a id="pvpfun">pvpfun</a>
**Description:** None
**Commands:** /slap
## <a id="reservedslot">reservedslot</a>
**Description:** Allows you to bypass the max slots for up to 5 slots above your max
**Commands:** None
## <a id="rootonly">rootonly</a>
**Description:** Meant for super admins only
**Commands:** /user /userinfo(/ui) /auth-verify
## <a id="seeids">seeids</a>
**Description:** User can see the id of players with /who
**Commands:** None
## <a id="spawnboss">spawnboss</a>
**Description:** User can spawn bosses
**Commands:** /eater /eye /king /skeletron /wof(/wallofflesh) /twins /destroyer /skeletronp(/prime) /hardcore
## <a id="spawnmob">spawnmob</a>
**Description:** User can spawn npcs
**Commands:** /spawnmob(/sm)
## <a id="startinvasion">startinvasion</a>
**Description:** User can start invasions (Goblin/Snow Legion) using items
**Commands:** None
## <a id="summonboss">summonboss</a>
**Description:** User can summon bosses using items
**Commands:** None
## <a id="time">time</a>
**Description:** None
**Commands:** /time
## <a id="tp">tp</a>
**Description:** User can teleport
**Commands:** /home /spawn /tp
## <a id="tpall">tpall</a>
**Description:** Users can tp to anyone
**Commands:** None
## <a id="tpallow">tpallow</a>
**Description:** Users can stop people from TPing to them
**Commands:** /tpallow
## <a id="tphere">tphere</a>
**Description:** User can teleport people to them
**Commands:** /tphere /sendwarp(/sw)
## <a id="tphide">tphide</a>
**Description:** Users can tp to people without showing a notice
**Commands:** None
## <a id="usebanneditem">usebanneditem</a>
**Description:** Allows you to use banned items
**Commands:** None
## <a id="warp">warp</a>
**Description:** User can use warps
**Commands:** /warp
## <a id="whisper">whisper</a>
**Description:** User can whisper to others
**Commands:** /whisper(/w /tell) /reply(/r)
## <a id="whitelist">whitelist</a>
**Description:** User can modify the whitelist
**Commands:** /whitelist

17
docs/src/perms.md Normal file
View file

@ -0,0 +1,17 @@
<link href="https://raw.github.com/clownfart/Markdown-CSS/master/markdown.css" rel="stylesheet"></link>
# Permissions
[Back to index](index.md.html)
## Permissions<a id="Permissions"></a>
Like Bukkit and other administrative modifications, TShock supports adding groups and permissions. In the current implementation, you can only edit groups ingame, adding and removing them isn't supported *yet*.
## Adding permissions:
To add a permission to a given group, use the command "**/modgroup [add|del] [group] [permission]**". Example: */modgroup add trustedadmin tpall*.
## Permission nodes:
[A list of permission nodes can be found here.](permissions.md.html)

View file

@ -1,128 +0,0 @@
For the full list of changes, please take a look at GitHub:
https://github.com/TShock/TShock/commits/master
From now on, all release notes aren't put here. It's too much to track, but new features will be tossed here.
Changes in API release 3:
- Added support for SQLite
- Added support for MySQL
- Added /user command, supports adding users via in game command
- Added database editor for editing the MySQL + SQLite DB systems
- Fixed /region list and /warp list
- Fixed Jexius's font exploit
- Added /annoy
- Added canbuild permission
- Fixed mysterious chair rotation system
- Added /ip <player> to retrieve a player's IPv4 address
- Removed /buff
- Added command line paramater -worldpath, which changes the location where Terraria checks for worlds
- Fixed save world race conditions
- Added /login <username> <password>
- Fixed an instance where NPC.maxSpawns was incorrectly referenced in favor of NPC.defaultMaxSpawns
- Chests are ignored from kill tile abuse
- Added /reply (/r) to reply to a /whisper
- Fixed /broadcast spacing
- User names and passwords are now accepted. Passwords are hashed with SHA512
- Added MaximumLoginAttempts to configuration
- Added /tphere * and /tphere all
- Added /auth-verify to verify and turn off the auth code system
- Added the ability to Log/notify admins when commands are executed.
- Added a new Configuration Flag called "DisableBuild".
- Added command to toggle anti-build.
- Added -ip commandline
- Fixed hair exploit
- Added /rules, reads from ./tshock/rules.txt
- Added AdminChatPrefix configuration option.
- Added ForceKillAll to kick all players.
- Added support to spawn all types of slimes (have to use the full exact name)
- Added /king to spawn king slime.
- Adds protected regions. Use /region help ingame for extra help
- Added warps to tshock, edited some region commands
- Added the ability to ban specific items for being in inventory when joining server
- Added /setspawn command,Sets the spawn point of the server
- Added HardcoreOnly
- Sandgun and Dirt Rod no longer triggers a Impossible to place block.
- Added /displaylogs. Toggles log output to player who executed the command.
- Added -configpath command line parameter to set config path.
- Added broadcasting on map saves
- Added /tphere * and /tphere all
Still Lots More To Add! :)
Changes in API release 2.0.0.0:
- Added update checker.
Changes in API release 1.8.0.0:
- Added permissions system for managing different levels of admins
-- Added one time auth system for new permissions. When you start the server and load the map, you get a one time auth code. Use /auth <code> to become superadmin.
- Check the wiki on Github for more information on Permissions.
- Fixed BanExplosives not doing anything.
- All ban lists have been consolidated into one file, where reasons, IPs, and player names are stored together.
- Fixed spawnrate and max spawns
Changes in API release 1.6.0.0:
- Added spawn protection
- Fixed numerous bugs
- Added a few commands
Changes in API release 1.5.0.1:
- Fixed cheat detection
Changes in API release 1.5.0.0:
- Added /time
- Added /kill <player>
- Fixed /item
- Added /slap <player> [dmg]
- Added broadcast event for kill tile abuse
- Fixed teleport somewhat
- More cheat detection
- Extended new cheat protection to mana
- Update player exploit patched
- Fixed /spawn
- Made /invasion a toggle
Changes in API release 1.4.0.0:
- The configuration file is now located at config.json
- Something else.
Changes in API release 1.3.0.1:
- Re-coded the entire command system
- Re-coded the help command
- Implemented what seems to be the most recurring blacklist ever
Changes in API release 1.3.0.0:
- Added /maxspawns
- Added /spawnrate
- Resetup the configuration file to read spawn rate information.
- Patched the ability for clients to spawn NPCs
- Patched the ability for clients to rewrite the server through SendSection
- Make sure to use this Terraria.exe for the server: (http://dl.dropbox.com/u/29760911/Terraria.exe)
-- Allows spawn rates to be changed
Changes in API release 1.2.0.1:
- New update system
Changes in API release 1.2:
- Added /butcher
- Added /heal
- /spawnmob now takes another argument for the amount
- /item now adds items to the inventory directly
- This update credit to Deathmax
Changes in API release 1.1:
- Added /tp
- Added /tphere
- Added /spawnmob
- Added /item
- Fixed /spawn
- Updated /help
- Everything in this update credit to Deathmax, High, and Shank
Changes in API release 0.1:
- Added /save to save the world
- Added /spawn to teleport to spawn
- Added broken teleport stuff
- Major re-write of the anti-tnt code (now uses a blacklist instead of a whitelist)
- Fixed server crashing bug of the anti-tnt code
- Made the anti-tnt code decrease the threshold instantaniously
Re added the update checker.

View file

@ -1,3 +0,0 @@
Documentation, NPC lists, Spawn lists, and more can be found on GitHub at:
https://github.com/TShock/TShock/wiki

View file

@ -1,3 +0,0 @@
For installation instructions, please refer to:
https://github.com/TShock/TShock/wiki/Installation-instructions