Merge branch 'general-devel' of github.com:TShock/TShock

Conflicts:
	TShockAPI/Properties/AssemblyInfo.cs
	TShockAPI/Utils.cs
This commit is contained in:
Zidonuke 2011-12-29 13:51:14 -05:00
commit 1247b8760d
30 changed files with 1324 additions and 410 deletions

View file

@ -66,6 +66,7 @@ namespace TShockAPI
TShock.Utils.Broadcast("Server map saving, potential lag spike");
Console.WriteLine("Backing up world...");
Thread SaveWorld = new Thread(TShock.Utils.SaveWorld);
SaveWorld.Start();

View file

@ -120,8 +120,8 @@ namespace TShockAPI
add(Permissions.ban, UnBanIP, "unbanip");
add(Permissions.maintenance, ClearBans, "clearbans");
add(Permissions.whitelist, Whitelist, "whitelist");
add(Permissions.maintenance, Off, "off");
add(Permissions.maintenance, OffNoSave, "off-nosave");
add(Permissions.maintenance, Off, "off", "exit");
add(Permissions.maintenance, OffNoSave, "off-nosave", "exit-nosave");
add(Permissions.maintenance, CheckUpdates, "checkupdates");
add(Permissions.causeevents, DropMeteor, "dropmeteor");
add(Permissions.causeevents, Star, "star");
@ -167,19 +167,20 @@ namespace TShockAPI
add(Permissions.manageregion, Region, "region");
add(Permissions.manageregion, DebugRegions, "debugreg");
add(null, Help, "help");
add(null, Playing, "playing", "online", "who");
add(null, Playing, "playing", "online", "who", "version");
add(null, AuthToken, "auth");
add(null, ThirdPerson, "me");
add(null, PartyChat, "p");
add(Permissions.cantalkinthird, ThirdPerson, "me");
add(Permissions.canpartychat, PartyChat, "p");
add(null, Motd, "motd");
add(null, Rules, "rules");
add(Permissions.mute, Mute, "mute", "unmute");
add(Permissions.logs, DisplayLogs, "displaylogs");
ChatCommands.Add(new Command(PasswordUser, "password") { DoLog = false });
ChatCommands.Add(new Command(RegisterUser, "register") { DoLog = false });
ChatCommands.Add(new Command(Permissions.canchangepassword, PasswordUser, "password") { DoLog = false });
ChatCommands.Add(new Command(Permissions.canregister, RegisterUser, "register") { DoLog = false });
ChatCommands.Add(new Command(Permissions.rootonly, ManageUsers, "user") { DoLog = false });
add(Permissions.rootonly, GrabUserUserInfo, "userinfo", "ui");
add(Permissions.rootonly, AuthVerify, "auth-verify");
ChatCommands.Add(new Command(AttemptLogin, "login") { DoLog = false });
ChatCommands.Add(new Command(Permissions.canlogin, AttemptLogin, "login") { DoLog = false });
add(Permissions.cfg, Broadcast, "broadcast", "bc");
add(Permissions.whisper, Whisper, "whisper", "w", "tell");
add(Permissions.whisper, Reply, "reply", "r");
@ -215,7 +216,10 @@ namespace TShockAPI
Command cmd = ChatCommands.FirstOrDefault(c => c.HasAlias(cmdName));
if (cmd == null)
return false;
{
player.SendMessage("Invalid Command Entered. Type /help for a list of valid Commands.", Color.Red);
return true;
}
if (!cmd.CanRun(player))
{
@ -344,12 +348,21 @@ namespace TShockAPI
if (TShock.Config.ServerSideInventory)
{
if (!TShock.CheckInventory(args.Player))
if (args.Player.Group.HasPermission(Permissions.bypassinventorychecks))
{
args.Player.SendMessage("Login Failed, Please fix the above errors then log back in.", Color.Cyan);
args.Player.IgnoreActionsForInventory = false;
args.Player.IgnoreActionsForClearingTrashCan = false;
}
else if (!TShock.CheckInventory(args.Player))
{
args.Player.SendMessage("Login Failed, Please fix the above errors then /login again.", Color.Cyan);
args.Player.IgnoreActionsForClearingTrashCan = true;
return;
}
}
if (args.Player.Group.HasPermission(Permissions.ignorestackhackdetection))
args.Player.IgnoreActionsForCheating = "none";
args.Player.Group = TShock.Utils.GetGroup(user.Group);
args.Player.UserAccountName = args.Parameters[0];
@ -358,7 +371,7 @@ namespace TShockAPI
args.Player.IgnoreActionsForInventory = false;
args.Player.PlayerData.CopyInventory(args.Player);
TShock.InventoryDB.InsertPlayerData(args.Player, args.Player.UserID);
TShock.InventoryDB.InsertPlayerData(args.Player);
args.Player.SendMessage("Authenticated as " + args.Parameters[0] + " successfully.", Color.LimeGreen);
Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + args.Parameters[0]);
@ -414,29 +427,37 @@ namespace TShockAPI
{
try
{
if (args.Parameters.Count == 2)
var user = new User();
if (args.Parameters.Count == 1)
{
user.Name = args.Player.Name;
user.Password = args.Parameters[0];
}
else if (args.Parameters.Count == 2 && TShock.Config.AllowRegisterAnyUsername)
{
var user = new User();
user.Name = args.Parameters[0];
user.Password = args.Parameters[1];
user.Group = TShock.Config.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB.
if (TShock.Users.GetUserByName(user.Name) == null) // Cheap way of checking for existance of a user
{
args.Player.SendMessage("Account " + user.Name + " has been registered.", Color.Green);
TShock.Users.AddUser(user);
Log.ConsoleInfo(args.Player.Name + " registered an Account: " + user.Name);
}
else
{
args.Player.SendMessage("Account " + user.Name + " has already been registered.", Color.Green);
Log.ConsoleInfo(args.Player.Name + " failed to register an existing Account: " + user.Name);
}
}
else
{
args.Player.SendMessage("Invalid syntax! Proper syntax: /register <username> <password>", Color.Red);
args.Player.SendMessage("Invalid syntax! Proper syntax: /register <password>", Color.Red);
return;
}
user.Group = TShock.Config.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB.
if (TShock.Users.GetUserByName(user.Name) == null) // Cheap way of checking for existance of a user
{
args.Player.SendMessage("Account " + user.Name + " has been registered.", Color.Green);
args.Player.SendMessage("Your password is " + user.Password);
TShock.Users.AddUser(user);
Log.ConsoleInfo(args.Player.Name + " registered an Account: " + user.Name);
}
else
{
args.Player.SendMessage("Account " + user.Name + " has already been registered.", Color.Green);
Log.ConsoleInfo(args.Player.Name + " failed to register an existing Account: " + user.Name);
}
}
catch (UserManagerException ex)
@ -1249,12 +1270,20 @@ namespace TShockAPI
{
switch (Main.tile[x, y].type)
{
case 22:
case 25:
Main.tile[x, y].type = 117;
break;
case 23:
Main.tile[x, y].type = 109;
break;
case 32:
Main.tile[x, y].type = 0;
Main.tile[x, y].active = false;
break;
case 24:
Main.tile[x, y].type = 110;
break;
case 112:
Main.tile[x, y].type = 116;
break;
@ -1762,7 +1791,7 @@ namespace TShockAPI
return;
}
string passwd = args.Parameters[0];
Netplay.password = passwd;
TShock.Config.ServerPassword = passwd;
args.Player.SendMessage(string.Format("Server password changed to: {0}", passwd));
}
@ -1971,7 +2000,7 @@ namespace TShockAPI
var width = Math.Abs(args.Player.TempPoints[0].X - args.Player.TempPoints[1].X);
var height = Math.Abs(args.Player.TempPoints[0].Y - args.Player.TempPoints[1].Y);
if (TShock.Regions.AddRegion(x, y, width, height, regionName, Main.worldID.ToString()))
if (TShock.Regions.AddRegion(x, y, width, height, regionName, args.Player.UserAccountName, Main.worldID.ToString()))
{
args.Player.TempPoints[0] = Point.Zero;
args.Player.TempPoints[1] = Point.Zero;
@ -2107,8 +2136,78 @@ namespace TShockAPI
}
}
else
args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow [name] [region]", Color.Red);
args.Player.SendMessage("Invalid syntax! Proper syntax: /region remove [name] [region]", Color.Red);
break;
case "allowg":
{
if (args.Parameters.Count > 2)
{
string group = args.Parameters[1];
string regionName = "";
for (int i = 2; i < args.Parameters.Count; i++)
{
if (regionName == "")
{
regionName = args.Parameters[2];
}
else
{
regionName = regionName + " " + args.Parameters[i];
}
}
if (TShock.Groups.GroupExists(group))
{
if (TShock.Regions.AllowGroup(regionName, group))
{
args.Player.SendMessage("Added group " + group + " to " + regionName, Color.Yellow);
}
else
args.Player.SendMessage("Region " + regionName + " not found", Color.Red);
}
else
{
args.Player.SendMessage("Group " + group + " not found", Color.Red);
}
}
else
args.Player.SendMessage("Invalid syntax! Proper syntax: /region allow [group] [region]", Color.Red);
break;
}
case "removeg":
if (args.Parameters.Count > 2)
{
string group = args.Parameters[1];
string regionName = "";
for (int i = 2; i < args.Parameters.Count; i++)
{
if (regionName == "")
{
regionName = args.Parameters[2];
}
else
{
regionName = regionName + " " + args.Parameters[i];
}
}
if (TShock.Groups.GroupExists(group))
{
if (TShock.Regions.RemoveGroup(regionName, group))
{
args.Player.SendMessage("Removed group " + group + " from " + regionName, Color.Yellow);
}
else
args.Player.SendMessage("Region " + regionName + " not found", Color.Red);
}
else
{
args.Player.SendMessage("Group " + group + " not found", Color.Red);
}
}
else
args.Player.SendMessage("Invalid syntax! Proper syntax: /region removeg [group] [region]", Color.Red);
break;
case "list":
{
//How many regions per page
@ -2314,6 +2413,7 @@ namespace TShockAPI
private static void Playing(CommandArgs args)
{
args.Player.SendMessage(string.Format("Current players: {0}.", TShock.Utils.GetPlayers()), 255, 240, 20);
args.Player.SendMessage(string.Format("TShock: {0} ({1}): ({2}/{3})", TShock.VersionNum, TShock.VersionCodename, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots));
}
private static void AuthToken(CommandArgs args)
@ -2393,7 +2493,10 @@ namespace TShockAPI
args.Player.SendMessage("Invalid syntax! Proper syntax: /me <text>", Color.Red);
return;
}
TShock.Utils.Broadcast(string.Format("*{0} {1}", args.Player.Name, String.Join(" ", args.Parameters)), 205, 133, 63);
if (args.Player.mute)
args.Player.SendMessage("You are muted.");
else
TShock.Utils.Broadcast(string.Format("*{0} {1}", args.Player.Name, String.Join(" ", args.Parameters)), 205, 133, 63);
}
private static void PartyChat(CommandArgs args)
@ -2404,7 +2507,10 @@ namespace TShockAPI
return;
}
int playerTeam = args.Player.Team;
if (playerTeam != 0)
if (args.Player.mute)
args.Player.SendMessage("You are muted.");
else if (playerTeam != 0)
{
string msg = string.Format("<{0}> {1}", args.Player.Name, String.Join(" ", args.Parameters));
foreach (TSPlayer player in TShock.Players)
@ -2414,11 +2520,42 @@ namespace TShockAPI
}
}
else
{
args.Player.SendMessage("You are not in a party!", 255, 240, 20);
}
}
private static void Mute(CommandArgs args)
{
if (args.Parameters.Count < 1)
{
args.Player.SendMessage("Invalid syntax! Proper syntax: /mute <player> ", Color.Red);
return;
}
string plStr = String.Join(" ", args.Parameters);
var players = TShock.Utils.FindPlayer(plStr);
if (players.Count == 0)
args.Player.SendMessage("Invalid player!", Color.Red);
else if (players.Count > 1)
args.Player.SendMessage("More than one player matched!", Color.Red);
else if (players[0].mute && !players[0].Group.HasPermission(Permissions.mute))
{
var plr = players[0];
plr.mute = false;
plr.SendMessage("You have been unmuted.");
TShock.Utils.Broadcast(plr.Name + " has been unmuted by " + args.Player.Name, Color.Yellow);
}
else if (!players[0].Group.HasPermission(Permissions.mute))
{
var plr = players[0];
plr.mute = true;
plr.SendMessage("You have been muted.");
TShock.Utils.Broadcast(plr.Name + " has been muted by " + args.Player.Name, Color.Yellow);
}
else
args.Player.SendMessage("You cannot mute this player.");
}
private static void Motd(CommandArgs args)
{
TShock.Utils.ShowFileToUser(args.Player, "motd.txt");
@ -2446,6 +2583,8 @@ namespace TShockAPI
{
args.Player.SendMessage("More than one player matched!", Color.Red);
}
else if (args.Player.mute)
args.Player.SendMessage("You are muted.");
else
{
var plr = players[0];
@ -2459,7 +2598,9 @@ namespace TShockAPI
private static void Reply(CommandArgs args)
{
if (args.Player.LastWhisper != null)
if (args.Player.mute)
args.Player.SendMessage("You are muted.");
else if (args.Player.LastWhisper != null)
{
var msg = string.Join(" ", args.Parameters);
args.Player.LastWhisper.SendMessage("(Whisper From)" + "<" + args.Player.Name + ">" + msg, Color.MediumPurple);

View file

@ -16,7 +16,6 @@ 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.ComponentModel;
using System.IO;
using System.Linq;
@ -39,8 +38,8 @@ namespace TShockAPI
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("Enable or disable perma pvp.")]
public bool AlwaysPvP = false;
[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.")]
@ -51,14 +50,12 @@ namespace TShockAPI
public bool RangeChecks = true;
[Description("Disables any building; placing of blocks")]
public bool DisableBuild;
[Description("Kick a player if they exceed this number of tile kills within 1 second.")]
public int TileThreshold = 120;
[Description("#.#.#. = Red/Blue/Green - RGB Colors for the Admin Chat Color. Max value: 255")]
public float[] SuperAdminChatRGB = { 255, 0, 0 };
[Description("The Chat Prefix before an admin speaks. eg. *The prefix was set to \"(Admin) \", so.. (Admin) : Hi! Note: If you put a space after the prefix, it will look like this: (Admin) <TerrariaDude): Hi!")]
public string AdminChatPrefix = "(Admin) ";
[Description("")]
public bool AdminChatEnabled = true;
[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.")]
@ -94,28 +91,39 @@ namespace TShockAPI
[Description("Valid types are \"sqlite\" and \"mysql\"")]
public string StorageType = "sqlite";
[Description("")]
[Description("The MySQL Hostname and port to direct connections to")]
public string MySqlHost = "localhost:3306";
[Description("")]
[Description("Database name to connect to")]
public string MySqlDbName = "";
[Description("")]
[Description("Database username to connect with")]
public string MySqlUsername = "";
[Description("")]
[Description("Database password to connect with")]
public string MySqlPassword = "";
[Description("")]
public string MediumcoreBanReason = "Death results in a ban";
[Description("")]
public string MediumcoreKickReason = "Death results in a kick";
[Description("")]
public bool EnableDNSHostResolution;
[Description("")]
public bool EnableBanOnUsernames;
[Description("")]
public bool EnableAntiLag = true;
[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("Drops excessive sync packets")]
public bool EnableAntiLag = true;
[Description("Selects the default group name to place new registrants under")]
public string DefaultRegistrationGroupName = "default";
[Description("")]
[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")]
@ -124,12 +132,12 @@ namespace TShockAPI
[Description("Buffers up the packets and sends them out at the end of each frame")]
public bool BufferPackets = true;
[Description("Display the users group when they chat.")]
public bool ChatDisplayGroup = false;
[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;
@ -160,12 +168,6 @@ namespace TShockAPI
[Description("Kicks users using a proxy as identified with the GeoIP database")]
public bool KickProxyUsers = true;
[Description("Kicks banned users by their name")]
public bool EnableNameBans = false;
[Description("Kicks banned users by their IP")]
public bool EnableIPBans = true;
[Description("Disables hardmode, can't never be activated. Overrides /starthardmode")]
public bool DisableHardmode = false;
@ -175,7 +177,55 @@ namespace TShockAPI
[Description("Enable Server Side Inventory checks, EXPERIMENTAL")]
public bool ServerSideInventory = false;
public static ConfigFile Read(string path)
[Description("Disables reporting of playercount to the stat system.")]
public bool DisablePlayerCountReporting = false;
[Description("Disables clown bomb projectiles from spawning")]
public bool DisableClownBombs = false;
[Description("Disables snow ball projectiles from spawning")]
public bool DisableSnowBalls = false;
[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("Require all players to register or login before being allowed to play.")]
public bool RequireLogin = false;
[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 = false;
[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 = false;
[Description("Disable users from being able to login with account password when joining")]
public bool DisableLoginBeforeJoin = false;
[Description("Allows users to register any username with /register")]
public bool AllowRegisterAnyUsername = false;
public static ConfigFile Read(string path)
{
if (!File.Exists(path))
return new ConfigFile();

View file

@ -21,13 +21,16 @@ namespace TShockAPI.DB
new SqlColumn("GroupName", MySqlDbType.VarChar, 32) { Primary = true },
new SqlColumn("Parent", MySqlDbType.VarChar, 32),
new SqlColumn("Commands", MySqlDbType.Text),
new SqlColumn("ChatColor", MySqlDbType.Text)
new SqlColumn("ChatColor", MySqlDbType.Text),
new SqlColumn("Prefix", MySqlDbType.Text),
new SqlColumn("Suffix", MySqlDbType.Text)
);
var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder)new SqliteQueryCreator() : new MysqlQueryCreator());
creator.EnsureExists(table);
//Add default groups
AddGroup("default", "warp,canbuild");
AddGroup("guest", "canbuild,canregister,canlogin,canpartychat,cantalkinthird");
AddGroup("default", "guest", "warp,canchangepassword");
AddGroup("newadmin", "default", "kick,editspawn,reservedslot");
AddGroup("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");
@ -199,6 +202,9 @@ namespace TShockAPI.DB
string groupname = reader.Get<String>("GroupName");
var group = new Group(groupname);
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)

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;

View file

@ -18,9 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Data;
using MySql.Data.MySqlClient;
using Terraria;
namespace TShockAPI.DB
{
@ -54,7 +52,6 @@ namespace TShockAPI.DB
{
playerData.exists = true;
playerData.maxHealth = reader.Get<int>("MaxHealth");
playerData.maxMana = reader.Get<int>("MaxMana");
playerData.inventory = NetItem.Parse(reader.Get<string>("Inventory"));
return playerData;
}
@ -68,15 +65,18 @@ namespace TShockAPI.DB
return playerData;
}
public bool InsertPlayerData(TSPlayer player, int acctid)
public bool InsertPlayerData(TSPlayer player)
{
PlayerData playerData = player.PlayerData;
if (!GetPlayerData(player, acctid).exists)
if (!player.IsLoggedIn)
return false;
if (!GetPlayerData(player, player.UserID).exists)
{
try
{
database.Query("INSERT INTO Inventory (Account, MaxHealth, MaxMana, Inventory) VALUES (@0, @1, @2, @3);", acctid, playerData.maxHealth, playerData.maxMana, NetItem.ToString(playerData.inventory));
database.Query("INSERT INTO Inventory (Account, MaxHealth, Inventory) VALUES (@0, @1, @2);", player.UserID, playerData.maxHealth, NetItem.ToString(playerData.inventory));
return true;
}
catch (Exception ex)
@ -88,7 +88,7 @@ namespace TShockAPI.DB
{
try
{
database.Query("UPDATE Inventory SET MaxHealth = @0, MaxMana = @1, Inventory = @2 WHERE Account = @3;", playerData.maxHealth, playerData.maxMana, NetItem.ToString(playerData.inventory), acctid);
database.Query("UPDATE Inventory SET MaxHealth = @0, Inventory = @1 WHERE Account = @2;", playerData.maxHealth, NetItem.ToString(playerData.inventory), player.UserID);
return true;
}
catch (Exception ex)

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using MySql.Data.MySqlClient;
namespace TShockAPI.DB
@ -9,14 +10,15 @@ namespace TShockAPI.DB
public class ItemManager
{
private IDbConnection database;
public List<string> ItemBans = new List<string>();
public List<ItemBan> ItemBans = new List<ItemBan>();
public ItemManager(IDbConnection db)
{
database = db;
var table = new SqlTable("ItemBans",
new SqlColumn("ItemName", MySqlDbType.VarChar, 50) { Primary = true }
new SqlColumn("ItemName", MySqlDbType.VarChar, 50) { Primary = true },
new SqlColumn("AllowedGroups", MySqlDbType.Text )
);
var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder)new SqliteQueryCreator() : new MysqlQueryCreator());
creator.EnsureExists(table);
@ -33,13 +35,13 @@ namespace TShockAPI.DB
{
string query = (TShock.Config.StorageType.ToLower() == "sqlite") ?
"INSERT OR IGNORE INTO 'ItemBans' (ItemName) VALUES (@0);" :
"INSERT IGNORE INTO ItemBans SET ItemName=@0;";
"INSERT OR IGNORE INTO 'ItemBans' (ItemName, AllowedGroups) VALUES (@0, @1);" :
"INSERT IGNORE INTO ItemBans SET ItemName=@0,AllowedGroups=@1 ;";
int id = 0;
int.TryParse(line, out id);
database.Query(query, TShock.Utils.GetItemById(id).name);
database.Query(query, TShock.Utils.GetItemById(id).name, "");
}
}
}
@ -62,17 +64,22 @@ namespace TShockAPI.DB
using (var reader = database.QueryReader("SELECT * FROM ItemBans"))
{
while (reader != null && reader.Read())
ItemBans.Add(reader.Get<string>("ItemName"));
while (reader != null && reader.Read())
{
ItemBan ban = new ItemBan(reader.Get<string>("ItemName"));
ban.SetAllowedGroups( reader.Get<string>("AllowedGroups") );
ItemBans.Add(ban);
}
}
}
public void AddNewBan(string itemname = "")
{
try
{
database.Query("INSERT INTO ItemBans (ItemName) VALUES (@0);", TShock.Utils.GetItemByName(itemname)[0].name);
if (!ItemIsBanned(itemname))
ItemBans.Add(itemname);
database.Query("INSERT INTO ItemBans (ItemName, AllowedGroups) VALUES (@0, @1);", TShock.Utils.GetItemByName(itemname)[0].name, "");
if (!ItemIsBanned(itemname, null))
ItemBans.Add( new ItemBan(itemname) );
}
catch (Exception ex)
{
@ -82,12 +89,12 @@ namespace TShockAPI.DB
public void RemoveBan(string itemname)
{
if (!ItemIsBanned(itemname))
if (!ItemIsBanned(itemname, null))
return;
try
{
database.Query("Delete FROM 'ItemBans' WHERE ItemName=@0;", TShock.Utils.GetItemByName(itemname)[0].name);
ItemBans.Remove(itemname);
ItemBans.Remove( new ItemBan(itemname) );
}
catch (Exception ex)
{
@ -97,10 +104,123 @@ namespace TShockAPI.DB
public bool ItemIsBanned(string name)
{
if (ItemBans.Contains(name))
if (ItemBans.Contains(new ItemBan(name)))
{
return true;
}
return false;
}
public bool ItemIsBanned(string name, TSPlayer ply)
{
if (ItemBans.Contains( new ItemBan(name) ) )
{
ItemBan b = GetItemBanByName(name);
return !b.HasPermissionToUseItem(ply);
}
return false;
}
public bool AllowGroup(string item, string name)
{
string groupsNew = "";
ItemBan b = GetItemBanByName(item);
if (b != null)
{
groupsNew = String.Join(",", b.AllowedGroups);
if (groupsNew.Length > 0)
groupsNew += ",";
groupsNew += name;
b.SetAllowedGroups(groupsNew);
int q = database.Query("UPDATE ItemBans SET AllowedGroups=@0 WHERE ItemName=@1", groupsNew,
item);
return q > 0;
}
return false;
}
public bool RemoveGroup(string item, string group)
{
ItemBan b = GetItemBanByName(item);
if (b != null)
{
b.RemoveGroup(group);
string groups = string.Join(",", b.AllowedGroups);
int q = database.Query("UPDATE ItemBans SET AllowedGroups=@0 WHERE ItemName=@1", groups,
item);
if (q > 0)
return true;
}
return false;
}
public ItemBan GetItemBanByName(String name)
{
foreach (ItemBan b in ItemBans)
{
if (b.Name == name)
{
return b;
}
}
return null;
}
}
public class ItemBan : IEquatable<ItemBan>
{
public string Name { get; set; }
public List<string> AllowedGroups { get; set; }
public ItemBan(string name)
: this()
{
Name = name;
AllowedGroups = new List<string>();
}
public ItemBan()
{
Name = "";
AllowedGroups = new List<string>();
}
public bool Equals(ItemBan other)
{
return Name == other.Name;
}
public bool HasPermissionToUseItem(TSPlayer ply)
{
if (ply == null)
return false;
Console.WriteLine(ply.Group.Name);
return AllowedGroups.Contains(ply.Group.Name); // could add in the other permissions in this class instead of a giant if switch.
}
public void SetAllowedGroups( String groups )
{
// prevent null pointer exceptions
if (!string.IsNullOrEmpty(groups))
{
List<String> groupArr = groups.Split(',').ToList();
for (int i = 0; i < groupArr.Count; i++)
{
groupArr[i] = groupArr[i].Trim();
Console.WriteLine(groupArr[i]);
}
AllowedGroups = groupArr;
}
}
public bool RemoveGroup(string groupName)
{
return AllowedGroups.Remove(groupName);
}
}
}

View file

@ -38,7 +38,6 @@ namespace TShockAPI.DB
public RegionManager(IDbConnection db)
{
database = db;
var table = new SqlTable("Regions",
new SqlColumn("X1", MySqlDbType.Int32),
new SqlColumn("Y1", MySqlDbType.Int32),
@ -47,7 +46,10 @@ namespace TShockAPI.DB
new SqlColumn("RegionName", MySqlDbType.VarChar, 50) { Primary = true },
new SqlColumn("WorldID", MySqlDbType.Text),
new SqlColumn("UserIds", MySqlDbType.Text),
new SqlColumn("Protected", MySqlDbType.Int32)
new SqlColumn("Protected", MySqlDbType.Int32),
new SqlColumn("Groups", MySqlDbType.Text),
new SqlColumn("Owner", MySqlDbType.VarChar, 50)
);
var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder)new SqliteQueryCreator() : new MysqlQueryCreator());
creator.EnsureExists(table);
@ -180,11 +182,13 @@ namespace TShockAPI.DB
int Protected = reader.Get<int>("Protected");
string mergedids = reader.Get<string>("UserIds");
string name = reader.Get<string>("RegionName");
string owner = reader.Get<string>("Owner");
string groups = reader.Get<string>("Groups");
string[] splitids = mergedids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Region r = new Region(new Rectangle(X1, Y1, width, height), name, Protected != 0, Main.worldID.ToString());
Region r = new Region(new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString());
r.SetAllowedGroups( groups );
try
{
for (int i = 0; i < splitids.Length; i++)
@ -231,8 +235,11 @@ namespace TShockAPI.DB
string MergedIDs = reader.Get<string>("UserIds");
string name = reader.Get<string>("RegionName");
string[] SplitIDs = MergedIDs.Split(',');
string owner = reader.Get<string>("Owner");
string groups = reader.Get<string>("Groups");
Region r = new Region(new Rectangle(X1, Y1, width, height), name, Protected != 0, Main.worldID.ToString());
Region r = new Region(new Rectangle(X1, Y1, width, height), name, owner, Protected != 0, Main.worldID.ToString());
r.SetAllowedGroups( groups );
try
{
for (int i = 0; i < SplitIDs.Length; i++)
@ -260,7 +267,7 @@ namespace TShockAPI.DB
}
public bool AddRegion(int tx, int ty, int width, int height, string regionname, string worldid)
public bool AddRegion(int tx, int ty, int width, int height, string regionname, string owner, string worldid)
{
if (GetRegionByName(regionname) != null)
{
@ -268,9 +275,9 @@ namespace TShockAPI.DB
}
try
{
database.Query("INSERT INTO Regions (X1, Y1, width, height, RegionName, WorldID, UserIds, Protected) VALUES (@0, @1, @2, @3, @4, @5, @6, @7);",
tx, ty, width, height, regionname, worldid, "", 1);
Regions.Add(new Region(new Rectangle(tx, ty, width, height), regionname, true, worldid));
database.Query("INSERT INTO Regions (X1, Y1, width, height, RegionName, WorldID, UserIds, Protected, Groups, Owner) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9);",
tx, ty, width, height, regionname, worldid, "", 1, "", owner);
Regions.Add(new Region(new Rectangle(tx, ty, width, height), regionname, owner, true, worldid));
return true;
}
catch (Exception ex)
@ -524,23 +531,84 @@ namespace TShockAPI.DB
}
return null;
}
public bool ChangeOwner( string regionName, string newOwner )
{
var region = GetRegionByName(regionName);
if( region != null )
{
region.Owner = newOwner;
int q = database.Query("UPDATE Regions SET Owner=@0 WHERE RegionName=@1 AND WorldID=@2", newOwner,
regionName, Main.worldID.ToString());
if (q > 0)
return true;
}
return false;
}
public bool AllowGroup( string regionName, string groups)
{
string groupsNew = "";
using (var reader = database.QueryReader("SELECT * FROM Regions WHERE RegionName=@0 AND WorldID=@1", regionName, Main.worldID.ToString()))
{
if (reader.Read())
groupsNew = reader.Get<string>("Groups");
}
if (groupsNew != "")
groupsNew += ",";
groupsNew += groups;
int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", groupsNew,
regionName, Main.worldID.ToString());
Region r = GetRegionByName(regionName);
if( r != null )
{
r.SetAllowedGroups( groupsNew );
}
else
{
return false;
}
return q > 0;
}
public bool RemoveGroup( string regionName, string group )
{
Region r = GetRegionByName(regionName);
if (r != null)
{
r.RemoveGroup(group);
string groups = string.Join(",", r.AllowedGroups);
int q = database.Query("UPDATE Regions SET Groups=@0 WHERE RegionName=@1 AND WorldID=@2", groups,
regionName, Main.worldID.ToString());
if (q > 0)
return true;
}
return false;
}
}
public class Region
{
public Rectangle Area { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public bool DisableBuild { get; set; }
public string WorldID { get; set; }
public List<int> AllowedIDs { get; set; }
public List<string> AllowedGroups { get; set; }
public Region(Rectangle region, string name, bool disablebuild, string RegionWorldIDz)
public Region(Rectangle region, string name, string owner, bool disablebuild, string RegionWorldIDz)
: this()
{
Area = region;
Name = name;
DisableBuild = disablebuild;
Owner = owner;
DisableBuild = disablebuild;
WorldID = RegionWorldIDz;
}
public Region()
@ -550,6 +618,7 @@ namespace TShockAPI.DB
DisableBuild = true;
WorldID = string.Empty;
AllowedIDs = new List<int>();
AllowedGroups = new List<string>();
}
public bool InArea(Rectangle point)
@ -577,14 +646,7 @@ namespace TShockAPI.DB
return true;
}
for (int i = 0; i < AllowedIDs.Count; i++)
{
if (AllowedIDs[i] == ply.UserID)
{
return true;
}
}
return false;
return AllowedIDs.Contains(ply.UserID) || AllowedGroups.Contains(ply.Group.Name) || Owner == ply.UserAccountName || ply.Group.HasPermission("manageregion");
}
public void setAllowedIDs(String ids)
@ -601,6 +663,20 @@ namespace TShockAPI.DB
AllowedIDs = id_list;
}
public void SetAllowedGroups( String groups )
{
// prevent null pointer exceptions
if (!string.IsNullOrEmpty(groups))
{
List<String> groupArr = groups.Split(',').ToList();
for (int i = 0; i < groupArr.Count; i++)
groupArr[i] = groupArr[i].Trim();
AllowedGroups = groupArr;
}
}
public void RemoveID(int id)
{
var index = -1;
@ -614,5 +690,10 @@ namespace TShockAPI.DB
}
AllowedIDs.RemoveAt(index);
}
public bool RemoveGroup(string groupName)
{
return AllowedGroups.Remove(groupName);
}
}
}

