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
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TShockRestTestPlugin", "TShockRestTestPlugin\TShockRestTestPlugin.csproj", "{F2FEDAFB-58DE-4611-9168-A86112C346C7}"
EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
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|Mixed Platforms.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1,100 +1,96 @@
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Threading;
using Terraria;
namespace TShockAPI
{
public class BackupManager
{
public string BackupPath { get; set; }
public int Interval { get; set; }
public int KeepFor { get; set; }
private DateTime lastbackup = DateTime.UtcNow;
public BackupManager(string path)
{
BackupPath = path;
}
public bool IsBackupTime
{
get { return (Interval > 0) && ((DateTime.UtcNow - lastbackup).TotalMinutes >= Interval); }
}
public void Backup()
{
lastbackup = DateTime.UtcNow;
ThreadPool.QueueUserWorkItem(DoBackup);
ThreadPool.QueueUserWorkItem(DeleteOld);
}
private void DoBackup(object o)
{
try
{
string worldname = Main.worldPathName;
string name = Path.GetFileName(worldname);
Main.worldPathName = Path.Combine(BackupPath, string.Format("{0}.{1:dd.MM.yy-HH.mm.ss}.bak", name, DateTime.UtcNow));
string worldpath = Path.GetDirectoryName(Main.worldPathName);
if (worldpath != null && !Directory.Exists(worldpath))
Directory.CreateDirectory(worldpath);
TShock.Utils.Broadcast("Server map saving, potential lag spike");
Console.WriteLine("Backing up world...");
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
SaveWorld.Start();
while (SaveWorld.ThreadState == ThreadState.Running)
Thread.Sleep(50);
Console.WriteLine("World backed up");
Console.ForegroundColor = ConsoleColor.Gray;
Log.Info(string.Format("World backed up ({0})", Main.worldPathName));
Main.worldPathName = worldname;
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Backup failed");
Console.ForegroundColor = ConsoleColor.Gray;
Log.Error("Backup failed");
Log.Error(ex.ToString());
}
}
private void DeleteOld(object o)
{
if (KeepFor <= 0)
return;
foreach (var fi in new DirectoryInfo(BackupPath).GetFiles("*.bak"))
{
if ((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalMinutes > KeepFor)
{
fi.Delete();
}
}
}
}
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Threading;
using Terraria;
namespace TShockAPI
{
public class BackupManager
{
public string BackupPath { get; set; }
public int Interval { get; set; }
public int KeepFor { get; set; }
private DateTime lastbackup = DateTime.UtcNow;
public BackupManager(string path)
{
BackupPath = path;
}
public bool IsBackupTime
{
get { return (Interval > 0) && ((DateTime.UtcNow - lastbackup).TotalMinutes >= Interval); }
}
public void Backup()
{
lastbackup = DateTime.UtcNow;
ThreadPool.QueueUserWorkItem(DoBackup);
ThreadPool.QueueUserWorkItem(DeleteOld);
}
private void DoBackup(object o)
{
try
{
string worldname = Main.worldPathName;
string name = Path.GetFileName(worldname);
Main.worldPathName = Path.Combine(BackupPath, string.Format("{0}.{1:dd.MM.yy-HH.mm.ss}.bak", name, DateTime.UtcNow));
string worldpath = Path.GetDirectoryName(Main.worldPathName);
if (worldpath != null && !Directory.Exists(worldpath))
Directory.CreateDirectory(worldpath);
TShock.Utils.Broadcast("Server map saving, potential lag spike");
Console.WriteLine("Backing up world...");
SaveManager.Instance.SaveWorld();
Console.WriteLine("World backed up");
Console.ForegroundColor = ConsoleColor.Gray;
Log.Info(string.Format("World backed up ({0})", Main.worldPathName));
Main.worldPathName = worldname;
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Backup failed");
Console.ForegroundColor = ConsoleColor.Gray;
Log.Error("Backup failed");
Log.Error(ex.ToString());
}
}
private void DeleteOld(object o)
{
if (KeepFor <= 0)
return;
foreach (var fi in new DirectoryInfo(BackupPath).GetFiles("*.bak"))
{
if ((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalMinutes > KeepFor)
{
fi.Delete();
}
}
}
}
}

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

@ -1,4 +1,4 @@
/*
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
@ -407,8 +407,18 @@ namespace TShockAPI
TShock.InventoryDB.InsertPlayerData(args.Player);
}
args.Player.SendMessage("Authenticated as " + user.Name + " successfully.", Color.LimeGreen);
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
{
args.Player.SendMessage("Incorrect password", Color.LimeGreen);
@ -574,7 +584,10 @@ namespace TShockAPI
else if (subcmd == "del" && args.Parameters.Count == 2)
{
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];
else
user.Name = args.Parameters[1];
@ -620,7 +633,11 @@ namespace TShockAPI
else if (subcmd == "group")
{
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];
else
user.Name = args.Parameters[1];
@ -769,7 +786,7 @@ namespace TShockAPI
string reason = args.Parameters.Count > 1
? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1))
: "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);
}
@ -816,7 +833,7 @@ namespace TShockAPI
string reason = args.Parameters.Count > 1
? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1))
: "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);
}
@ -860,25 +877,14 @@ namespace TShockAPI
var ban = TShock.Bans.GetBanByName(plStr);
if (ban != null)
{
if (TShock.Bans.RemoveBan(ban.IP))
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))
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
{
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;
}
string plStr = args.Parameters[0];
var ban = TShock.Bans.GetBanByIp(plStr);
var ip = args.Parameters[0];
var ban = TShock.Bans.GetBanByIp(ip);
if (ban != null)
{
if (TShock.Bans.RemoveBan(ban.IP))
@ -948,7 +954,7 @@ namespace TShockAPI
}
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!");
WorldGen.saveWorld();
Netplay.disconnect = true;
TShock.Utils.StopServer();
}
//Added restart command
private static void Restart(CommandArgs args)
{
if (Main.runningMono){
Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono");
}else{
if (TShock.Config.ServerSideInventory)
{
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
{
TShock.InventoryDB.InsertPlayerData(player);
}
}
}
//Added restart command
private static void Restart(CommandArgs args)
{
if (Main.runningMono)
{
Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono");
}
else
{
if (TShock.Config.ServerSideInventory)
{
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
{
TShock.InventoryDB.InsertPlayerData(player);
}
}
}
TShock.Utils.ForceKickAll("Server restarting!");
WorldGen.saveWorld();
Netplay.disconnect = true;
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Environment.Exit(0);
}}
TShock.Utils.StopServer();
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
Environment.Exit(0);
}
}
private static void OffNoSave(CommandArgs args)
{
TShock.Utils.ForceKickAll("Server shutting down!");
Netplay.disconnect = true;
TShock.Utils.StopServer(false);
}
private static void CheckUpdates(CommandArgs args)
@ -1085,7 +1090,7 @@ namespace TShockAPI
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);
}
else if (args.Parameters[0] == "cobalt")
@ -1112,7 +1117,34 @@ namespace TShockAPI
{
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
{
num = 2;
@ -1147,7 +1179,41 @@ namespace TShockAPI
num = 9;
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
{
num = 111;
@ -1165,11 +1231,11 @@ namespace TShockAPI
{
int i2 = WorldGen.genRand.Next(100, Main.maxTilesX - 100);
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;
}
if (num == 111)
if ((num == 111) || (num == 22) || (num == 68))
{
num6 = (Main.rockLayer + Main.rockLayer + (double)Main.maxTilesY) / 3.0;
}
@ -1544,6 +1610,8 @@ namespace TShockAPI
Main.tile[x, y].type = 2;
break;
case 32:
case 113:
case 110:
Main.tile[x, y].type = 0;
Main.tile[x, y].active = false;
break;
@ -1552,11 +1620,14 @@ namespace TShockAPI
break;
case 112:
case 116:
Main.tile[x, y].type = 169;
Main.tile[x, y].type = 53;
break;
case 113:
case 118:
Main.tile[x, y].type = 38;
break;
case 115:
Main.tile[x, y].type = 52;
break;
default:
continue;
}
@ -2186,15 +2257,13 @@ namespace TShockAPI
{
Main.spawnTileX = args.Player.TileX + 1;
Main.spawnTileY = args.Player.TileY + 3;
TShock.Utils.Broadcast("Server map saving, potential lag spike");
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
SaveWorld.Start();
SaveManager.Instance.SaveWorld(false);
}
private static void Reload(CommandArgs args)
{
FileTools.SetupConfig();
TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
TShock.Groups.LoadPermisions();
TShock.Regions.ReloadAllRegions();
args.Player.SendMessage(
@ -2215,9 +2284,7 @@ namespace TShockAPI
private static void Save(CommandArgs args)
{
TShock.Utils.Broadcast("Server map saving, potential lag spike");
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
SaveWorld.Start();
SaveManager.Instance.SaveWorld(false);
}
private static void Settle(CommandArgs args)

View file

@ -1,287 +1,287 @@
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace TShockAPI
{
public class ConfigFile
{
[Description(
"The equation for calculating invasion size is 100 + (multiplier * (number of active players with greater than 200 health))"
)] public int InvasionMultiplier = 1;
[Description("The default maximum mobs that will spawn per wave. Higher means more mobs in that wave.")] public int
DefaultMaximumSpawns = 5;
[Description("The delay between waves. Shorter values lead to less mobs.")] public int DefaultSpawnRate = 600;
[Description("The port the server runs on.")] public int ServerPort = 7777;
[Description("Enable or disable the whitelist based on IP addresses in whitelist.txt")] public bool EnableWhitelist;
[Description(
"Enable the ability for invaison size to never decrease. Make sure to run /invade, and note that this adds 2 million+ goblins to the spawn que for the map."
)] public bool InfiniteInvasion;
[Description("Set the server pvp mode. Vaild types are, \"normal\", \"always\", \"disabled\"")] public string PvPMode
= "normal";
[Description("Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.")] public bool
SpawnProtection = true;
[Description("Radius from spawn tile for SpawnProtection.")] public int SpawnProtectionRadius = 10;
[Description(
"Max slots for the server. If you want people to be kicked with \"Server is full\" set this to how many players you want max and then set Terraria max players to 2 higher."
)] public int MaxSlots = 8;
[Description("Global protection agent for any block distance based anti-grief check.")] public bool RangeChecks = true;
[Description("Disables any building; placing of blocks")] public bool DisableBuild;
[Description("#.#.#. = Red/Blue/Green - RGB Colors for the Admin Chat Color. Max value: 255")] public float[]
SuperAdminChatRGB = {255, 0, 0};
[Description("Super admin group chat prefix")] public string SuperAdminChatPrefix = "(Admin) ";
[Description("Super admin group chat suffix")] public string SuperAdminChatSuffix = "";
[Description(
"Backup frequency in minutes. So, a value of 60 = 60 minutes. Backups are stored in the \\tshock\\backups folder.")] public int BackupInterval;
[Description("How long backups are kept in minutes. 2880 = 2 days.")] public int BackupKeepFor = 60;
[Description(
"Remembers where a player left off. It works by remembering the IP, NOT the character. \neg. When you try to disconnect, and reconnect to be automatically placed at spawn, you'll be at your last location. Note: Won't save after server restarts."
)] public bool RememberLeavePos;
[Description("Hardcore players ONLY. This means softcore players cannot join.")] public bool HardcoreOnly;
[Description("Mediumcore players ONLY. This means softcore players cannot join.")] public bool MediumcoreOnly;
[Description("Kicks a Hardcore player on death.")] public bool KickOnMediumcoreDeath;
[Description("Bans a Hardcore player on death.")] public bool BanOnMediumcoreDeath;
[Description("Enable/Disable Terrarias built in auto save")] public bool AutoSave = true;
[Description("Number of failed login attempts before kicking the player.")] public int MaximumLoginAttempts = 3;
[Description("Not implemented")] public string RconPassword = "";
[Description("Not implemented")] public int RconPort = 7777;
[Description("Used when replying to a rest /status request.")] public string ServerName = "";
[Description("Not implemented")] public string MasterServer = "127.0.0.1";
[Description("Valid types are \"sqlite\" and \"mysql\"")] public string StorageType = "sqlite";
[Description("The MySQL Hostname and port to direct connections to")] public string MySqlHost = "localhost:3306";
[Description("Database name to connect to")] public string MySqlDbName = "";
[Description("Database username to connect with")] public string MySqlUsername = "";
[Description("Database password to connect with")] public string MySqlPassword = "";
[Description("Bans a Mediumcore player on death.")] public string MediumcoreBanReason = "Death results in a ban";
[Description("Kicks a Mediumcore player on death.")] public string MediumcoreKickReason = "Death results in a kick";
[Description("Enables DNS resolution of incoming connections with GetGroupForIPExpensive.")] public bool
EnableDNSHostResolution;
[Description("Enables kicking of banned users by matching their IP Address")] public bool EnableIPBans = true;
[Description("Enables kicking of banned users by matching their Character Name")] public bool EnableBanOnUsernames;
[Description("Selects the default group name to place new registrants under")] public string
DefaultRegistrationGroupName = "default";
[Description("Selects the default group name to place non registered users under")] public string
DefaultGuestGroupName = "guest";
[Description("Force-Disable printing logs to players with the log permission")] public bool DisableSpewLogs = true;
[Description("Valid types are \"sha512\", \"sha256\", \"md5\", append with \"-xp\" for the xp supported algorithms")] public string HashAlgorithm = "sha512";
[Description("Buffers up the packets and sends them out at the end of each frame")] public bool BufferPackets = true;
[Description("String that is used when kicking people when the server is full.")] public string ServerFullReason =
"Server is full";
[Description("String that is used when kicking people when the server is full with no reserved slots.")] public string
ServerFullNoReservedReason = "Server is full. No reserved slots open.";
[Description("This will save the world if Terraria crashes from an unhandled exception.")] public bool
SaveWorldOnCrash = true;
[Description("This will announce a player's location on join")] public bool EnableGeoIP;
[Description("This will turn on a token requirement for the /status API endpoint.")] public bool
EnableTokenEndpointAuthentication;
[Description("This is used when the API endpoint /status is queried.")] public string ServerNickname = "TShock Server";
[Description("Enable/Disable the rest api.")] public bool RestApiEnabled;
[Description("This is the port which the rest api will listen on.")] public int RestApiPort = 7878;
[Description("Disable tombstones for all players.")] public bool DisableTombstones = true;
[Description("Displays a player's IP on join to everyone who has the log permission")] public bool DisplayIPToAdmins;
[Description(
"Some tiles are 'fixed' by not letting TShock handle them. Disabling this may break certain asthetic tiles.")] public
bool EnableInsecureTileFixes = true;
[Description("Kicks users using a proxy as identified with the GeoIP database")] public bool KickProxyUsers = true;
[Description("Disables hardmode, can't never be activated. Overrides /starthardmode")] public bool DisableHardmode;
[Description("Disables Dungeon Guardian from being spawned by player packets, this will instead force a respawn")] public bool DisableDungeonGuardian;
[Description("Enable Server Side Inventory checks, EXPERIMENTAL")] public bool ServerSideInventory;
[Description("How often SSI should save, in minutes")] public int ServerSideInventorySave = 15;
[Description("Time, in milliseconds, to disallow discarding items after logging in when ServerSideInventory is ON")] public int LogonDiscardThreshold=250;
[Description("Disables reporting of playercount to the stat system.")] public bool DisablePlayerCountReporting;
[Description("Disables clown bomb projectiles from spawning")] public bool DisableClownBombs;
[Description("Disables snow ball projectiles from spawning")] public bool DisableSnowBalls;
[Description(
"Change ingame chat format, {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message"
)] public string ChatFormat = "{1}{2}{3}: {4}";
[Description("Force the world time to be normal, day, or night")] public string ForceTime = "normal";
[Description("Disable/Revert a player if they exceed this number of tile kills within 1 second.")] public int
TileKillThreshold = 60;
[Description("Disable/Revert a player if they exceed this number of tile places within 1 second.")] public int
TilePlaceThreshold = 20;
[Description("Disable a player if they exceed this number of liquid sets within 1 second.")] public int
TileLiquidThreshold = 15;
[Description("Disable a player if they exceed this number of projectile new within 1 second.")] public int
ProjectileThreshold = 50;
[Description("Ignore shrapnel from crystal bullets for Projectile Threshold.")] public bool
ProjIgnoreShrapnel = true;
[Description("Require all players to register or login before being allowed to play.")] public bool RequireLogin;
[Description(
"Disables Invisibility potions from being used in PvP (Note, they can use them on the client, but the effect isn't sent to the rest of the server)"
)] public bool DisableInvisPvP;
[Description("The maximum distance players disabled for various reasons can move from")] public int
MaxRangeForDisabled = 10;
[Description("Server password required to join server")] public string ServerPassword = "";
[Description("Protect chests with region and build permissions")] public bool RegionProtectChests;
[Description("Disable users from being able to login with account password when joining")] public bool
DisableLoginBeforeJoin;
[Description("Allows users to register any username with /register")] public bool AllowRegisterAnyUsername;
[Description("Allows users to login with any username with /login")] public bool AllowLoginAnyUsername = true;
[Description("The maximum damage a player/npc can inflict")] public int MaxDamage = 175;
[Description("The maximum damage a projectile can inflict")] public int MaxProjDamage = 175;
[Description("Ignores checking to see if player 'can' update a projectile")] public bool IgnoreProjUpdate = false;
[Description("Ignores checking to see if player 'can' kill a projectile")] public bool IgnoreProjKill = false;
[Description("Ignores all no clip checks for players")] public bool IgnoreNoClip = false;
[Description("Allow Ice placement even when user does not have canbuild")] public bool AllowIce = false;
public static ConfigFile Read(string path)
{
if (!File.Exists(path))
return new ConfigFile();
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return Read(fs);
}
}
public static ConfigFile Read(Stream stream)
{
using (var sr = new StreamReader(stream))
{
var cf = JsonConvert.DeserializeObject<ConfigFile>(sr.ReadToEnd());
if (ConfigRead != null)
ConfigRead(cf);
return cf;
}
}
public void Write(string path)
{
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write))
{
Write(fs);
}
}
public void Write(Stream stream)
{
var str = JsonConvert.SerializeObject(this, Formatting.Indented);
using (var sw = new StreamWriter(stream))
{
sw.Write(str);
}
}
public static Action<ConfigFile> ConfigRead;
public static void DumpDescriptions()
{
var sb = new StringBuilder();
var defaults = new ConfigFile();
foreach (var field in defaults.GetType().GetFields().OrderBy(f => f.Name))
{
if (field.IsStatic)
continue;
var name = field.Name;
var type = field.FieldType.Name;
var descattr =
field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute;
var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : "None";
var def = field.GetValue(defaults);
sb.AppendLine("## {0} ".SFormat(name));
sb.AppendLine("**Type:** {0} ".SFormat(type));
sb.AppendLine("**Description:** {0} ".SFormat(desc));
sb.AppendLine("**Default:** \"{0}\" ".SFormat(def));
sb.AppendLine();
}
File.WriteAllText("ConfigDescriptions.txt", sb.ToString());
}
}
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace TShockAPI
{
public class ConfigFile
{
[Description(
"The equation for calculating invasion size is 100 + (multiplier * (number of active players with greater than 200 health))"
)] public int InvasionMultiplier = 1;
[Description("The default maximum mobs that will spawn per wave. Higher means more mobs in that wave.")] public int
DefaultMaximumSpawns = 5;
[Description("The delay between waves. Shorter values lead to less mobs.")] public int DefaultSpawnRate = 600;
[Description("The port the server runs on.")] public int ServerPort = 7777;
[Description("Enable or disable the whitelist based on IP addresses in whitelist.txt")] public bool EnableWhitelist;
[Description(
"Enable the ability for invaison size to never decrease. Make sure to run /invade, and note that this adds 2 million+ goblins to the spawn que for the map."
)] public bool InfiniteInvasion;
[Description("Set the server pvp mode. Vaild types are, \"normal\", \"always\", \"disabled\"")] public string PvPMode
= "normal";
[Description("Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.")] public bool
SpawnProtection = true;
[Description("Radius from spawn tile for SpawnProtection.")] public int SpawnProtectionRadius = 10;
[Description(
"Max slots for the server. If you want people to be kicked with \"Server is full\" set this to how many players you want max and then set Terraria max players to 2 higher."
)] public int MaxSlots = 8;
[Description("Global protection agent for any block distance based anti-grief check.")] public bool RangeChecks = true;
[Description("Disables any building; placing of blocks")] public bool DisableBuild;
[Description("#.#.#. = Red/Blue/Green - RGB Colors for the Admin Chat Color. Max value: 255")] public float[]
SuperAdminChatRGB = {255, 0, 0};
[Description("Super admin group chat prefix")] public string SuperAdminChatPrefix = "(Admin) ";
[Description("Super admin group chat suffix")] public string SuperAdminChatSuffix = "";
[Description(
"Backup frequency in minutes. So, a value of 60 = 60 minutes. Backups are stored in the \\tshock\\backups folder.")] public int BackupInterval;
[Description("How long backups are kept in minutes. 2880 = 2 days.")] public int BackupKeepFor = 60;
[Description(
"Remembers where a player left off. It works by remembering the IP, NOT the character. \neg. When you try to disconnect, and reconnect to be automatically placed at spawn, you'll be at your last location. Note: Won't save after server restarts."
)] public bool RememberLeavePos;
[Description("Hardcore players ONLY. This means softcore players cannot join.")] public bool HardcoreOnly;
[Description("Mediumcore players ONLY. This means softcore players cannot join.")] public bool MediumcoreOnly;
[Description("Kicks a Hardcore player on death.")] public bool KickOnMediumcoreDeath;
[Description("Bans a Hardcore player on death.")] public bool BanOnMediumcoreDeath;
[Description("Enable/Disable Terrarias built in auto save")] public bool AutoSave = true;
[Description("Number of failed login attempts before kicking the player.")] public int MaximumLoginAttempts = 3;
[Description("Not implemented")] public string RconPassword = "";
[Description("Not implemented")] public int RconPort = 7777;
[Description("Used when replying to a rest /status request.")] public string ServerName = "";
[Description("Not implemented")] public string MasterServer = "127.0.0.1";
[Description("Valid types are \"sqlite\" and \"mysql\"")] public string StorageType = "sqlite";
[Description("The MySQL Hostname and port to direct connections to")] public string MySqlHost = "localhost:3306";
[Description("Database name to connect to")] public string MySqlDbName = "";
[Description("Database username to connect with")] public string MySqlUsername = "";
[Description("Database password to connect with")] public string MySqlPassword = "";
[Description("Bans a Mediumcore player on death.")] public string MediumcoreBanReason = "Death results in a ban";
[Description("Kicks a Mediumcore player on death.")] public string MediumcoreKickReason = "Death results in a kick";
[Description("Enables DNS resolution of incoming connections with GetGroupForIPExpensive.")] public bool
EnableDNSHostResolution;
[Description("Enables kicking of banned users by matching their IP Address")] public bool EnableIPBans = true;
[Description("Enables kicking of banned users by matching their Character Name")] public bool EnableBanOnUsernames;
[Description("Selects the default group name to place new registrants under")] public string
DefaultRegistrationGroupName = "default";
[Description("Selects the default group name to place non registered users under")] public string
DefaultGuestGroupName = "guest";
[Description("Force-Disable printing logs to players with the log permission")] public bool DisableSpewLogs = true;
[Description("Valid types are \"sha512\", \"sha256\", \"md5\", append with \"-xp\" for the xp supported algorithms")] public string HashAlgorithm = "sha512";
[Description("Buffers up the packets and sends them out at the end of each frame")] public bool BufferPackets = true;
[Description("String that is used when kicking people when the server is full.")] public string ServerFullReason =
"Server is full";
[Description("String that is used when kicking people when the server is full with no reserved slots.")] public string
ServerFullNoReservedReason = "Server is full. No reserved slots open.";
[Description("This will save the world if Terraria crashes from an unhandled exception.")] public bool
SaveWorldOnCrash = true;
[Description("This will announce a player's location on join")] public bool EnableGeoIP;
[Description("This will turn on a token requirement for the /status API endpoint.")] public bool
EnableTokenEndpointAuthentication;
[Description("This is used when the API endpoint /status is queried.")] public string ServerNickname = "TShock Server";
[Description("Enable/Disable the rest api.")] public bool RestApiEnabled;
[Description("This is the port which the rest api will listen on.")] public int RestApiPort = 7878;
[Description("Disable tombstones for all players.")] public bool DisableTombstones = true;
[Description("Displays a player's IP on join to everyone who has the log permission")] public bool DisplayIPToAdmins;
[Description(
"Some tiles are 'fixed' by not letting TShock handle them. Disabling this may break certain asthetic tiles.")] public
bool EnableInsecureTileFixes = true;
[Description("Kicks users using a proxy as identified with the GeoIP database")] public bool KickProxyUsers = true;
[Description("Disables hardmode, can't never be activated. Overrides /starthardmode")] public bool DisableHardmode;
[Description("Disables Dungeon Guardian from being spawned by player packets, this will instead force a respawn")] public bool DisableDungeonGuardian;
[Description("Enable Server Side Inventory checks, EXPERIMENTAL")] public bool ServerSideInventory;
[Description("How often SSI should save, in minutes")] public int ServerSideInventorySave = 15;
[Description("Time, in milliseconds, to disallow discarding items after logging in when ServerSideInventory is ON")] public int LogonDiscardThreshold=250;
[Description("Disables reporting of playercount to the stat system.")] public bool DisablePlayerCountReporting;
[Description("Disables clown bomb projectiles from spawning")] public bool DisableClownBombs;
[Description("Disables snow ball projectiles from spawning")] public bool DisableSnowBalls;
[Description(
"Change ingame chat format, {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message"
)] public string ChatFormat = "{1}{2}{3}: {4}";
[Description("Force the world time to be normal, day, or night")] public string ForceTime = "normal";
[Description("Disable/Revert a player if they exceed this number of tile kills within 1 second.")] public int
TileKillThreshold = 60;
[Description("Disable/Revert a player if they exceed this number of tile places within 1 second.")] public int
TilePlaceThreshold = 20;
[Description("Disable a player if they exceed this number of liquid sets within 1 second.")] public int
TileLiquidThreshold = 15;
[Description("Disable a player if they exceed this number of projectile new within 1 second.")] public int
ProjectileThreshold = 50;
[Description("Ignore shrapnel from crystal bullets for Projectile Threshold.")] public bool
ProjIgnoreShrapnel = true;
[Description("Require all players to register or login before being allowed to play.")] public bool RequireLogin;
[Description(
"Disables Invisibility potions from being used in PvP (Note, they can use them on the client, but the effect isn't sent to the rest of the server)"
)] public bool DisableInvisPvP;
[Description("The maximum distance players disabled for various reasons can move from")] public int
MaxRangeForDisabled = 10;
[Description("Server password required to join server")] public string ServerPassword = "";
[Description("Protect chests with region and build permissions")] public bool RegionProtectChests;
[Description("Disable users from being able to login with account password when joining")] public bool
DisableLoginBeforeJoin;
[Description("Allows users to register any username with /register")] public bool AllowRegisterAnyUsername;
[Description("Allows users to login with any username with /login")] public bool AllowLoginAnyUsername = true;
[Description("The maximum damage a player/npc can inflict")] public int MaxDamage = 175;
[Description("The maximum damage a projectile can inflict")] public int MaxProjDamage = 175;
[Description("Ignores checking to see if player 'can' update a projectile")] public bool IgnoreProjUpdate = false;
[Description("Ignores checking to see if player 'can' kill a projectile")] public bool IgnoreProjKill = false;
[Description("Ignores all no clip checks for players")] public bool IgnoreNoClip = false;
[Description("Allow Ice placement even when user does not have canbuild")] public bool AllowIce = false;
public static ConfigFile Read(string path)
{
if (!File.Exists(path))
return new ConfigFile();
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return Read(fs);
}
}
public static ConfigFile Read(Stream stream)
{
using (var sr = new StreamReader(stream))
{
var cf = JsonConvert.DeserializeObject<ConfigFile>(sr.ReadToEnd());
if (ConfigRead != null)
ConfigRead(cf);
return cf;
}
}
public void Write(string path)
{
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write))
{
Write(fs);
}
}
public void Write(Stream stream)
{
var str = JsonConvert.SerializeObject(this, Formatting.Indented);
using (var sw = new StreamWriter(stream))
{
sw.Write(str);
}
}
public static Action<ConfigFile> ConfigRead;
public static void DumpDescriptions()
{
var sb = new StringBuilder();
var defaults = new ConfigFile();
foreach (var field in defaults.GetType().GetFields().OrderBy(f => f.Name))
{
if (field.IsStatic)
continue;
var name = field.Name;
var type = field.FieldType.Name;
var descattr =
field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute;
var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : "None";
var def = field.GetValue(defaults);
sb.AppendLine("## {0} ".SFormat(name));
sb.AppendLine("**Type:** {0} ".SFormat(type));
sb.AppendLine("**Description:** {0} ".SFormat(desc));
sb.AppendLine("**Default:** \"{0}\" ".SFormat(def));
sb.AppendLine();
}
File.WriteAllText("ConfigDescriptions.txt", sb.ToString());
}
}
}

View file

@ -1,4 +1,4 @@
/*
/*
TShock, a server mod for Terraria
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/>.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using MySql.Data.MySqlClient;
@ -39,15 +40,15 @@ namespace TShockAPI.DB
db.GetSqlType() == SqlType.Sqlite
? (IQueryBuilder) new SqliteQueryCreator()
: new MysqlQueryCreator());
try{
creator.EnsureExists(table);
}
catch (DllNotFoundException ex)
{
System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?");
throw new Exception("Could not find a database library (probably Sqlite3.dll)");
}
try
{
creator.EnsureExists(table);
}
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)");
}
}
public Ban GetBanByIp(string ip)
@ -67,12 +68,30 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)");
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)
{
if (!TShock.Config.EnableBanOnUsernames)
{
return null;
}
try
{
var namecol = casesensitive ? "Name" : "UPPER(Name)";
@ -91,7 +110,14 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)");
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
{
@ -99,19 +125,34 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)");
}
catch (Exception ex)
{
if (exceptions)
throw ex;
Log.Error(ex.ToString());
}
return false;
}
#if COMPAT_SIGS
[Obsolete("This method is for signature compatibility for external code only")]
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
{
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)
{
if (exceptions)
throw ex;
Log.Error(ex.ToString());
}
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/>.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
@ -24,11 +25,10 @@ using MySql.Data.MySqlClient;
namespace TShockAPI.DB
{
public class GroupManager
public class GroupManager : IEnumerable<Group>
{
private IDbConnection database;
public List<Group> groups = new List<Group>();
public readonly List<Group> groups = new List<Group>();
public GroupManager(IDbConnection db)
{
@ -48,14 +48,23 @@ namespace TShockAPI.DB
: new MysqlQueryCreator());
creator.EnsureExists(table);
//Add default groups
AddGroup("guest", "canbuild,canregister,canlogin,canpartychat,cantalkinthird");
AddGroup("default", "guest", "warp,canchangepassword");
AddGroup("newadmin", "default", "kick,editspawn,reservedslot");
AddGroup("admin", "newadmin",
// Load Permissions from the DB
LoadPermisions();
// Add default groups if they don't exist
AddDefaultGroup("guest", "", "canbuild,canregister,canlogin,canpartychat,cantalkinthird");
AddDefaultGroup("default", "guest", "warp,canchangepassword");
AddDefaultGroup("newadmin", "default", "kick,editspawn,reservedslot");
AddDefaultGroup("admin", "newadmin",
"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");
AddGroup("vip", "default", "reservedslot");
AddDefaultGroup("trustedadmin", "admin", "maintenance,cfg,butcher,item,heal,immunetoban,usebanneditem,manageusers");
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")
return true;
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>
/// Adds group with name and permissions if it does not exist.
/// </summary>
/// <param name="name">name of group</param>
/// <param name="parentname">parent of group</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 (exceptions)
throw new GroupExistsException(name);
return "Error: Group already exists. Use /modGroup to change permissions.";
}
var group = new Group(name, null, chatcolor);
group.permissions.Add(permissions);
group.Permissions = permissions;
if (!string.IsNullOrWhiteSpace(parentname))
{
var parent = groups.FirstOrDefault(gp => gp.Name == parentname);
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);
return error;
}
@ -98,81 +129,136 @@ namespace TShockAPI.DB
? "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";
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 message;
return "";
}
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)
{
String message = "";
return DeleteGroup(name, false);
}
#endif
public String DeleteGroup(String name, bool exceptions = false)
{
if (!GroupExists(name))
{
if (exceptions)
throw new GroupNotExistException(name);
return "Error: Group doesn't exists.";
}
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)
{
String message = "";
if (!GroupExists(name))
return "Error: Group doesn't exists.";
var group = TShock.Utils.GetGroup(name);
//Add existing permissions (without duplicating)
permissions.AddRange(group.permissions.Where(s => !permissions.Contains(s)));
var oldperms = group.Permissions; // Store old permissions in case of error
permissions.ForEach(p => group.AddPermission(p));
if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", group.Permissions, name) == 1)
return "Group " + name + " has been modified successfully.";
if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", String.Join(",", permissions), name) != 0)
{
message = "Group " + name + " has been modified successfully.";
group.SetPermission(permissions);
}
return message;
// Restore old permissions so DB and internal object are in a consistent state
group.Permissions = oldperms;
return "";
}
public String DeletePermissions(String name, List<String> permissions)
{
String message = "";
if (!GroupExists(name))
return "Error: Group doesn't exists.";
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.
var newperms = group.permissions.Except(permissions);
if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", String.Join(",", newperms), name) != 0)
{
message = "Group " + name + " has been modified successfully.";
group.SetPermission(newperms.ToList());
}
return message;
if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", group.Permissions, name) == 1)
return "Group " + name + " has been modified successfully.";
// Restore old permissions so DB and internal object are in a consistent state
group.Permissions = oldperms;
return "";
}
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>();
tempgroups.Add(new SuperAdminGroup());
if (groups == null || groups.Count < 2)
groups = tempgroups;
{
groups.Clear();
groups.AddRange(tempgroups);
}
try
{
@ -181,34 +267,9 @@ namespace TShockAPI.DB
{
while (reader.Read())
{
string groupname = reader.Get<String>("GroupName");
var group = new Group(groupname);
var group = new Group(reader.Get<String>("GroupName"), null, reader.Get<String>("ChatColor"), reader.Get<String>("Commands"));
group.Prefix = reader.Get<String>("Prefix");
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")));
}
}
@ -222,7 +283,7 @@ namespace TShockAPI.DB
var parent = groupsparents.FirstOrDefault(gp => gp.Item1.Name == parentname);
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;
}
group.Parent = parent.Item1;
@ -230,8 +291,8 @@ namespace TShockAPI.DB
tempgroups.Add(group);
}
groups = tempgroups;
groups.Clear();
groups.AddRange(tempgroups);
}
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 ReadColumn(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 =
table.Columns.Select(
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.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();
/// <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)
public override string RenameTable(string from, string to)
{
var rstr = rand.NextString(20);
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);
return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
}
private static readonly Dictionary<MySqlDbType, string> TypesAsStrings = new Dictionary<MySqlDbType, string>
@ -189,137 +73,32 @@ namespace TShockAPI.DB
return ret;
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 =
table.Columns.Select(
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" : ""));
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
? ", UNIQUE({0})".SFormat(string.Join(", ", uniques))
: "");
}
private static Random rand = new Random();
/// <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)
public override string RenameTable(string from, string to)
{
var rstr = rand.NextString(20);
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);
return "RENAME TABLE {0} TO {1}".SFormat(from, to);
}
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) : "");
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
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.Data;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using MySql.Data.MySqlClient;
using System.Text.RegularExpressions;
namespace TShockAPI.DB
{
@ -50,20 +53,25 @@ namespace TShockAPI.DB
/// <param name="user">User user</param>
public void AddUser(User user)
{
if (!TShock.Groups.GroupExists(user.Group))
throw new GroupNotExistsException(user.Group);
int ret;
try
{
if (!TShock.Groups.GroupExists(user.Group))
throw new GroupNotExistsException(user.Group);
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);
ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name,
TShock.Utils.HashPassword(user.Password), user.Group, user.Address);
}
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>
@ -123,11 +131,18 @@ namespace TShockAPI.DB
{
try
{
if (!TShock.Groups.GroupExists(group))
Group grp = TShock.Groups.GetGroupByName(group);
if (null == grp)
throw new GroupNotExistsException(group);
if (database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0)
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)
{
@ -239,37 +254,83 @@ namespace TShockAPI.DB
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
{
QueryResult result;
if (string.IsNullOrEmpty(user.Address))
using (var result = database.QueryReader(query, arg))
{
result = database.QueryReader("SELECT * FROM Users WHERE Username=@0", user.Name);
}
else
{
result = database.QueryReader("SELECT * FROM Users WHERE IP=@0", user.Address);
}
using (var reader = result)
{
if (reader.Read())
if (result.Read())
{
user.ID = reader.Get<int>("ID");
user.Group = reader.Get<string>("Usergroup");
user.Password = reader.Get<string>("Password");
user.Name = reader.Get<string>("Username");
user.Address = reader.Get<string>("IP");
return user;
user = LoadUserFromResult(user, result);
// Check for multiple matches
if (!result.Read())
return user;
multiple = true;
}
}
}
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);
}
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

View file

@ -1,119 +1,119 @@
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
namespace TShockAPI
{
public class FileTools
{
internal static string RulesPath
{
get { return Path.Combine(TShock.SavePath, "rules.txt"); }
}
internal static string MotdPath
{
get { return Path.Combine(TShock.SavePath, "motd.txt"); }
}
internal static string WhitelistPath
{
get { return Path.Combine(TShock.SavePath, "whitelist.txt"); }
}
internal static string RememberedPosPath
{
get { return Path.Combine(TShock.SavePath, "oldpos.xml"); }
}
internal static string ConfigPath
{
get { return Path.Combine(TShock.SavePath, "config.json"); }
}
public static void CreateFile(string file)
{
File.Create(file).Close();
}
public static void CreateIfNot(string file, string data = "")
{
if (!File.Exists(file))
{
File.WriteAllText(file, data);
}
}
/// <summary>
/// Sets up the configuration file for all variables, and creates any missing files.
/// </summary>
public static void SetupConfig()
{
if (!Directory.Exists(TShock.SavePath))
{
Directory.CreateDirectory(TShock.SavePath);
}
CreateIfNot(RulesPath, "Respect the admins!\nDon't use TNT!");
CreateIfNot(MotdPath,
"This server is running TShock for Terraria.\n Type /help for a list of commands.\n%255,000,000%Current map: %map%\nCurrent players: %players%");
CreateIfNot(WhitelistPath);
if (File.Exists(ConfigPath))
{
TShock.Config = ConfigFile.Read(ConfigPath);
// Add all the missing config properties in the json file
}
TShock.Config.Write(ConfigPath);
}
/// <summary>
/// Tells if a user is on the whitelist
/// </summary>
/// <param name="ip">string ip of the user</param>
/// <returns>true/false</returns>
public static bool OnWhitelist(string ip)
{
if (!TShock.Config.EnableWhitelist)
{
return true;
}
CreateIfNot(WhitelistPath, "127.0.0.1");
using (var tr = new StreamReader(WhitelistPath))
{
string whitelist = tr.ReadToEnd();
ip = TShock.Utils.GetRealIP(ip);
bool contains = whitelist.Contains(ip);
if (!contains)
{
foreach (var line in whitelist.Split(Environment.NewLine.ToCharArray()))
{
if (string.IsNullOrWhiteSpace(line))
continue;
contains = TShock.Utils.GetIPv4Address(line).Equals(ip);
if (contains)
return true;
}
return false;
}
return true;
}
}
}
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
namespace TShockAPI
{
public class FileTools
{
internal static string RulesPath
{
get { return Path.Combine(TShock.SavePath, "rules.txt"); }
}
internal static string MotdPath
{
get { return Path.Combine(TShock.SavePath, "motd.txt"); }
}
internal static string WhitelistPath
{
get { return Path.Combine(TShock.SavePath, "whitelist.txt"); }
}
internal static string RememberedPosPath
{
get { return Path.Combine(TShock.SavePath, "oldpos.xml"); }
}
internal static string ConfigPath
{
get { return Path.Combine(TShock.SavePath, "config.json"); }
}
public static void CreateFile(string file)
{
File.Create(file).Close();
}
public static void CreateIfNot(string file, string data = "")
{
if (!File.Exists(file))
{
File.WriteAllText(file, data);
}
}
/// <summary>
/// Sets up the configuration file for all variables, and creates any missing files.
/// </summary>
public static void SetupConfig()
{
if (!Directory.Exists(TShock.SavePath))
{
Directory.CreateDirectory(TShock.SavePath);
}
CreateIfNot(RulesPath, "Respect the admins!\nDon't use TNT!");
CreateIfNot(MotdPath,
"This server is running TShock for Terraria.\n Type /help for a list of commands.\n%255,000,000%Current map: %map%\nCurrent players: %players%");
CreateIfNot(WhitelistPath);
if (File.Exists(ConfigPath))
{
TShock.Config = ConfigFile.Read(ConfigPath);
// Add all the missing config properties in the json file
}
TShock.Config.Write(ConfigPath);
}
/// <summary>
/// Tells if a user is on the whitelist
/// </summary>
/// <param name="ip">string ip of the user</param>
/// <returns>true/false</returns>
public static bool OnWhitelist(string ip)
{
if (!TShock.Config.EnableWhitelist)
{
return true;
}
CreateIfNot(WhitelistPath, "127.0.0.1");
using (var tr = new StreamReader(WhitelistPath))
{
string whitelist = tr.ReadToEnd();
ip = TShock.Utils.GetRealIP(ip);
bool contains = whitelist.Contains(ip);
if (!contains)
{
foreach (var line in whitelist.Split(Environment.NewLine.ToCharArray()))
{
if (string.IsNullOrWhiteSpace(line))
continue;
contains = TShock.Utils.GetIPv4Address(line).Equals(ip);
if (contains)
return true;
}
return false;
}
return true;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,106 +1,211 @@
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
namespace TShockAPI
{
public class Group
{
public readonly List<string> permissions = new List<string>();
public readonly List<string> negatedpermissions = new List<string>();
public string Name { get; set; }
public Group Parent { get; set; }
public int Order { get; set; }
public string Prefix { get; set; }
public string Suffix { get; set; }
public byte R = 255;
public byte G = 255;
public byte B = 255;
public Group(string groupname, Group parentgroup = null, string chatcolor = "255,255,255")
{
Name = groupname;
Parent = parentgroup;
byte.TryParse(chatcolor.Split(',')[0], out R);
byte.TryParse(chatcolor.Split(',')[1], out G);
byte.TryParse(chatcolor.Split(',')[2], out B);
}
public virtual bool HasPermission(string permission)
{
var cur = this;
var traversed = new List<Group>();
while (cur != null)
{
if (string.IsNullOrEmpty(permission))
return true;
if (cur.negatedpermissions.Contains(permission))
return false;
if (cur.permissions.Contains(permission))
return true;
if (traversed.Contains(cur))
{
throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name));
}
traversed.Add(cur);
cur = cur.Parent;
}
return false;
}
public void NegatePermission(string permission)
{
negatedpermissions.Add(permission);
}
public void AddPermission(string permission)
{
permissions.Add(permission);
}
public void SetPermission(List<string> permission)
{
permissions.Clear();
foreach (string s in permission)
{
permissions.Add(s);
}
}
}
public class SuperAdminGroup : Group
{
public SuperAdminGroup()
: base("superadmin")
{
R = (byte) TShock.Config.SuperAdminChatRGB[0];
G = (byte) TShock.Config.SuperAdminChatRGB[1];
B = (byte) TShock.Config.SuperAdminChatRGB[2];
Prefix = TShock.Config.SuperAdminChatPrefix;
Suffix = TShock.Config.SuperAdminChatSuffix;
}
public override bool HasPermission(string permission)
{
return true;
}
}
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Linq;
using System.Collections.Generic;
namespace TShockAPI
{
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> negatedpermissions = new List<string>();
public string Name { get; set; }
public Group Parent { get; set; }
public int Order { get; set; }
public string Prefix { 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 G = 255;
public byte B = 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;
Parent = parentgroup;
ChatColor = chatcolor;
Permissions = permissions;
}
public virtual bool HasPermission(string permission)
{
if (string.IsNullOrEmpty(permission))
return true;
var cur = this;
var traversed = new List<Group>();
while (cur != null)
{
if (cur.negatedpermissions.Contains(permission))
return false;
if (cur.permissions.Contains(permission))
return true;
if (traversed.Contains(cur))
{
throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name));
}
traversed.Add(cur);
cur = cur.Parent;
}
return false;
}
public void NegatePermission(string 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)
{
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)
{
permissions.Clear();
negatedpermissions.Clear();
permission.ForEach(p => AddPermission(p));
}
public void RemovePermission(string permission)
{
if (permission.StartsWith("!"))
{
negatedpermissions.Remove(permission.Substring(1));
return;
}
permissions.Remove(permission);
}
}
public class SuperAdminGroup : Group
{
public SuperAdminGroup()
: base("superadmin")
{
R = (byte) TShock.Config.SuperAdminChatRGB[0];
G = (byte) TShock.Config.SuperAdminChatRGB[1];
B = (byte) TShock.Config.SuperAdminChatRGB[2];
Prefix = TShock.Config.SuperAdminChatPrefix;
Suffix = TShock.Config.SuperAdminChatSuffix;
}
public override bool HasPermission(string permission)
{
return true;
}
}
}

View file

@ -200,7 +200,7 @@ namespace TShockAPI
c =>
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("**Commands:** {0} ".SFormat(strs.Count() > 0 ? string.Join(" ", strs) : "None"));
sb.AppendLine();

View file

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

View file

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

View file

@ -21,6 +21,7 @@ using System.ComponentModel;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using HttpServer;
using HttpServer.Headers;
using Newtonsoft.Json;
@ -41,6 +42,7 @@ namespace Rests
{
private readonly List<RestCommand> commands = new List<RestCommand>();
private HttpListener listener;
private StringHeader serverHeader;
public IPAddress Ip { get; set; }
public int Port { get; set; }
@ -48,6 +50,9 @@ namespace Rests
{
Ip = ip;
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()
@ -117,13 +122,14 @@ namespace Rests
throw new NullReferenceException("obj");
if (OnRestRequestCall(e))
return;
return;
var str = JsonConvert.SerializeObject(obj, Formatting.Indented);
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.Status = HttpStatusCode.OK;
return;
}
protected virtual object ProcessRequest(object sender, RequestEventArgs e)

View file

@ -1,4 +1,4 @@
/*
/*
TShock, a server mod for Terraria
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
Copyright (C) 2011 The TShock Team
@ -41,7 +41,14 @@ namespace Rests
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;
}

View file

@ -1,4 +1,4 @@
/*
/*
TShock, a server mod for Terraria
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);
}
}
}
}

File diff suppressed because it is too large Load diff

2949
TShockAPI/TShock.cs Normal file → Executable file

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,102 +1,102 @@
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using Newtonsoft.Json;
namespace TShockAPI
{
internal class UpdateManager
{
private static string updateUrl = "http://shankshock.com/tshock-update.json";
public static DateTime lastcheck = DateTime.MinValue;
/// <summary>
/// Check once every X minutes.
/// </summary>
private static readonly int CheckXMinutes = 30;
public static void UpdateProcedureCheck()
{
if ((DateTime.Now - lastcheck).TotalMinutes >= CheckXMinutes)
{
ThreadPool.QueueUserWorkItem(CheckUpdate);
lastcheck = DateTime.Now;
}
}
public static void CheckUpdate(object o)
{
var updates = ServerIsOutOfDate();
if (updates != null)
{
NotifyAdministrators(updates);
}
}
/// <summary>
/// Checks to see if the server is out of date.
/// </summary>
/// <returns></returns>
private static Dictionary<string, string> ServerIsOutOfDate()
{
using (var client = new WebClient())
{
client.Headers.Add("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705;)");
try
{
string updatejson = client.DownloadString(updateUrl);
var update = JsonConvert.DeserializeObject<Dictionary<string, string>>(updatejson);
var version = new Version(update["version"]);
if (TShock.VersionNum.CompareTo(version) < 0)
return update;
}
catch (Exception e)
{
Log.Error(e.ToString());
}
return null;
}
}
private static void NotifyAdministrators(Dictionary<string, string> update)
{
var changes = update["changes"].Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
NotifyAdministrator(TSPlayer.Server, changes);
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player.Active && player.Group.HasPermission(Permissions.maintenance))
{
NotifyAdministrator(player, changes);
}
}
}
private static void NotifyAdministrator(TSPlayer player, string[] changes)
{
player.SendMessage("The server is out of date.", Color.Red);
for (int j = 0; j < changes.Length; j++)
{
player.SendMessage(changes[j], Color.Red);
}
}
}
/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using Newtonsoft.Json;
namespace TShockAPI
{
internal class UpdateManager
{
private static string updateUrl = "http://shankshock.com/tshock-update.json";
public static DateTime lastcheck = DateTime.MinValue;
/// <summary>
/// Check once every X minutes.
/// </summary>
private static readonly int CheckXMinutes = 30;
public static void UpdateProcedureCheck()
{
if ((DateTime.Now - lastcheck).TotalMinutes >= CheckXMinutes)
{
ThreadPool.QueueUserWorkItem(CheckUpdate);
lastcheck = DateTime.Now;
}
}
public static void CheckUpdate(object o)
{
var updates = ServerIsOutOfDate();
if (updates != null)
{
NotifyAdministrators(updates);
}
}
/// <summary>
/// Checks to see if the server is out of date.
/// </summary>
/// <returns></returns>
private static Dictionary<string, string> ServerIsOutOfDate()
{
using (var client = new WebClient())
{
client.Headers.Add("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705;)");
try
{
string updatejson = client.DownloadString(updateUrl);
var update = JsonConvert.DeserializeObject<Dictionary<string, string>>(updatejson);
var version = new Version(update["version"]);
if (TShock.VersionNum.CompareTo(version) < 0)
return update;
}
catch (Exception e)
{
Log.Error(e.ToString());
}
return null;
}
}
private static void NotifyAdministrators(Dictionary<string, string> update)
{
var changes = update["changes"].Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
NotifyAdministrator(TSPlayer.Server, changes);
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player.Active && player.Group.HasPermission(Permissions.maintenance))
{
NotifyAdministrator(player, changes);
}
}
}
private static void NotifyAdministrator(TSPlayer player, string[] changes)
{
player.SendMessage("The server is out of date.", Color.Red);
for (int j = 0; j < changes.Length; j++)
{
player.SendMessage(changes[j], Color.Red);
}
}
}
}

File diff suppressed because it is too large Load diff

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()
{
Assert.IsNotNull(Bans.GetBanByIp("127.0.0.1"));
TShock.Config.EnableBanOnUsernames = true;
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>
<ItemGroup>
<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">
<HintPath>..\SqlBins\Mono.Data.Sqlite.dll</HintPath>
</Reference>
@ -87,6 +88,10 @@
<Project>{49606449-072B-4CF5-8088-AA49DA586694}</Project>
<Name>TShockAPI</Name>
</ProjectReference>
<ProjectReference Include="..\TShockRestTestPlugin\TShockRestTestPlugin.csproj">
<Project>{F2FEDAFB-58DE-4611-9168-A86112C346C7}</Project>
<Name>TShockRestTestPlugin</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="BanManagerTest.orderedtest">
@ -102,6 +107,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="UnitTests.licenseheader" />
<None Include="RestApiTests.webtest">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<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