View file

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
namespace TShockAPI.DB

View file

@ -1,5 +1,4 @@
using System.Data;
using MySql.Data.MySqlClient;
using System.Collections.Generic;
namespace TShockAPI.DB

View file

@ -218,7 +218,7 @@ namespace TShockAPI.DB
{
Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex);
}
return TShock.Utils.GetGroup("default");
return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName);
}
public Group GetGroupForIPExpensive(string ip)
@ -240,7 +240,7 @@ namespace TShockAPI.DB
{
Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex);
}
return TShock.Utils.GetGroup("default");
return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName);
}

View file

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TShockAPI
{

View file

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TShockAPI.Extensions

View file

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TShockAPI
{

View file

@ -17,7 +17,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
@ -25,7 +24,6 @@ using Terraria;
using TShockAPI.Net;
using System.IO.Streams;
using System.Net;
namespace TShockAPI
{
@ -90,6 +88,9 @@ namespace TShockAPI
{PacketTypes.NpcStrike, HandleNpcStrike},
{PacketTypes.NpcSpecial, HandleSpecial},
{PacketTypes.PlayerAnimation, HandlePlayerAnimation},
{PacketTypes.PlayerBuff, HandlePlayerBuffUpdate},
{PacketTypes.PasswordSend, HandlePassword},
{PacketTypes.ContinueConnecting2, HandleConnecting}
};
}
@ -137,9 +138,9 @@ namespace TShockAPI
item.netDefaults(type);
item.Prefix(prefix);
if (stack > item.maxStack && type != 0)
if (stack > item.maxStack && type != 0 && args.Player.IgnoreActionsForCheating != "none" && !args.Player.Group.HasPermission(Permissions.ignorestackhackdetection))
{
args.Player.IgnoreActionsForCheating = true;
args.Player.IgnoreActionsForCheating = "Item Hack: " + item.name + " (" + stack + ") exceeds max stack of " + item.maxStack;
}
if (args.Player.IsLoggedIn)
@ -156,9 +157,13 @@ namespace TShockAPI
int cur = args.Data.ReadInt16();
int max = args.Data.ReadInt16();
if (cur > 600 || max > 600)
if (args.Player.FirstMaxHP == 0)
args.Player.FirstMaxHP = max;
if (max > 400 && max > args.Player.FirstMaxHP)
{
args.Player.IgnoreActionsForCheating = true;
TShock.Utils.ForceKick(args.Player, "Hacked Client Detected.");
return false;
}
if (args.Player.IsLoggedIn)
@ -175,14 +180,13 @@ namespace TShockAPI
int cur = args.Data.ReadInt16();
int max = args.Data.ReadInt16();
if (cur > 600 || max > 600)
{
args.Player.IgnoreActionsForCheating = true;
}
if (args.Player.FirstMaxMP == 0)
args.Player.FirstMaxMP = max;
if (args.Player.IsLoggedIn)
if (max > 400 && max > args.Player.FirstMaxMP)
{
args.Player.PlayerData.maxMana = max;
TShock.Utils.ForceKick(args.Player, "Hacked Client Detected.");
return false;
}
return false;
@ -236,21 +240,126 @@ namespace TShockAPI
args.TPlayer.name = name;
args.Player.ReceivedInfo = true;
return false;
}
private static bool HandleConnecting(GetDataHandlerArgs args)
{
var user = TShock.Users.GetUserByName(args.Player.Name);
if (user != null && !TShock.Config.DisableLoginBeforeJoin)
{
args.Player.RequiresPassword = true;
NetMessage.SendData((int)PacketTypes.PasswordRequired, args.Player.Index);
return true;
}
else if (!string.IsNullOrEmpty(TShock.Config.ServerPassword))
{
args.Player.RequiresPassword = true;
NetMessage.SendData((int)PacketTypes.PasswordRequired, args.Player.Index);
return true;
}
if (args.Player.State == 1)
args.Player.State = 2;
NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index);
return true;
}
private static bool HandlePassword(GetDataHandlerArgs args)
{
if (!args.Player.RequiresPassword)
return true;
string password = Encoding.ASCII.GetString(args.Data.ReadBytes((int)(args.Data.Length - args.Data.Position - 1)));
var user = TShock.Users.GetUserByName(args.Player.Name);
if (user != null)
{
string encrPass = TShock.Utils.HashPassword(password);
if (user.Password.ToUpper() == encrPass.ToUpper())
{
args.Player.RequiresPassword = false;
args.Player.PlayerData = TShock.InventoryDB.GetPlayerData(args.Player, TShock.Users.GetUserID(args.Player.Name));
if (args.Player.State == 1)
args.Player.State = 2;
NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index);
if (TShock.Config.ServerSideInventory)
{
if (!TShock.CheckInventory(args.Player))
{
args.Player.SendMessage("Login Failed, Please fix the above errors then /login again.", Color.Cyan);
args.Player.IgnoreActionsForClearingTrashCan = true;
return true;
}
}
args.Player.Group = TShock.Utils.GetGroup(user.Group);
args.Player.UserAccountName = args.Player.Name;
args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName);
args.Player.IsLoggedIn = true;
args.Player.IgnoreActionsForInventory = false;
args.Player.PlayerData.CopyInventory(args.Player);
TShock.InventoryDB.InsertPlayerData(args.Player);
args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen);
Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + args.Player.Name);
return true;
}
TShock.Utils.ForceKick(args.Player, "Incorrect User Account Password");
return true;
}
if (!string.IsNullOrEmpty(TShock.Config.ServerPassword))
{
if(TShock.Config.ServerPassword == password)
{
args.Player.RequiresPassword = false;
if (args.Player.State == 1)
args.Player.State = 2;
NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index);
return true;
}
TShock.Utils.ForceKick(args.Player, "Incorrect Server Password");
return true;
}
TShock.Utils.ForceKick(args.Player, "Bad Password Attempt");
return true;
}
private static bool HandleGetSection(GetDataHandlerArgs args)
{
if (args.Player.RequestedSection)
return true;
args.Player.RequestedSection = true;
if (TShock.HackedHealth(args.Player) && !args.Player.Group.HasPermission(Permissions.ignorestathackdetection))
{
TShock.Utils.ForceKick(args.Player, "You have Hacked Health/Mana, Please use a different character.");
}
if (!args.Player.Group.HasPermission(Permissions.ignorestackhackdetection))
{
TShock.HackedInventory(args.Player);
}
if (TShock.Utils.ActivePlayers() + 1 > TShock.Config.MaxSlots && !args.Player.Group.HasPermission(Permissions.reservedslot))
{
TShock.Utils.ForceKick(args.Player, TShock.Config.ServerFullReason);
return true;
}
NetMessage.SendData((int)PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY);
if (TShock.Config.EnableGeoIP && TShock.Geo != null)
{
var code = TShock.Geo.TryGetCountryCode(IPAddress.Parse(args.Player.IP));
args.Player.Country = code == null ? "N/A" : MaxMind.GeoIPCountry.GetCountryNameByCode(code);
if (code == "A1")
if (TShock.Config.KickProxyUsers)
TShock.Utils.Kick(args.Player, "Proxies are not allowed");
Log.Info(string.Format("{0} ({1}) from '{2}' group from '{3}' joined.", args.Player.Name, args.Player.IP, args.Player.Group.Name, args.Player.Country));
Log.Info(string.Format("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", args.Player.Name, args.Player.IP, args.Player.Group.Name, args.Player.Country, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots));
TShock.Utils.Broadcast(args.Player.Name + " has joined from the " + args.Player.Country, Color.Yellow);
}
else
{
Log.Info(string.Format("{0} ({1}) from '{2}' group joined.", args.Player.Name, args.Player.IP, args.Player.Group.Name));
Log.Info(string.Format("{0} ({1}) from '{2}' group joined. ({3}/{4})", args.Player.Name, args.Player.IP, args.Player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots));
TShock.Utils.Broadcast(args.Player.Name + " has joined", Color.Yellow);
}
@ -308,6 +417,10 @@ namespace TShockAPI
var tile = Main.tile[realx, realy];
var newtile = tiles[x, y];
if(TShock.CheckTilePermission(args.Player, x, y))
{
continue;
}
if(TShock.CheckRangePermission(args.Player, x, y))
{
continue;
}
@ -430,6 +543,10 @@ namespace TShockAPI
TSPlayer.All.SendTileSquare(tileX, tileY, size);
WorldGen.RangeFrame(tileX, tileY, tileX + size, tileY + size);
}
else
{
args.Player.SendTileSquare(tileX, tileY, size);
}
return true;
}
@ -474,7 +591,13 @@ namespace TShockAPI
{
return true;
}
if (tiletype == 48 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Spikes"))
if (tiletype == 29 && tiletype == 97 && TShock.Config.ServerSideInventory)
{
args.Player.SendMessage("You cannot place this tile, Server side inventory is enabled.", Color.Red);
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (tiletype == 48 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Spike", args.Player))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
@ -485,7 +608,7 @@ namespace TShockAPI
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (tiletype == 141 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Explosives"))
if (tiletype == 141 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Explosives", args.Player))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
@ -504,12 +627,29 @@ namespace TShockAPI
return true;
}
if (type == 0 && Main.tileSolid[Main.tile[tileX, tileY].type] && args.Player.Active)
if ((tiletype == 127 || Main.tileCut[tiletype]) && type == 0) //Ice Block Kill, Bypass range checks and such
{
args.Player.TileThreshold++;
var coords = new Vector2(tileX, tileY);
if (!args.Player.TilesDestroyed.ContainsKey(coords))
args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY]);
return false;
}
if (TShock.CheckRangePermission(args.Player, tileX, tileY))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (args.Player.TileKillThreshold >= TShock.Config.TileKillThreshold)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000)
@ -518,6 +658,22 @@ namespace TShockAPI
return true;
}
if (type == 1 && !args.Player.Group.HasPermission(Permissions.ignoreplacetiledetection))
{
args.Player.TilePlaceThreshold++;
var coords = new Vector2(tileX, tileY);
if (!args.Player.TilesCreated.ContainsKey(coords))
args.Player.TilesCreated.Add(coords, Main.tile[tileX, tileY].Data);
}
if ((type == 0 || type == 4) && Main.tileSolid[Main.tile[tileX, tileY].type] && !args.Player.Group.HasPermission(Permissions.ignorekilltiledetection))
{
args.Player.TileKillThreshold++;
var coords = new Vector2(tileX, tileY);
if (!args.Player.TilesDestroyed.ContainsKey(coords))
args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY].Data);
}
return false;
}
@ -543,7 +699,7 @@ namespace TShockAPI
args.TPlayer.hostile = pvp;
if (TShock.Config.AlwaysPvP)
if (TShock.Config.PvPMode == "always")
{
if (pvp == true)
args.Player.IgnoreActionsForPvP = false;
@ -572,40 +728,107 @@ namespace TShockAPI
return true;
}
if ((control & 32) == 32)
{
if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned(args.TPlayer.inventory[item].name))
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendMessage(string.Format("You cannot use {0} on this server. Your actions are being ignored.", args.TPlayer.inventory[item].name), Color.Red);
return true;
}
}
if (!pos.Equals(args.Player.LastNetPosition))
{
float distance = Vector2.Distance(new Vector2((pos.X / 16f), (pos.Y / 16f)), new Vector2(Main.spawnTileX, Main.spawnTileY));
if (TShock.CheckIgnores(args.Player) && distance > 6f)
if (TShock.CheckIgnores(args.Player) && distance > TShock.Config.MaxRangeForDisabled)
{
if (args.Player.IgnoreActionsForCheating)
if(args.Player.IgnoreActionsForCheating != "none")
{
args.Player.SendMessage("You have been disabled for cheating! Please login with a new character!", Color.Red);
args.Player.SendMessage("Disabled for cheating: " + args.Player.IgnoreActionsForCheating, Color.Red);
}
else if (TShock.Config.ServerSideInventory && !args.Player.IsLoggedIn)
else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn)
{
args.Player.SendMessage("Please /register or /login to play!", Color.Red);
}
else if (args.Player.IgnoreActionsForInventory)
{
args.Player.SendMessage("Server Side Inventory is enabled! Please /register or /login to play!", Color.Red);
}
else if (TShock.Config.AlwaysPvP && !args.TPlayer.hostile)
else if (args.Player.IgnoreActionsForClearingTrashCan)
{
args.Player.SendMessage("You need to rejoin to ensure your trash can is cleared!", Color.Red);
}
else if (args.Player.IgnoreActionsForPvP)
{
args.Player.SendMessage("PvP is forced! Enable PvP else you can't move or do anything!", Color.Red);
}
args.Player.Spawn();
return true;
}
}
if (args.Player.Dead)
{
return true;
}
if (!args.Player.Group.HasPermission(Permissions.ignorenoclipdetection) && Collision.SolidCollision(pos, args.TPlayer.width, args.TPlayer.height))
{
int lastTileX = (int)(args.Player.LastNetPosition.X / 16f);
int lastTileY = (int)(args.Player.LastNetPosition.Y / 16f);
if (!args.Player.Teleport(lastTileX, lastTileY + 3))
{
args.Player.SendMessage("You got stuck in a solid object, Sent to spawn point.");
args.Player.Spawn();
}
return true;
}
}
args.Player.LastNetPosition = pos;
return false;
if ((control & 32) == 32)
{
if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned(args.TPlayer.inventory[item].name, args.Player))
{
control -= 32;
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendMessage(string.Format("You cannot use {0} on this server. Your actions are being ignored.", args.TPlayer.inventory[item].name), Color.Red);
}
}
args.TPlayer.selectedItem = item;
args.TPlayer.position = pos;
args.TPlayer.velocity = vel;
args.TPlayer.oldVelocity = args.TPlayer.velocity;
args.TPlayer.fallStart = (int)(pos.Y / 16f);
args.TPlayer.controlUp = false;
args.TPlayer.controlDown = false;
args.TPlayer.controlLeft = false;
args.TPlayer.controlRight = false;
args.TPlayer.controlJump = false;
args.TPlayer.controlUseItem = false;
args.TPlayer.direction = -1;
if ((control & 1) == 1)
{
args.TPlayer.controlUp = true;
}
if ((control & 2) == 2)
{
args.TPlayer.controlDown = true;
}
if ((control & 4) == 4)
{
args.TPlayer.controlLeft = true;
}
if ((control & 8) == 8)
{
args.TPlayer.controlRight = true;
}
if ((control & 16) == 16)
{
args.TPlayer.controlJump = true;
}
if ((control & 32) == 32)
{
args.TPlayer.controlUseItem = true;
}
if ((control & 64) == 64)
{
args.TPlayer.direction = 1;
}
NetMessage.SendData((int)PacketTypes.PlayerUpdate, -1, args.Player.Index, "", args.Player.Index);
return true;
}
private static bool HandleProjectileNew(GetDataHandlerArgs args)
@ -627,12 +850,14 @@ namespace TShockAPI
if (args.Player.Index != owner)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendData(PacketTypes.ProjectileNew, "", index);
return true;
}
if (dmg > 175)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendData(PacketTypes.ProjectileNew, "", index);
return true;
}
@ -650,6 +875,24 @@ namespace TShockAPI
return true;
}
if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendData(PacketTypes.ProjectileNew, "", index);
return true;
}
if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000)
{
args.Player.SendData(PacketTypes.ProjectileNew, "", index);
return true;
}
if (!args.Player.Group.HasPermission(Permissions.ignoreprojectiledetection))
{
args.Player.ProjectileThreshold++;
}
return false;
}
@ -660,6 +903,7 @@ namespace TShockAPI
if (args.Player.Index != owner)
{
args.Player.LastThreat = DateTime.UtcNow;
return true;
}
@ -674,6 +918,7 @@ namespace TShockAPI
if (args.Player.Index != Main.projectile[index].owner)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendData(PacketTypes.ProjectileNew, "", index);
return true;
}
@ -691,6 +936,12 @@ namespace TShockAPI
return true;
}
if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000)
{
args.Player.SendData(PacketTypes.ProjectileNew, "", index);
return true;
}
return false;
}
@ -713,9 +964,7 @@ namespace TShockAPI
}
args.Player.LastDeath = DateTime.Now;
if (args.Player.Difficulty != 2)
args.Player.ForceSpawn = true;
args.Player.Dead = true;
return false;
}
@ -740,43 +989,59 @@ namespace TShockAPI
return true;
}
if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000)
if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold)
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendTileSquare(tileX, tileY);
return true;
}
bool bucket = false;
for (int i = 0; i < 49; i++)
if (!args.Player.Group.HasPermission(Permissions.ignoreliquidsetdetection))
{
if (args.TPlayer.inventory[i].type >= 205 && args.TPlayer.inventory[i].type <= 207)
{
bucket = true;
break;
}
args.Player.TileLiquidThreshold++;
}
if (!bucket)
int bucket = 0;
if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 206)
{
bucket = 1;
}
else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 207)
{
bucket = 2;
}
if (lava && bucket != 2 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player))
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (lava && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Lava Bucket"))
if (!lava && bucket != 1 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player))
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (!lava && !args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Water Bucket"))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (TShock.CheckTilePermission(args.Player, tileX, tileY))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if (TShock.CheckRangePermission(args.Player, tileX, tileY, 16))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
}
if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000)
{
args.Player.SendTileSquare(tileX, tileY);
return true;
}
return false;
}
@ -797,6 +1062,7 @@ namespace TShockAPI
if (Main.tile[tileX, tileY].type != 0x15 && (!TShock.Utils.MaxChests() && Main.tile[tileX, tileY].type != 0)) //Chest
{
args.Player.LastThreat = DateTime.UtcNow;
args.Player.SendTileSquare(tileX, tileY);
return true;
}
@ -807,6 +1073,12 @@ namespace TShockAPI
return true;
}
if (TShock.CheckRangePermission(args.Player, tileX, tileY))
{
args.Player.SendTileSquare(tileX, tileY);
return true;
}
return false;
}
@ -838,6 +1110,7 @@ namespace TShockAPI
else
args.Player.InitSpawn = true;
args.Player.Dead = false;
return false;
}
@ -851,10 +1124,16 @@ namespace TShockAPI
return true;
}
if (TShock.Config.RangeChecks && ((Math.Abs(args.Player.TileX - x) > 32) || (Math.Abs(args.Player.TileY - y) > 32)))
if (TShock.CheckRangePermission(args.Player, x, y))
{
return true;
}
if (TShock.CheckTilePermission(args.Player, x, y) && TShock.Config.RegionProtectChests)
{
return true;
}
return false;
}
@ -879,15 +1158,18 @@ namespace TShockAPI
Item item = new Item();
item.netDefaults(type);
if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name))
if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name, args.Player))
{
args.Player.SendData(PacketTypes.ChestItem, "", id, slot);
return false;
}
if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y))
if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests)
{
return false;
}
if (TShock.CheckRangePermission(args.Player, Main.chest[id].x, Main.chest[id].y))
{
args.Player.SendData(PacketTypes.ChestItem, "", id, slot);
return false;
}
@ -905,15 +1187,12 @@ namespace TShockAPI
args.Player.SendData(PacketTypes.SignNew, "", id);
return true;
}
return false;
}
private static bool HandleGetSection(GetDataHandlerArgs args)
{
if (args.Player.RequestedSection)
if (TShock.CheckRangePermission(args.Player, x, y))
{
args.Player.SendData(PacketTypes.SignNew, "", id);
return true;
args.Player.RequestedSection = true;
}
return false;
}
@ -936,6 +1215,12 @@ namespace TShockAPI
args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, Convert.ToByte(Main.npc[id].homeless));
return true;
}
if (TShock.CheckRangePermission(args.Player, x, y))
{
args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, Convert.ToByte(Main.npc[id].homeless));
return true;
}
return false;
}
@ -955,7 +1240,7 @@ namespace TShockAPI
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
return true;
}
if (TShock.Config.RangeChecks && ((Math.Abs(args.Player.TileX - TShock.Players[id].TileX) > 64) || (Math.Abs(args.Player.TileY - TShock.Players[id].TileY) > 64)))
if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 50))
{
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
return true;
@ -989,7 +1274,7 @@ namespace TShockAPI
return false;
}
if (TShock.Config.RangeChecks && ((Math.Abs(args.Player.TileX - (pos.X / 16f)) > 64) || (Math.Abs(args.Player.TileY - (pos.Y / 16f)) > 64)))
if (TShock.CheckRangePermission(args.Player, (int)(pos.X / 16f), (int)(pos.Y / 16f)))
{
args.Player.SendData(PacketTypes.ItemDrop, "", id);
return true;
@ -997,7 +1282,7 @@ namespace TShockAPI
Item item = new Item();
item.netDefaults(type);
if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name))
if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name, args.Player))
{
args.Player.SendData(PacketTypes.ItemDrop, "", id);
return true;
@ -1055,7 +1340,7 @@ namespace TShockAPI
return true;
}
if (TShock.Config.RangeChecks && ((Math.Abs(args.Player.TileX - TShock.Players[id].TileX) > 128) || (Math.Abs(args.Player.TileY - TShock.Players[id].TileY) > 128)))
if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 100))
{
args.Player.SendData(PacketTypes.PlayerHp, "", id);
args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
@ -1101,7 +1386,7 @@ namespace TShockAPI
return true;
}
if (TShock.Config.RangeChecks && ((Math.Abs(args.Player.TileX - (Main.npc[id].position.X / 16f)) > 128) || (Math.Abs(args.Player.TileY - (Main.npc[id].position.Y / 16f)) > 128)))
if (TShock.Config.RangeChecks && TShock.CheckRangePermission(args.Player, (int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 100))
{
args.Player.SendData(PacketTypes.NpcUpdate, "", id);
return true;
@ -1147,5 +1432,34 @@ namespace TShockAPI
return false;
}
private static bool HandlePlayerBuffUpdate(GetDataHandlerArgs args)
{
var id = args.Data.ReadInt8();
for (int i = 0; i < 10; i++)
{
var buff = args.Data.ReadInt8();
if (buff == 10)
{
if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Invisibility Potion", args.Player) )
buff = 0;
else if (TShock.Config.DisableInvisPvP && args.TPlayer.hostile)
buff = 0;
}
args.TPlayer.buffType[i] = buff;
if (args.TPlayer.buffType[i] > 0)
{
args.TPlayer.buffTime[i] = 60;
}
else
{
args.TPlayer.buffTime[i] = 0;
}
}
NetMessage.SendData((int)PacketTypes.PlayerBuff, -1, args.Player.Index, "", args.Player.Index);
return true;
}
}
}

View file

@ -28,6 +28,8 @@ namespace TShockAPI
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;
@ -91,6 +93,9 @@ namespace TShockAPI
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)

View file

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO.Streams;
using System.Linq;
using System.Text;
namespace TShockAPI.Net
{

View file

@ -35,14 +35,28 @@ namespace TShockAPI
[Description("Prevents you from being reverted by kill tile abuse detection")]
public static readonly string ignorekilltiledetection;
[Description("Prevents you from being reverted by place tile abuse detection")]
public static readonly string ignoreplacetiledetection;
[Description("Prevents you from being disabled by liquid set abuse detection")]
public static readonly string ignoreliquidsetdetection;
[Description("Prevents you from being disabled by liquid set abuse detection")]
public static readonly string ignoreprojectiledetection;
[Description("Prevents you from being reverted by no clip detection")]
public static readonly string ignorenoclipdetection;
[Description("Prevents you from being disabled by stack hack detection")]
public static readonly string ignorestackhackdetection;
[Description("Prevents you from being kicked by hacked health detection")]
public static readonly string ignorestathackdetection;
[Description("Specific log messages are sent to users with this permission")]
public static readonly string logs;
[Description("User gets the admin prefix/color in chat")]
public static readonly string adminchat;
[Todo]
[Description("Not currently working")]
[Description("Allows you to bypass the max slots for up to 5 slots above your max")]
public static readonly string reservedslot;
[Description("User is notified when an update is available")]
@ -141,6 +155,28 @@ namespace TShockAPI
[Description("User can convert hallow into corruption and vice-versa")]
public static readonly string converthardmode;
[Description("User can mute and unmute users")]
public static readonly string mute;
[Description("User can register account in game")]
public static readonly string canregister;
[Description("User can login in game")]
public static readonly string canlogin;
[Description("User can change password in game")]
public static readonly string canchangepassword;
[Description("User can use party chat in game")]
public static readonly string canpartychat;
[Description("User can talk in third person")]
public static readonly string cantalkinthird;
[Description("Bypass Server Side Inventory checks")]
public static readonly string bypassinventorychecks;
static Permissions()
{
foreach (var field in typeof(Permissions).GetFields())

View file

@ -8,9 +8,9 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTitle("TShockAPI")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyCompany("Nyx Team")]
[assembly: AssemblyProduct("TShockAPI")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
[assembly: AssemblyCopyright("Copyright © Nyx Team 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@ -35,6 +35,5 @@ using System.Runtime.InteropServices;
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.4.1.1221")]
[assembly: AssemblyFileVersion("3.4.1.1221")]
[assembly: AssemblyVersion("3.4.2.1232")]
[assembly: AssemblyFileVersion("3.4.2.1232")]

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HttpServer;
using Rests;
using Terraria;

View file

@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using HttpServer;
using TShockAPI;
namespace Rests
{

75
TShockAPI/StatTracker.cs Normal file
View file

@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Net;
using System.Threading;
using Terraria;
namespace TShockAPI
{
public class StatTracker
{
Utils Utils = TShock.Utils;
public DateTime lastcheck = DateTime.MinValue;
readonly int checkinFrequency = 5;
public void checkin()
{
if ((DateTime.Now - lastcheck).TotalMinutes >= checkinFrequency)
{
ThreadPool.QueueUserWorkItem(callHome);
lastcheck = DateTime.Now;
}
}
private void callHome(object state)
{
string fp;
string lolpath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/.tshock/";
if (!Directory.Exists(lolpath))
{
Directory.CreateDirectory(lolpath);
}
if (!File.Exists(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint")))
{
fp = "";
int random = Utils.Random.Next(500000, 1000000);
fp += random;
fp = Utils.HashPassword(Netplay.serverIP + fp + Netplay.serverPort + Netplay.serverListenIP);
TextWriter tw = new StreamWriter(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint"));
tw.Write(fp);
tw.Close();
}
else
{
fp = "";
TextReader tr = new StreamReader(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint"));
fp = tr.ReadToEnd();
tr.Close();
}
using (var client = new WebClient())
{
client.Headers.Add("user-agent",
"TShock (" + TShock.VersionNum + ")");
try
{
string response;
if (TShock.Config.DisablePlayerCountReporting)
{
response = client.DownloadString("http://tshock.co/tickto.php?do=log&fp=" + fp + "&ver=" + TShock.VersionNum + "&os=" + System.Environment.OSVersion.ToString() + "&mono=" + Main.runningMono + "&port=" + Netplay.serverPort + "&plcount=0");
}
else
{
response = client.DownloadString("http://tshock.co/tickto.php?do=log&fp=" + fp + "&ver=" + TShock.VersionNum + "&os=" + System.Environment.OSVersion.ToString() + "&mono=" + Main.runningMono + "&port=" + Netplay.serverPort + "&plcount=" + TShock.Utils.ActivePlayers());
}
Log.ConsoleInfo("Stat Tracker: " + response + "\n");
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
}
}

View file

@ -30,10 +30,14 @@ namespace TShockAPI
{
public static readonly TSServerPlayer Server = new TSServerPlayer();
public static readonly TSPlayer All = new TSPlayer("All");
public int TileThreshold { get; set; }
public Dictionary<Vector2, Tile> TilesDestroyed { get; protected set; }
public bool SyncHP { get; set; }
public bool SyncMP { get; set; }
public int TileKillThreshold { get; set; }
public int TilePlaceThreshold { get; set; }
public int TileLiquidThreshold { get; set; }
public int ProjectileThreshold { get; set; }
public Dictionary<Vector2, TileData> TilesDestroyed { get; protected set; }
public Dictionary<Vector2, TileData> TilesCreated { get; protected set; }
public int FirstMaxHP { get; set; }
public int FirstMaxMP { get; set; }
public Group Group { get; set; }
public bool ReceivedInfo { get; set; }
public int Index { get; protected set; }
@ -56,18 +60,21 @@ namespace TShockAPI
public int UserID = -1;
public bool HasBeenNaggedAboutLoggingIn;
public bool TPAllow = true;
public bool mute = false;
public bool TpLock = false;
Player FakePlayer;
public bool RequestedSection = false;
public DateTime LastDeath { get; set; }
public bool ForceSpawn = false;
public bool Dead = false;
public string Country = "??";
public int Difficulty;
private string CacheIP;
public bool IgnoreActionsForPvP = false;
public bool IgnoreActionsForInventory = false;
public bool IgnoreActionsForCheating = false;
public string IgnoreActionsForCheating = "none";
public bool IgnoreActionsForClearingTrashCan = false;
public PlayerData PlayerData;
public bool RequiresPassword = false;
public bool RealPlayer
{
@ -77,6 +84,13 @@ namespace TShockAPI
{
get { return RealPlayer && (Netplay.serverSock[Index] != null && Netplay.serverSock[Index].active && !Netplay.serverSock[Index].kill); }
}
public int State
{
get { return Netplay.serverSock[Index].state; }
set { Netplay.serverSock[Index].state = value; }
}
public string IP
{
get
@ -154,17 +168,19 @@ namespace TShockAPI
public TSPlayer(int index)
{
TilesDestroyed = new Dictionary<Vector2, Tile>();
TilesDestroyed = new Dictionary<Vector2, TileData>();
TilesCreated = new Dictionary<Vector2, TileData>();
Index = index;
Group = new Group("null");
Group = new Group(TShock.Config.DefaultGuestGroupName);
}
protected TSPlayer(String playerName)
{
TilesDestroyed = new Dictionary<Vector2, Tile>();
TilesDestroyed = new Dictionary<Vector2, TileData>();
TilesCreated = new Dictionary<Vector2, TileData>();
Index = -1;
FakePlayer = new Player { name = playerName, whoAmi = -1 };
Group = new Group("null");
Group = new Group(TShock.Config.DefaultGuestGroupName);
}
public virtual void Disconnect(string reason)
@ -375,7 +391,7 @@ namespace TShockAPI
public override void SendMessage(string msg, byte red, byte green, byte blue)
{
Console.WriteLine(msg);
RconHandler.Response += msg + "\n";
//RconHandler.Response += msg + "\n";
}
public void SetFullMoon(bool fullmoon)
@ -417,17 +433,15 @@ namespace TShockAPI
NetMessage.SendData((int)PacketTypes.NpcStrike, -1, -1, "", npcid, damage, knockBack, hitDirection);
}
public void RevertKillTile(Dictionary<Vector2, Tile> destroyedTiles)
public void RevertTiles(Dictionary<Vector2, TileData> tiles)
{
// Update Main.Tile first so that when tile sqaure is sent it is correct
foreach (KeyValuePair<Vector2, Tile> entry in destroyedTiles)
foreach (KeyValuePair<Vector2, TileData> entry in tiles)
{
Main.tile[(int)entry.Key.X, (int)entry.Key.Y] = entry.Value;
Log.Debug(string.Format("Reverted DestroyedTile(TileXY:{0}_{1}, Type:{2})",
entry.Key.X, entry.Key.Y, Main.tile[(int)entry.Key.X, (int)entry.Key.Y].type));
Main.tile[(int)entry.Key.X, (int)entry.Key.Y].Data = entry.Value;
}
// Send all players updated tile sqaures
foreach (Vector2 coords in destroyedTiles.Keys)
foreach (Vector2 coords in tiles.Keys)
{
All.SendTileSquare((int)coords.X, (int)coords.Y, 3);
}
@ -438,7 +452,7 @@ namespace TShockAPI
{
public NetItem[] inventory = new NetItem[NetItem.maxNetInventory];
public int maxHealth = 100;
public int maxMana = 100;
//public int maxMana = 100;
public bool exists = false;
public PlayerData(TSPlayer player)
@ -479,7 +493,6 @@ namespace TShockAPI
public void CopyInventory(TSPlayer player)
{
this.maxHealth = player.TPlayer.statLifeMax;
this.maxMana = player.TPlayer.statManaMax;
Item[] inventory = player.TPlayer.inventory;
Item[] armor = player.TPlayer.armor;
for (int i = 0; i < NetItem.maxNetInventory; i++)

View file

@ -32,12 +32,10 @@ using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Mono.Data.Sqlite;
using Hooks;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using Rests;
using Terraria;
using TShockAPI.DB;
@ -71,7 +69,7 @@ namespace TShockAPI
public static SecureRest RestApi;
public static RestManager RestManager;
public static Utils Utils = new Utils();
public static StatTracker StatTracker = new StatTracker();
/// <summary>
/// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded.
/// </summary>
@ -189,10 +187,12 @@ namespace TShockAPI
if (Config.EnableGeoIP && File.Exists(geoippath))
Geo = new MaxMind.GeoIPCountry(geoippath);
Console.Title = string.Format("TerrariaShock Version {0} ({1})", Version, VersionCodename);
Log.ConsoleInfo(string.Format("TerrariaShock Version {0} ({1}) now running.", Version, VersionCodename));
GameHooks.PostInitialize += OnPostInit;
GameHooks.Update += OnUpdate;
ServerHooks.Connect += OnConnect;
ServerHooks.Join += OnJoin;
ServerHooks.Leave += OnLeave;
ServerHooks.Chat += OnChat;
@ -226,49 +226,7 @@ namespace TShockAPI
}
private void callHome()
{
string fp;
string lolpath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/.tshock/";
if (!Directory.Exists(lolpath))
{
Directory.CreateDirectory(lolpath);
}
if (!File.Exists(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint")))
{
fp = "";
int random = Utils.Random.Next(500000, 1000000);
fp += random;
fp = Utils.HashPassword(Netplay.serverIP + fp + Netplay.serverPort + Netplay.serverListenIP);
TextWriter tw = new StreamWriter(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint"));
tw.Write(fp);
tw.Close();
} else
{
fp = "";
TextReader tr = new StreamReader(Path.Combine(lolpath, Netplay.serverPort + ".fingerprint"));
fp = tr.ReadToEnd();
tr.Close();
}
using (var client = new WebClient())
{
client.Headers.Add("user-agent",
"TShock (" + VersionNum + ")");
try
{
string response = client.DownloadString("http://tshock.co/tickto.php?do=log&fp=" + fp + "&ver=" + VersionNum + "&port=" + Netplay.serverPort);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("\nRegistered with stat tracker: " + response + "\n");
Console.ForegroundColor = ConsoleColor.Gray;
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
RestObject RestApi_Verify(string username, string password)
{
@ -455,54 +413,131 @@ namespace TShockAPI
if (Config.RestApiEnabled)
RestApi.Start();
Thread t = new Thread(callHome);
t.Start();
StatTracker.checkin();
FixChestStacks();
}
private void FixChestStacks()
{
foreach(Chest chest in Main.chest)
{
if (chest != null)
{
foreach (Item item in chest.item)
{
if (item != null && item.stack > item.maxStack)
item.stack = item.maxStack;
}
}
}
}
private DateTime LastCheck = DateTime.UtcNow;
private DateTime LastSave = DateTime.UtcNow;
private void OnUpdate()
{
UpdateManager.UpdateProcedureCheck();
StatTracker.checkin();
if (Backups.IsBackupTime)
Backups.Backup();
//call these every second, not every update
if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1)
{
OnSecondUpdate();
LastCheck = DateTime.UtcNow;
foreach (TSPlayer player in Players)
}
if ((DateTime.UtcNow - LastSave).TotalMinutes >= 15)
{
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player.Active)
// prevent null point exceptions
if (player != null && player.IsLoggedIn)
{
if (player.TilesDestroyed != null)
{
if (player.TileThreshold >= Config.TileThreshold)
{
TSPlayer.Server.RevertKillTile(player.TilesDestroyed);
}
if (player.TileThreshold > 0)
{
player.TileThreshold = 0;
player.TilesDestroyed.Clear();
}
}
/*if (CheckPlayerCollision(player.TileX, player.TileY))
player.SendMessage("You are currently nocliping!", Color.Red);*/
if (player.ForceSpawn && (DateTime.Now - player.LastDeath).Seconds >= 3)
{
player.Spawn();
player.ForceSpawn = false;
}
TShock.InventoryDB.InsertPlayerData(player);
}
}
LastSave = DateTime.UtcNow;
}
}
private void OnJoin(int ply, HandledEventArgs handler)
private void OnSecondUpdate()
{
if (Config.ForceTime != "normal")
{
switch(Config.ForceTime)
{
case "day":
TSPlayer.Server.SetTime(true, 27000.0);
break;
case "night":
TSPlayer.Server.SetTime(false, 16200.0);
break;
}
}
int count = 0;
foreach (TSPlayer player in Players)
{
if (player != null && player.Active)
{
count++;
if (player.TilesDestroyed != null)
{
if (player.TileKillThreshold >= Config.TileKillThreshold)
{
player.LastThreat = DateTime.UtcNow;
TSPlayer.Server.RevertTiles(player.TilesDestroyed);
player.TilesDestroyed.Clear();
}
}
if (player.TileKillThreshold > 0)
{
player.TileKillThreshold = 0;
}
if (player.TilesCreated != null)
{
if (player.TilePlaceThreshold >= Config.TilePlaceThreshold)
{
player.LastThreat = DateTime.UtcNow;
TSPlayer.Server.RevertTiles(player.TilesCreated);
player.TilesCreated.Clear();
}
}
if (player.TilePlaceThreshold > 0)
{
player.TilePlaceThreshold = 0;
}
if(player.TileLiquidThreshold >= Config.TileLiquidThreshold)
{
player.LastThreat = DateTime.UtcNow;
}
if (player.TileLiquidThreshold > 0)
{
player.TileLiquidThreshold = 0;
}
if (player.ProjectileThreshold >= Config.ProjectileThreshold)
{
player.LastThreat = DateTime.UtcNow;
}
if (player.ProjectileThreshold > 0)
{
player.ProjectileThreshold = 0;
}
if (player.Dead && (DateTime.Now - player.LastDeath).Seconds >= 3 && player.Difficulty != 2)
{
player.Spawn();
}
}
}
Console.Title = string.Format("TerrariaShock Version {0} ({1}) ({2}/{3})", Version, VersionCodename, count, Config.MaxSlots);
}
private void OnConnect(int ply, HandledEventArgs handler)
{
var player = new TSPlayer(ply);
if (Config.EnableDNSHostResolution)
@ -514,20 +549,17 @@ namespace TShockAPI
player.Group = Users.GetGroupForIP(player.IP);
}
if (TShock.Utils.ActivePlayers() + 1 > Config.MaxSlots && !player.Group.HasPermission(Permissions.reservedslot))
if (TShock.Utils.ActivePlayers() + 1 > Config.MaxSlots + 20)
{
TShock.Utils.ForceKick(player, Config.ServerFullReason);
TShock.Utils.ForceKick(player, Config.ServerFullNoReservedReason);
handler.Handled = true;
return;
}
var ipban = Bans.GetBanByIp(player.IP);
var nameban = Bans.GetBanByName(player.Name);
Ban ban = null;
if (ipban != null && Config.EnableIPBans)
ban = ipban;
else if (nameban != null && Config.EnableIPBans)
ban = nameban;
if (ban != null)
{
@ -543,9 +575,45 @@ namespace TShockAPI
return;
}
if (TShock.Geo != null)
{
var code = TShock.Geo.TryGetCountryCode(IPAddress.Parse(player.IP));
player.Country = code == null ? "N/A" : MaxMind.GeoIPCountry.GetCountryNameByCode(code);
if (code == "A1")
{
if (TShock.Config.KickProxyUsers)
{
TShock.Utils.ForceKick(player, "Proxies are not allowed");
handler.Handled = true;
return;
}
}
}
Players[ply] = player;
}
private void OnJoin(int ply, HandledEventArgs handler)
{
var player = Players[ply];
if (player == null)
{
handler.Handled = true;
return;
}
var nameban = Bans.GetBanByName(player.Name);
Ban ban = null;
if (nameban != null && Config.EnableBanOnUsernames)
ban = nameban;
if (ban != null)
{
TShock.Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason));
handler.Handled = true;
return;
}
}
private void OnLeave(int ply)
{
var tsplr = Players[ply];
@ -559,7 +627,7 @@ namespace TShockAPI
if (tsplr.IsLoggedIn)
{
tsplr.PlayerData.CopyInventory(tsplr);
InventoryDB.InsertPlayerData(tsplr, tsplr.UserID);
InventoryDB.InsertPlayerData(tsplr);
}
if (Config.RememberLeavePos)
@ -587,15 +655,6 @@ namespace TShockAPI
return;
}
if (tsplr.Group.HasPermission(Permissions.adminchat) && !text.StartsWith("/") && Config.AdminChatEnabled)
{
TShock.Utils.Broadcast(Config.AdminChatPrefix + "<" + tsplr.Name + "> " + text,
tsplr.Group.R, tsplr.Group.G,
tsplr.Group.B);
e.Handled = true;
return;
}
if (text.StartsWith("/"))
{
try
@ -608,12 +667,14 @@ namespace TShockAPI
Log.Error(ex.ToString());
}
}
else
else if (!tsplr.mute)
{
TShock.Utils.Broadcast("{2}<{0}> {1}".SFormat(tsplr.Name, text, Config.ChatDisplayGroup ? "[{0}] ".SFormat(tsplr.Group.Name) : ""),
tsplr.Group.R, tsplr.Group.G,
tsplr.Group.B);
//Log.Info(string.Format("{0} said: {1}", tsplr.Name, text));
TShock.Utils.Broadcast(String.Format(TShock.Config.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, text), tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
e.Handled = true;
}
else if (tsplr.mute)
{
tsplr.SendMessage("You are muted!");
e.Handled = true;
}
}
@ -655,7 +716,6 @@ namespace TShockAPI
}
}
TSPlayer.Server.SendMessage(string.Format("{0} players connected.", count));
e.Handled = true;
}
else if (text.StartsWith("say "))
{
@ -665,13 +725,12 @@ namespace TShockAPI
{
Main.autoSave = Config.AutoSave = !Config.AutoSave;
Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled"));
e.Handled = true;
}
else if (text.StartsWith("/"))
else
{
if (Commands.HandleCommand(TSPlayer.Server, text))
e.Handled = true;
Commands.HandleCommand(TSPlayer.Server, text);
}
e.Handled = true;
}
private void OnGetData(GetDataEventArgs e)
@ -696,24 +755,28 @@ namespace TShockAPI
return;
}
// Stop accepting updates from player as this player is going to be kicked/banned during OnUpdate (different thread so can produce race conditions)
if (player.TileThreshold >= Config.TileThreshold && !player.Group.HasPermission(Permissions.ignorekilltiledetection))
if (player.RequiresPassword && type != PacketTypes.PasswordSend)
{
e.Handled = true;
return;
}
else
if ((player.State < 10 || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 && (int)type != 38)
{
using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length))
e.Handled = true;
return;
}
using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length))
{
try
{
try
{
if (GetDataHandlers.HandlerGetData(type, player, data))
e.Handled = true;
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
if (GetDataHandlers.HandlerGetData(type, player, data))
e.Handled = true;
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
}
}
@ -728,40 +791,41 @@ namespace TShockAPI
}
TShock.Utils.ShowFileToUser(player, "motd.txt");
if (HackedHealth(player))
{
player.IgnoreActionsForCheating = true;
player.SendMessage("You are using a health/mana cheat. Please choose a different character.");
}
if (HackedInventory(player))
{
player.IgnoreActionsForCheating = true;
}
NetMessage.syncPlayers();
if (Config.ServerSideInventory && !player.IsLoggedIn)
{
player.IgnoreActionsForInventory = true;
player.SendMessage("Server Side Inventory is enabled! Please /register or /login to play!", Color.Red);
}
if (Config.AlwaysPvP && !player.TPlayer.hostile)
if (Config.PvPMode == "always" && !player.TPlayer.hostile)
{
player.IgnoreActionsForPvP = true;
player.SendMessage("PvP is forced! Enable PvP else you can't move or do anything!", Color.Red);
}
if (!player.IsLoggedIn)
{
if (Config.RequireLogin)
{
player.SendMessage("Please /register or /login to play!", Color.Red);
}
else if (Config.ServerSideInventory)
{
player.SendMessage("Server Side Inventory is enabled! Please /register or /login to play!", Color.Red);
}
if (Config.ServerSideInventory)
{
player.IgnoreActionsForInventory = true;
}
}
if (player.Group.HasPermission(Permissions.causeevents) && Config.InfiniteInvasion)
{
StartInvasion();
}
if (Config.RememberLeavePos)
{
var pos = RememberedPos.GetLeavePos(player.Name, player.IP);
player.Teleport((int)pos.X, (int)pos.Y);
player.SendTileSquare((int)pos.X, (int)pos.Y);
player.Teleport((int) pos.X, (int) pos.Y);
}
e.Handled = true;
}
@ -782,6 +846,21 @@ namespace TShockAPI
if (e.Info == 43)
if (Config.DisableTombstones)
e.Object.SetDefaults(0);
if (e.Info == 75)
if (Config.DisableClownBombs)
e.Object.SetDefaults(0);
if (e.Info == 109)
if (Config.DisableSnowBalls)
e.Object.SetDefaults(0);
}
void OnNpcSetDefaults(SetDefaultsEventArgs<NPC, int> e)
{
if (TShock.Itembans.ItemIsBanned(e.Object.name, null) )
{
e.Object.SetDefaults(0);
}
}
/// <summary>
@ -937,12 +1016,12 @@ namespace TShockAPI
return true;
}
if (type == 17 && !player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Dirt Wand")) //Dirt Wand Projectile
if (type == 17 && !player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Dirt Rod", player)) //Dirt Rod Projectile
{
return true;
}
if ((type == 42 || type == 65 || type == 68) && !player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Sandgun")) //Sandgun Projectiles
if ((type == 42 || type == 65 || type == 68) && !player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned("Sandgun", player)) //Sandgun Projectiles
{
return true;
}
@ -950,7 +1029,7 @@ namespace TShockAPI
Projectile proj = new Projectile();
proj.SetDefaults(type);
if (!player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned(proj.name))
if (!player.Group.HasPermission(Permissions.usebanneditem) && TShock.Itembans.ItemIsBanned(proj.name, player))
{
return true;
}
@ -963,12 +1042,17 @@ namespace TShockAPI
return false;
}
public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY)
public static bool CheckRangePermission(TSPlayer player, int x, int y, int range = 32)
{
if (TShock.Config.RangeChecks && ((Math.Abs(player.TileX - tileX) > 32) || (Math.Abs(player.TileY - tileY) > 32)))
if (TShock.Config.RangeChecks && ((Math.Abs(player.TileX - x) > 32) || (Math.Abs(player.TileY - y) > 32)))
{
return true;
}
return false;
}
public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY)
{
if (!player.Group.HasPermission(Permissions.canbuild))
{
player.SendMessage("You do not have permission to build!", Color.Red);
@ -1078,12 +1162,6 @@ namespace TShockAPI
check = false;
}
if (player.TPlayer.statManaMax > playerData.maxMana)
{
player.SendMessage("Error: Your max mana exceeded (" + playerData.maxMana + ") which is stored on server", Color.Cyan);
check = false;
}
Item[] inventory = player.TPlayer.inventory;
Item[] armor = player.TPlayer.armor;
for (int i = 0; i < NetItem.maxNetInventory; i++)
@ -1164,30 +1242,13 @@ namespace TShockAPI
check = true;
if (player.IgnoreActionsForInventory)
check = true;
if (player.IgnoreActionsForCheating)
if (player.IgnoreActionsForCheating != "none")
check = true;
if (!player.IsLoggedIn && Config.RequireLogin)
check = true;
return check;
}
public static bool CheckPlayerCollision(int x, int y)
{
if (x + 1 <= Main.maxTilesX && y + 3 <= Main.maxTilesY
&& x >= 0 && y >= 0)
{
for (int i = x; i < x + 2; i++)
{
for (int h = y; h < y + 4; h++)
{
if (!Main.tile[i, h].active || !Main.tileSolid[Main.tile[i, h].type])
return false;
}
}
}
else
return false;
return true;
}
public void OnConfigRead(ConfigFile file)
{
NPC.defaultMaxSpawns = file.DefaultMaximumSpawns;
@ -1204,6 +1265,10 @@ namespace TShockAPI
Netplay.serverPort = file.ServerPort;
}
if (file.MaxSlots > 235)
file.MaxSlots = 235;
Main.maxNetPlayers = file.MaxSlots + 20;
Netplay.password = "";
Netplay.spamCheck = false;
RconHandler.Password = file.RconPassword;

View file

@ -125,6 +125,7 @@
<Compile Include="Rest\RestObject.cs" />
<Compile Include="Rest\RestVerbs.cs" />
<Compile Include="Rest\SecureRest.cs" />
<Compile Include="StatTracker.cs" />
<Compile Include="Utils.cs" />
<Compile Include="TShock.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -181,7 +182,7 @@
</PropertyGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" BuildVersion_BuildAction="Both" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_StartDate="2011/6/17" BuildVersion_IncrementBeforeBuild="False" />
<UserProperties BuildVersion_IncrementBeforeBuild="False" BuildVersion_StartDate="2011/6/17" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_BuildAction="Both" BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View file

@ -468,6 +468,7 @@ namespace TShockAPI
{
foo = foo.Replace("%map%", Main.worldName);
foo = foo.Replace("%players%", GetPlayers());
foo = SanitizeString(foo);
if (foo.Substring(0, 1) == "%" && foo.Substring(12, 1) == "%") //Look for a beginning color code.
{
string possibleColor = foo.Substring(0, 13);
@ -508,7 +509,7 @@ namespace TShockAPI
return TShock.Groups.groups[i];
}
}
return new Group("default");
return new Group(TShock.Config.DefaultGuestGroupName);
}
/// <summary>
@ -611,5 +612,16 @@ namespace TShockAPI
}
return 1000;
}
public string SanitizeString(string str)
{
var returnstr = str.ToCharArray();
for (int i = 0; i < str.Length; i++)
{
if (!ValidString(str[i].ToString()))
returnstr[i] = ' ';
}
return new string(returnstr);
}
}
}