diff --git a/.idea/.idea.TShock/.idea/indexLayout.xml b/.idea/.idea.TShock/.idea/indexLayout.xml new file mode 100644 index 000000000..7b08163ce --- /dev/null +++ b/.idea/.idea.TShock/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.TShock/.idea/projectSettingsUpdater.xml b/.idea/.idea.TShock/.idea/projectSettingsUpdater.xml new file mode 100644 index 000000000..4bb9f4d2a --- /dev/null +++ b/.idea/.idea.TShock/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.TShock/.idea/vcs.xml b/.idea/.idea.TShock/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/.idea.TShock/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/.idea.TShock/.idea/workspace.xml b/.idea/.idea.TShock/.idea/workspace.xml new file mode 100644 index 000000000..213339039 --- /dev/null +++ b/.idea/.idea.TShock/.idea/workspace.xml @@ -0,0 +1,142 @@ + + + + TShockInstaller/TShockInstaller.csproj + TShockLauncher/TShockLauncher.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1706669685380 + + + + + + + + \ No newline at end of file diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index d39b84e0c..449b360ea 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -28,7 +28,7 @@ You should have received a copy of the GNU General Public License using Terraria; using Terraria.ID; using Terraria.Localization; -using TShockAPI.DB; +using TShockAPI.Database; using TerrariaApi.Server; using TShockAPI.Hooks; using Terraria.GameContent.Events; @@ -1038,7 +1038,7 @@ private static void RegisterUser(CommandArgs args) return; } - account.Group = TShock.Config.Settings.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB. --Why? + account.Group = TShock.Config.Settings.DefaultRegistrationGroupName; // FIXME -- we should get this from the Database. --Why? account.UUID = args.Player.UUID; if (TShock.UserAccounts.GetUserAccountByName(account.Name) == null && account.Name != TSServerPlayer.AccountName) // Cheap way of checking for existance of a user @@ -1458,7 +1458,7 @@ void MoreHelp(string cmd) void DisplayBanDetails(Ban ban) { - args.Player.SendMessage(GetString($"{"Ban Details".Color(Utils.BoldHighlight)} - Ticket Number: {ban.TicketNumber.Color(Utils.GreenHighlight)}"), Color.White); + args.Player.SendMessage(GetString($"{"Ban Details".Color(Utils.BoldHighlight)} - Ticket Number: {ban.BanId.Color(Utils.GreenHighlight)}"), Color.White); args.Player.SendMessage(GetString($"{"Identifier:".Color(Utils.BoldHighlight)} {ban.Identifier}"), Color.White); args.Player.SendMessage(GetString($"{"Reason:".Color(Utils.BoldHighlight)} {ban.Reason}"), Color.White); args.Player.SendMessage(GetString($"{"Banned by:".Color(Utils.BoldHighlight)} {ban.BanningUser.Color(Utils.GreenHighlight)} on {ban.BanDateTime.ToString("yyyy/MM/dd").Color(Utils.RedHighlight)} ({ban.GetPrettyTimeSinceBanString().Color(Utils.YellowHighlight)} ago)"), Color.White); @@ -1487,7 +1487,7 @@ AddBanResult DoBan(string ident, string reason, DateTime expiration) AddBanResult banResult = TShock.Bans.InsertBan(ident, reason, args.Player.Account.Name, DateTime.UtcNow, expiration); if (banResult.Ban != null) { - args.Player.SendSuccessMessage(GetString($"Ban added. Ticket Number {banResult.Ban.TicketNumber.Color(Utils.GreenHighlight)} was created for identifier {ident.Color(Utils.WhiteHighlight)}.")); + args.Player.SendSuccessMessage(GetString($"Ban added. Ticket Number {banResult.Ban.BanId.Color(Utils.GreenHighlight)} was created for identifier {ident.Color(Utils.WhiteHighlight)}.")); } else { @@ -1610,7 +1610,7 @@ void AddBan() if (banResult?.Ban != null) { - player.Disconnect(GetString($"#{banResult.Ban.TicketNumber} - You have been banned: {banResult.Ban.Reason}.")); + player.Disconnect(GetString($"#{banResult.Ban.BanId} - You have been banned: {banResult.Ban.Reason}.")); } } diff --git a/TShockAPI/Configuration/TShockConfig.cs b/TShockAPI/Configuration/TShockConfig.cs index 79c374b25..b40354b26 100644 --- a/TShockAPI/Configuration/TShockConfig.cs +++ b/TShockAPI/Configuration/TShockConfig.cs @@ -466,6 +466,17 @@ public class TShockSettings public bool DisableCustomDeathMessages = true; #endregion + #region MongoDB Settings + + [Description("The connection string for MongoDB")] + public string MongoConnectionString = "required"; + + [Description("The global database, or default. This database will be used to synchronize data between servers.")] + public string DefaultGlobalDatabase = "tsd"; + + [Description("The local database, essentially replacing a server's SQlite databases.")] + public string LocalDatabase = "server1"; + #endregion #region Chat Settings @@ -493,7 +504,7 @@ public class TShockSettings /// The superadmin chat prefix. [Description("The superadmin chat prefix.")] - public string SuperAdminChatPrefix = GetString("(Super Admin) "); + public string SuperAdminChatPrefix = GetString("[Admin] "); /// The superadmin chat suffix. [Description("The superadmin chat suffix.")] @@ -526,43 +537,6 @@ public class TShockSettings #endregion - #region MySQL Settings - - /// The type of database to use when storing data (either "sqlite" or "mysql"). - [Description("The type of database to use when storing data (either \"sqlite\" or \"mysql\").")] - public string StorageType = "sqlite"; - - /// The path of sqlite db. - [Description("The path of sqlite db.")] - public string SqliteDBPath = "tshock.sqlite"; - - /// The MySQL hostname and port to direct connections to. - [Description("The MySQL hostname and port to direct connections to.")] - public string MySqlHost = "localhost:3306"; - - /// The database name to connect to when using MySQL as the database type. - [Description("The database name to connect to when using MySQL as the database type.")] - public string MySqlDbName = ""; - - /// The username used when connecting to a MySQL database. - [Description("The username used when connecting to a MySQL database.")] - public string MySqlUsername = ""; - - /// The password used when connecting to a MySQL database. - [Description("The password used when connecting to a MySQL database.")] - public string MySqlPassword = ""; - - /// Whether or not to save logs to the SQL database instead of a text file. - [Description("Whether or not to save logs to the SQL database instead of a text file.\nDefault = false.")] - public bool UseSqlLogs = false; - - /// Number of times the SQL log must fail to insert logs before falling back to the text log. - [Description("Number of times the SQL log must fail to insert logs before falling back to the text log.")] - public int RevertToTextLogsOnSqlFailures = 10; - - #endregion - - #region REST API Settings /// Enable or disable the REST API. diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs deleted file mode 100644 index 428921006..000000000 --- a/TShockAPI/DB/BanManager.cs +++ /dev/null @@ -1,776 +0,0 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011-2019 Pryaxis & TShock Contributors - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -using System; -using System.Linq; -using System.Collections.Generic; -using System.Data; -using MySql.Data.MySqlClient; -using System.Collections.ObjectModel; - -namespace TShockAPI.DB -{ - /// - /// Class that manages bans. - /// - public class BanManager - { - private IDbConnection database; - - private Dictionary _bans; - - /// - /// Readonly dictionary of Bans, keyed on ban ticket number. - /// - public ReadOnlyDictionary Bans => new ReadOnlyDictionary(_bans); - - /// - /// Event invoked when a ban is checked for validity - /// - public static event EventHandler OnBanValidate; - /// - /// Event invoked before a ban is added - /// - public static event EventHandler OnBanPreAdd; - /// - /// Event invoked after a ban is added - /// - public static event EventHandler OnBanPostAdd; - - /// - /// Initializes a new instance of the class. - /// - /// A valid connection to the TShock database - public BanManager(IDbConnection db) - { - database = db; - - var table = new SqlTable("PlayerBans", - new SqlColumn("TicketNumber", MySqlDbType.Int32) { Primary = true, AutoIncrement = true }, - new SqlColumn("Identifier", MySqlDbType.Text), - new SqlColumn("Reason", MySqlDbType.Text), - new SqlColumn("BanningUser", MySqlDbType.Text), - new SqlColumn("Date", MySqlDbType.Int64), - new SqlColumn("Expiration", MySqlDbType.Int64) - ); - var creator = new SqlTableCreator(db, - db.GetSqlType() == SqlType.Sqlite - ? (IQueryBuilder)new SqliteQueryCreator() - : new MysqlQueryCreator()); - try - { - creator.EnsureTableStructure(table); - } - catch (DllNotFoundException) - { - System.Console.WriteLine(GetString("Possible problem with your database - is Sqlite3.dll present?")); - throw new Exception(GetString("Could not find a database library (probably Sqlite3.dll)")); - } - - EnsureBansCollection(); - TryConvertBans(); - - OnBanValidate += BanValidateCheck; - OnBanPreAdd += BanAddedCheck; - } - - /// - /// Ensures the collection is ready to use. - /// - private void EnsureBansCollection() - { - if (_bans == null) - { - _bans = RetrieveAllBans().ToDictionary(b => b.TicketNumber); - } - } - - /// - /// Converts bans from the old ban system to the new. - /// - public void TryConvertBans() - { - int res; - if (database.GetSqlType() == SqlType.Mysql) - { - res = database.QueryScalar("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = @0 and table_name = 'Bans'", TShock.Config.Settings.MySqlDbName); - } - else - { - res = database.QueryScalar("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name = 'Bans'"); - } - - if (res != 0) - { - var bans = new List(); - using (var reader = database.QueryReader("SELECT * FROM Bans")) - { - while (reader.Read()) - { - var ip = reader.Get("IP"); - var uuid = reader.Get("UUID"); - var account = reader.Get("AccountName"); - var reason = reader.Get("Reason"); - var banningUser = reader.Get("BanningUser"); - var date = reader.Get("Date"); - var expiration = reader.Get("Expiration"); - - if (!DateTime.TryParse(date, out DateTime start)) - { - start = DateTime.UtcNow; - } - - if (!DateTime.TryParse(expiration, out DateTime end)) - { - end = DateTime.MaxValue; - } - - if (!string.IsNullOrWhiteSpace(ip)) - { - bans.Add(new BanPreAddEventArgs - { - Identifier = $"{Identifier.IP}{ip}", - Reason = reason, - BanningUser = banningUser, - BanDateTime = start, - ExpirationDateTime = end - }); - } - - if (!string.IsNullOrWhiteSpace(account)) - { - bans.Add(new BanPreAddEventArgs - { - Identifier = $"{Identifier.Account}{account}", - Reason = reason, - BanningUser = banningUser, - BanDateTime = start, - ExpirationDateTime = end - }); - } - - if (!string.IsNullOrWhiteSpace(uuid)) - { - bans.Add(new BanPreAddEventArgs - { - Identifier = $"{Identifier.UUID}{uuid}", - Reason = reason, - BanningUser = banningUser, - BanDateTime = start, - ExpirationDateTime = end - }); - } - } - } - - foreach (var ban in bans) - InsertBan(ban); - - database.Query("DROP TABLE Bans"); - } - } - - internal bool CheckBan(TSPlayer player) - { - List identifiers = new List - { - $"{Identifier.UUID}{player.UUID}", - $"{Identifier.Name}{player.Name}", - $"{Identifier.IP}{player.IP}" - }; - - if (player.Account != null) - { - identifiers.Add($"{Identifier.Account}{player.Account.Name}"); - } - - Ban ban = TShock.Bans.Bans.FirstOrDefault(b => identifiers.Contains(b.Value.Identifier) && TShock.Bans.IsValidBan(b.Value, player)).Value; - - if (ban != null) - { - if (ban.ExpirationDateTime == DateTime.MaxValue) - { - player.Disconnect(GetParticularString("{0} is ban number, {1} is ban reason", $"#{ban.TicketNumber} - You are banned: {ban.Reason}")); - return true; - } - - TimeSpan ts = ban.ExpirationDateTime - DateTime.UtcNow; - player.Disconnect(GetParticularString("{0} is ban number, {1} is ban reason, {2} is a timestamp", $"#{ban.TicketNumber} - You are banned: {ban.Reason} ({ban.GetPrettyExpirationString()} remaining)")); - return true; - } - - return false; - } - - /// - /// Determines whether or not a ban is valid - /// - /// - /// - /// - public bool IsValidBan(Ban ban, TSPlayer player) - { - BanEventArgs args = new BanEventArgs - { - Ban = ban, - Player = player - }; - - OnBanValidate?.Invoke(this, args); - - return args.Valid; - } - - internal void BanValidateCheck(object sender, BanEventArgs args) - { - //Only perform validation if the event has not been cancelled before we got here - if (args.Valid) - { - //We consider a ban to be valid if the start time is before now and the end time is after now - args.Valid = (DateTime.UtcNow > args.Ban.BanDateTime && DateTime.UtcNow < args.Ban.ExpirationDateTime); - } - } - - internal void BanAddedCheck(object sender, BanPreAddEventArgs args) - { - //Only perform validation if the event has not been cancelled before we got here - if (args.Valid) - { - //We consider a ban valid to add if no other *current* bans exist for the identifier provided. - //E.g., if a previous ban has expired, a new ban is valid. - //However, if a previous ban on the provided identifier is still in effect, a new ban is not valid - args.Valid = !Bans.Any(b => b.Value.Identifier == args.Identifier && b.Value.ExpirationDateTime > DateTime.UtcNow); - args.Message = args.Valid ? null : GetString("The ban is invalid because a current ban for this identifier already exists."); - } - } - - /// - /// Adds a new ban for the given identifier. Returns a Ban object if the ban was added, else null - /// - /// - /// - /// - /// - /// - /// - public AddBanResult InsertBan(string identifier, string reason, string banningUser, DateTime fromDate, DateTime toDate) - { - BanPreAddEventArgs args = new BanPreAddEventArgs - { - Identifier = identifier, - Reason = reason, - BanningUser = banningUser, - BanDateTime = fromDate, - ExpirationDateTime = toDate - }; - return InsertBan(args); - } - - /// - /// Adds a new ban for the given data. Returns a Ban object if the ban was added, else null - /// - /// A predefined instance of - /// - public AddBanResult InsertBan(BanPreAddEventArgs args) - { - OnBanPreAdd?.Invoke(this, args); - - if (!args.Valid) - { - string message = args.Message ?? GetString("The ban was not valid for an unknown reason."); - return new AddBanResult { Message = message }; - } - - string query = "INSERT INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4);"; - - if (database.GetSqlType() == SqlType.Mysql) - { - query += "SELECT LAST_INSERT_ID();"; - } - else - { - query += "SELECT CAST(last_insert_rowid() as INT);"; - } - - int ticketId = database.QueryScalar(query, args.Identifier, args.Reason, args.BanningUser, args.BanDateTime.Ticks, args.ExpirationDateTime.Ticks); - - if (ticketId == 0) - { - return new AddBanResult { Message = GetString("Inserting the ban into the database failed.") }; - } - - Ban b = new Ban(ticketId, args.Identifier, args.Reason, args.BanningUser, args.BanDateTime, args.ExpirationDateTime); - _bans.Add(ticketId, b); - - OnBanPostAdd?.Invoke(this, new BanEventArgs { Ban = b }); - - return new AddBanResult { Ban = b }; - } - - /// - /// Attempts to remove a ban. Returns true if the ban was removed or expired. False if the ban could not be removed or expired - /// - /// The ticket number of the ban to change - /// If true, deletes the ban from the database. If false, marks the expiration time as now, rendering the ban expired. Defaults to false - /// - public bool RemoveBan(int ticketNumber, bool fullDelete = false) - { - int rowsModified; - if (fullDelete) - { - rowsModified = database.Query("DELETE FROM PlayerBans WHERE TicketNumber=@0", ticketNumber); - _bans.Remove(ticketNumber); - } - else - { - rowsModified = database.Query("UPDATE PlayerBans SET Expiration=@0 WHERE TicketNumber=@1", DateTime.UtcNow.Ticks, ticketNumber); - _bans[ticketNumber].ExpirationDateTime = DateTime.UtcNow; - } - - return rowsModified > 0; - } - - /// - /// Retrieves a single ban from a ban's ticket number - /// - /// - /// - public Ban GetBanById(int id) - { - if (Bans.ContainsKey(id)) - { - return Bans[id]; - } - - using (var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE TicketNumber=@0", id)) - { - if (reader.Read()) - { - var ticketNumber = reader.Get("TicketNumber"); - var identifier = reader.Get("Identifier"); - var reason = reader.Get("Reason"); - var banningUser = reader.Get("BanningUser"); - var date = reader.Get("Date"); - var expiration = reader.Get("Expiration"); - - return new Ban(ticketNumber, identifier, reason, banningUser, date, expiration); - } - } - - return null; - } - - /// - /// Retrieves an enumerable of all bans for a given identifier - /// - /// Identifier to search with - /// Whether or not to exclude expired bans - /// - public IEnumerable RetrieveBansByIdentifier(string identifier, bool currentOnly = true) - { - string query = "SELECT * FROM PlayerBans WHERE Identifier=@0"; - if (currentOnly) - { - query += $" AND Expiration > {DateTime.UtcNow.Ticks}"; - } - - using (var reader = database.QueryReader(query, identifier)) - { - while (reader.Read()) - { - var ticketNumber = reader.Get("TicketNumber"); - var ident = reader.Get("Identifier"); - var reason = reader.Get("Reason"); - var banningUser = reader.Get("BanningUser"); - var date = reader.Get("Date"); - var expiration = reader.Get("Expiration"); - - yield return new Ban(ticketNumber, ident, reason, banningUser, date, expiration); - } - } - } - - /// - /// Retrieves an enumerable of bans for a given set of identifiers - /// - /// Whether or not to exclude expired bans - /// - /// - public IEnumerable GetBansByIdentifiers(bool currentOnly = true, params string[] identifiers) - { - //Generate a sequence of '@0, @1, @2, ... etc' - var parameters = string.Join(", ", Enumerable.Range(0, identifiers.Count()).Select(p => $"@{p}")); - - string query = $"SELECT * FROM PlayerBans WHERE Identifier IN ({parameters})"; - if (currentOnly) - { - query += $" AND Expiration > {DateTime.UtcNow.Ticks}"; - } - - using (var reader = database.QueryReader(query, identifiers)) - { - while (reader.Read()) - { - var ticketNumber = reader.Get("TicketNumber"); - var identifier = reader.Get("Identifier"); - var reason = reader.Get("Reason"); - var banningUser = reader.Get("BanningUser"); - var date = reader.Get("Date"); - var expiration = reader.Get("Expiration"); - - yield return new Ban(ticketNumber, identifier, reason, banningUser, date, expiration); - } - } - } - - /// - /// Retrieves a list of bans from the database, sorted by their addition date from newest to oldest - /// - public IEnumerable RetrieveAllBans() => RetrieveAllBansSorted(BanSortMethod.AddedNewestToOldest); - - /// - /// Retrieves an enumerable of s from the database, sorted using the provided sort method - /// - /// - /// - public IEnumerable RetrieveAllBansSorted(BanSortMethod sortMethod) - { - List banlist = new List(); - try - { - var orderBy = SortToOrderByMap[sortMethod]; - using (var reader = database.QueryReader($"SELECT * FROM PlayerBans ORDER BY {orderBy}")) - { - while (reader.Read()) - { - var ticketNumber = reader.Get("TicketNumber"); - var identifier = reader.Get("Identifier"); - var reason = reader.Get("Reason"); - var banningUser = reader.Get("BanningUser"); - var date = reader.Get("Date"); - var expiration = reader.Get("Expiration"); - - var ban = new Ban(ticketNumber, identifier, reason, banningUser, date, expiration); - banlist.Add(ban); - } - } - } - catch (Exception ex) - { - TShock.Log.Error(ex.ToString()); - Console.WriteLine(ex.StackTrace); - } - - return banlist; - } - - /// - /// Removes all bans from the database - /// - /// true, if bans were cleared, false otherwise. - public bool ClearBans() - { - try - { - return database.Query("DELETE FROM PlayerBans") != 0; - } - catch (Exception ex) - { - TShock.Log.Error(ex.ToString()); - } - return false; - } - - internal Dictionary SortToOrderByMap = new Dictionary - { - { BanSortMethod.AddedNewestToOldest, "Date DESC" }, - { BanSortMethod.AddedOldestToNewest, "Date ASC" }, - { BanSortMethod.ExpirationSoonestToLatest, "Expiration ASC" }, - { BanSortMethod.ExpirationLatestToSoonest, "Expiration DESC" } - }; - } - - /// - /// Enum containing sort options for ban retrieval - /// - public enum BanSortMethod - { - /// - /// Bans will be sorted on expiration date, from soonest to latest - /// - ExpirationSoonestToLatest, - /// - /// Bans will be sorted on expiration date, from latest to soonest - /// - ExpirationLatestToSoonest, - /// - /// Bans will be sorted by the date they were added, from newest to oldest - /// - AddedNewestToOldest, - /// - /// Bans will be sorted by the date they were added, from oldest to newest - /// - AddedOldestToNewest, - /// - /// Bans will be sorted by their ticket number - /// - TicketNumber - } - - /// - /// Result of an attempt to add a ban - /// - public class AddBanResult - { - /// - /// Message generated from the attempt - /// - public string Message { get; set; } - /// - /// Ban object generated from the attempt, or null if the attempt failed - /// - public Ban Ban { get; set; } - } - - /// - /// Event args used for completed bans - /// - public class BanEventArgs : EventArgs - { - /// - /// Complete ban object - /// - public Ban Ban { get; set; } - - /// - /// Player ban is being applied to - /// - public TSPlayer Player { get; set; } - - /// - /// Whether or not the operation should be considered to be valid - /// - public bool Valid { get; set; } = true; - } - - /// - /// Event args used for ban data prior to a ban being formalized - /// - public class BanPreAddEventArgs : EventArgs - { - /// - /// An identifiable piece of information to ban - /// - public string Identifier { get; set; } - - /// - /// Gets or sets the ban reason. - /// - /// The ban reason. - public string Reason { get; set; } - - /// - /// Gets or sets the name of the user who added this ban entry. - /// - /// The banning user. - public string BanningUser { get; set; } - - /// - /// DateTime from which the ban will take effect - /// - public DateTime BanDateTime { get; set; } - - /// - /// DateTime at which the ban will end - /// - public DateTime ExpirationDateTime { get; set; } - - /// - /// Whether or not the operation should be considered to be valid - /// - public bool Valid { get; set; } = true; - - /// - /// Optional message to explain why the event was invalidated, if it was - /// - public string Message { get; set; } - } - - /// - /// Describes an identifier used by the ban system - /// - public class Identifier - { - /// - /// Identifiers currently registered - /// - public static List Available = new List(); - - /// - /// The prefix of the identifier. E.g, 'ip:' - /// - public string Prefix { get; } - /// - /// Short description of the identifier and its basic usage - /// - public string Description { get; set; } - - /// - /// IP identifier - /// - public static Identifier IP = Register("ip:", GetString($"An identifier for an IP Address in octet format. e.g., '{"127.0.0.1".Color(Utils.RedHighlight)}'.")); - /// - /// UUID identifier - /// - public static Identifier UUID = Register("uuid:", GetString("An identifier for a UUID.")); - /// - /// Player name identifier - /// - public static Identifier Name = Register("name:", GetString("An identifier for a character name.")); - /// - /// User account identifier - /// - public static Identifier Account = Register("acc:", GetString("An identifier for a TShock User Account name.")); - - private Identifier(string prefix, string description) - { - Prefix = prefix; - Description = description; - } - - /// - /// Returns the identifier's prefix - /// - /// - public override string ToString() - { - return Prefix; - } - - /// - /// Registers a new identifier with the given prefix and description - /// - /// - /// - public static Identifier Register(string prefix, string description) - { - var ident = new Identifier(prefix, description); - Available.Add(ident); - - return ident; - } - } - - /// - /// Model class that represents a ban entry in the TShock database. - /// - public class Ban - { - /// - /// A unique ID assigned to this ban - /// - public int TicketNumber { get; set; } - - /// - /// An identifiable piece of information to ban - /// - public string Identifier { get; set; } - - /// - /// Gets or sets the ban reason. - /// - /// The ban reason. - public string Reason { get; set; } - - /// - /// Gets or sets the name of the user who added this ban entry. - /// - /// The banning user. - public string BanningUser { get; set; } - - /// - /// DateTime from which the ban will take effect - /// - public DateTime BanDateTime { get; set; } - - /// - /// DateTime at which the ban will end - /// - public DateTime ExpirationDateTime { get; set; } - - /// - /// Returns a string in the format dd:mm:hh:ss indicating the time until the ban expires. - /// If the ban is not set to expire (ExpirationDateTime == DateTime.MaxValue), returns the string 'Never' - /// - /// - public string GetPrettyExpirationString() - { - if (ExpirationDateTime == DateTime.MaxValue) - { - return "Never"; - } - - TimeSpan ts = (ExpirationDateTime - DateTime.UtcNow).Duration(); // Use duration to avoid pesky negatives for expired bans - return $"{ts.Days:00}:{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}"; - } - - /// - /// Returns a string in the format dd:mm:hh:ss indicating the time elapsed since the ban was added. - /// - /// - public string GetPrettyTimeSinceBanString() - { - TimeSpan ts = (DateTime.UtcNow - BanDateTime).Duration(); - return $"{ts.Days:00}:{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}"; - } - - /// - /// Initializes a new instance of the class. - /// - /// Unique ID assigned to the ban - /// Identifier to apply the ban to - /// Reason for the ban - /// Account name that executed the ban - /// System ticks at which the ban began - /// System ticks at which the ban will end - public Ban(int ticketNumber, string identifier, string reason, string banningUser, long start, long end) - : this(ticketNumber, identifier, reason, banningUser, new DateTime(start), new DateTime(end)) - { - } - - - /// - /// Initializes a new instance of the class. - /// - /// Unique ID assigned to the ban - /// Identifier to apply the ban to - /// Reason for the ban - /// Account name that executed the ban - /// DateTime at which the ban will start - /// DateTime at which the ban will end - public Ban(int ticketNumber, string identifier, string reason, string banningUser, DateTime start, DateTime end) - { - TicketNumber = ticketNumber; - Identifier = identifier; - Reason = reason; - BanningUser = banningUser; - BanDateTime = start; - ExpirationDateTime = end; - } - } -} diff --git a/TShockAPI/Database/BanManager.cs b/TShockAPI/Database/BanManager.cs new file mode 100644 index 000000000..f86b59ed8 --- /dev/null +++ b/TShockAPI/Database/BanManager.cs @@ -0,0 +1,369 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2019 Pryaxis & TShock Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Data; +using MySql.Data.MySqlClient; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using MongoDB.Entities; +using Entity = Terraria.Entity; + +namespace TShockAPI.Database +{ + /// + /// Class that manages bans. + /// + public static class BanManager + { + /// + /// Returns the number of bans that already exist + /// + /// Total number of bans + public static async Task CountBans() + { + return await DB.CountAsync(); + } + + /// + /// Event invoked after a ban is added + /// + public static event EventHandler OnBanAdd; + + /// + /// Event invoked after a user is unbanned + /// + public static event EventHandler OnBanRemove; + + + internal static async Task IsPlayerBanned(TSPlayer player) + { + // Attempt to find a ban by account name if the player is logged in, + // otherwise, find by IP address or UUID. + var banFilter = player.IsLoggedIn + ? Builders.Filter.Eq(b => b.AccountName, player.Account.Name) + : Builders.Filter.Or( + Builders.Filter.Eq(b => b.IpAddress, player.IP), + Builders.Filter.Eq(b => b.Uuid, player.UUID)); + + // Execute the database query to find a matching ban. + var ban = await DB.Find().Match(banFilter).ExecuteFirstAsync(); + + // If no ban is found, the player is not banned. + if (ban == null) return false; + + // Disconnect the player with an appropriate message based on the ban expiration. + var disconnectMessage = GetBanDisconnectMessage(ban); + player.Disconnect(disconnectMessage); + return true; + } + + private static string GetBanDisconnectMessage(Ban ban) + { + var baseMessage = $"#{ban.BanId} - You are banned: {ban.Reason}"; + + // If the ban is permanent. + if (ban.ExpirationDateTime == DateTime.MaxValue) + { + return GetParticularString("{0} is ban number, {1} is ban reason", baseMessage); + } + + // If the ban has an expiration date. + var timeRemaining = ban.ExpirationDateTime - DateTime.UtcNow; + var prettyExpiration = + ban.GetPrettyExpirationString(); // Assuming this method exists and formats the expiration nicely. + return GetParticularString("{0} is ban number, {1} is ban reason, {2} is a timestamp", + $"{baseMessage} ({prettyExpiration} remaining)"); + } + + /// + /// Retrieves a single ban from a ban's ID + /// + /// The ban identifier + /// The requested ban + public static async Task GetBanById(int id) + { + return await DB.Find() + .Match(x => x.BanId == id) + .ExecuteFirstAsync(); + } + + /// + /// Retrieves a list of bans from the database, sorted by their addition date from newest to oldest + /// + public static async Task> RetrieveAllBans() => await RetrieveAllBansSorted(BanSortMethod.DateBanned, true); + + /// + /// Retrieves an enumerable of Bans from the database, sorted using the provided sort method + /// + /// The method to sort the bans. + /// Whether the sort should be in descending order. + /// A sorted enumerable of Ban objects. + public static async Task> RetrieveAllBansSorted(BanSortMethod sortMethod, bool descending = true) + { + var sortDefinition = descending + ? Builders.Sort.Descending(GetSortField(sortMethod)) + : Builders.Sort.Ascending(GetSortField(sortMethod)); + + var banList = await DB.Find().Sort(x=>sortDefinition).ExecuteAsync(); + return banList; + } + + private static string GetSortField(BanSortMethod sortMethod) + { + return sortMethod switch + { + BanSortMethod.TicketNumber => nameof(BanSortMethod.TicketNumber), + BanSortMethod.DateBanned => nameof(BanSortMethod.DateBanned), + BanSortMethod.EndDate => nameof(BanSortMethod.EndDate), + _ => throw new ArgumentOutOfRangeException(nameof(sortMethod), $"Not expected sort method value: {sortMethod}"), + }; + } + + public static Ban CreateBan(BanType type, string value, string reason, UserAccount banningUser, DateTime start, + DateTime? endDate = null) + { + Ban ban = new() + { + Reason = reason, + BanningUser = banningUser.Name, + BanDateTime = start, + ExpirationDateTime = endDate ?? DateTime.MaxValue + }; + + switch (type) + { + case BanType.Uuid: + { + ban.Uuid = value; + break; + } + case BanType.AccountName: + { + ban.AccountName = value; + break; + } + case BanType.IpAddress: + { + ban.IpAddress = value; + break; + } + default: throw new Exception("Invalid ban type!"); + } + + TShock.Log.Info("A new ban has been created for: "); + return ban; + } + + public static bool UnbanPlayer(string accountName) + { + + } + + + /// + /// Removes all bans from the database + /// + public static async Task ClearBans() => await DB.DeleteAsync(Builders.Filter.Empty); + + } + + /// + /// Enum containing sort options for ban retrieval + /// + public enum BanSortMethod + { + DateBanned, + EndDate, + TicketNumber + } + + /// + /// Result of an attempt to add a ban + /// + public class AddBanResult + { + /// + /// Message generated from the attempt + /// + public string Message { get; set; } + + /// + /// Ban object generated from the attempt, or null if the attempt failed + /// + public Ban Ban { get; set; } + } + + /// + /// Event args used for completed bans + /// + public class BanEventArgs : EventArgs + { + /// + /// Complete ban object + /// + public Ban Ban { get; set; } + + /// + /// Player ban is being applied to + /// + public TSPlayer Player { get; set; } + + /// + /// Whether or not the operation should be considered to be valid + /// + public bool Valid { get; set; } = true; + } + + /// + /// Event args used for ban data prior to a ban being formalized + /// + public class BanPreAddEventArgs : EventArgs + { + /// + /// An identifiable piece of information to ban + /// + public string Identifier { get; set; } + + /// + /// Gets or sets the ban reason. + /// + /// The ban reason. + public string Reason { get; set; } + + /// + /// Gets or sets the name of the user who added this ban entry. + /// + /// The banning user. + public string BanningUser { get; set; } + + /// + /// DateTime from which the ban will take effect + /// + public DateTime BanDateTime { get; set; } + + /// + /// DateTime at which the ban will end + /// + public DateTime ExpirationDateTime { get; set; } + + /// + /// Whether or not the operation should be considered to be valid + /// + public bool Valid { get; set; } = true; + + /// + /// Optional message to explain why the event was invalidated, if it was + /// + public string Message { get; set; } + } + + /// + /// Model class that represents a ban entry in the database. + /// + public class Ban : MongoDB.Entities.Entity + { + /// + /// A unique ID assigned to this ban + /// + public int BanId { get; set; } + + /// + /// A possible IP address we are banning + /// + public string? IpAddress { get; set; } + + /// + /// A possible UUID we are banning + /// + public string? Uuid { get; set; } + + /// + /// A possible account name we are banning + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the ban reason. + /// + /// The ban reason. + public string Reason { get; set; } + + /// + /// Gets or sets the name of the user who added this ban entry. + /// + /// The banning user. + public string BanningUser { get; set; } + + /// + /// DateTime from which the ban will take effect + /// + public DateTime BanDateTime { get; set; } + + /// + /// DateTime at which the ban will end + /// + public DateTime ExpirationDateTime { get; set; } + + /// + /// Returns whether or not the ban is still in effect + /// + [Ignore] public bool Valid => ExpirationDateTime > BanDateTime; + + /// + /// Returns a string in the format dd:mm:hh:ss indicating the time until the ban expires. + /// If the ban is not set to expire (ExpirationDateTime == DateTime.MaxValue), returns the string 'Never' + /// + /// + public string GetPrettyExpirationString() + { + if (ExpirationDateTime == DateTime.MaxValue) + { + return "Never"; + } + + TimeSpan + ts = (ExpirationDateTime - DateTime.UtcNow) + .Duration(); // Use duration to avoid pesky negatives for expired bans + return $"{ts.Days:00}:{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}"; + } + + /// + /// Returns a string in the format dd:mm:hh:ss indicating the time elapsed since the ban was added. + /// + /// + public string GetPrettyTimeSinceBanString() + { + TimeSpan ts = (DateTime.UtcNow - BanDateTime).Duration(); + return $"{ts.Days:00}:{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}"; + } + + } + + public enum BanType + { + Uuid, + AccountName, + IpAddress + } +} diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/Database/CharacterManager.cs similarity index 99% rename from TShockAPI/DB/CharacterManager.cs rename to TShockAPI/Database/CharacterManager.cs index 5a5e13a64..ec7cfabdc 100644 --- a/TShockAPI/DB/CharacterManager.cs +++ b/TShockAPI/Database/CharacterManager.cs @@ -24,7 +24,7 @@ You should have received a copy of the GNU General Public License using MySql.Data.MySqlClient; using Terraria; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class CharacterManager { diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/Database/GroupManager.cs similarity index 98% rename from TShockAPI/DB/GroupManager.cs rename to TShockAPI/Database/GroupManager.cs index 8e0e31923..d8c29b291 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/Database/GroupManager.cs @@ -24,7 +24,7 @@ You should have received a copy of the GNU General Public License using System.Linq; using MySql.Data.MySqlClient; -namespace TShockAPI.DB +namespace TShockAPI.Database { /// /// Represents the GroupManager, which is in charge of group management. @@ -196,7 +196,7 @@ public GroupManager(IDbConnection db) Permissions.createdumps)); } - // Load Permissions from the DB + // Load Permissions from the Database LoadPermisions(); Group.DefaultGroup = GetGroupByName(TShock.Config.Settings.DefaultGuestGroupName); @@ -360,7 +360,7 @@ public void UpdateGroup(string name, string parentname, string permissions, stri } } - // Ensure any group validation is also persisted to the DB. + // Ensure any group validation is also persisted to the Database. var newGroup = new Group(name, parent, chatcolor, permissions); newGroup.Prefix = prefix; newGroup.Suffix = suffix; @@ -531,7 +531,7 @@ public String AddPermissions(String name, List permissions) 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 + // Restore old permissions so Database and internal object are in a consistent state group.Permissions = oldperms; return ""; } @@ -554,7 +554,7 @@ public String DeletePermissions(String name, List permissions) 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 + // Restore old permissions so Database and internal object are in a consistent state group.Permissions = oldperms; return ""; } diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/Database/IQueryBuilder.cs similarity index 99% rename from TShockAPI/DB/IQueryBuilder.cs rename to TShockAPI/Database/IQueryBuilder.cs index c2be131d3..33735b5a9 100644 --- a/TShockAPI/DB/IQueryBuilder.cs +++ b/TShockAPI/Database/IQueryBuilder.cs @@ -24,7 +24,7 @@ You should have received a copy of the GNU General Public License using System.Text; using TShockAPI.Extensions; -namespace TShockAPI.DB +namespace TShockAPI.Database { /// /// Interface for various SQL related utilities. diff --git a/TShockAPI/DB/ItemManager.cs b/TShockAPI/Database/ItemManager.cs similarity index 99% rename from TShockAPI/DB/ItemManager.cs rename to TShockAPI/Database/ItemManager.cs index 012132dc6..21610eafc 100644 --- a/TShockAPI/DB/ItemManager.cs +++ b/TShockAPI/Database/ItemManager.cs @@ -23,7 +23,7 @@ You should have received a copy of the GNU General Public License using MySql.Data.MySqlClient; using TShockAPI.Hooks; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class ItemManager { diff --git a/TShockAPI/DB/ProjectileManager.cs b/TShockAPI/Database/ProjectileManager.cs similarity index 98% rename from TShockAPI/DB/ProjectileManager.cs rename to TShockAPI/Database/ProjectileManager.cs index 0e6cabe04..5d90219a2 100644 --- a/TShockAPI/DB/ProjectileManager.cs +++ b/TShockAPI/Database/ProjectileManager.cs @@ -23,14 +23,14 @@ You should have received a copy of the GNU General Public License using MySql.Data.MySqlClient; using TShockAPI.Hooks; -namespace TShockAPI.DB +namespace TShockAPI.Database { - public class ProjectileManagager + public class ProjectileManager { private IDbConnection database; public List ProjectileBans = new List(); - public ProjectileManagager(IDbConnection db) + public ProjectileManager(IDbConnection db) { database = db; diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/Database/RegionManager.cs similarity index 99% rename from TShockAPI/DB/RegionManager.cs rename to TShockAPI/Database/RegionManager.cs index 1a284ff30..8f47e74cf 100644 --- a/TShockAPI/DB/RegionManager.cs +++ b/TShockAPI/Database/RegionManager.cs @@ -24,7 +24,7 @@ You should have received a copy of the GNU General Public License using Terraria; using Microsoft.Xna.Framework; -namespace TShockAPI.DB +namespace TShockAPI.Database { /// /// Represents the Region database manager. diff --git a/TShockAPI/DB/RememberedPosManager.cs b/TShockAPI/Database/RememberedPosManager.cs similarity index 99% rename from TShockAPI/DB/RememberedPosManager.cs rename to TShockAPI/Database/RememberedPosManager.cs index c49c5912b..4d9857223 100644 --- a/TShockAPI/DB/RememberedPosManager.cs +++ b/TShockAPI/Database/RememberedPosManager.cs @@ -22,7 +22,7 @@ You should have received a copy of the GNU General Public License using Terraria; using Microsoft.Xna.Framework; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class RememberedPosManager { diff --git a/TShockAPI/DB/ResearchDatastore.cs b/TShockAPI/Database/ResearchDatastore.cs similarity index 97% rename from TShockAPI/DB/ResearchDatastore.cs rename to TShockAPI/Database/ResearchDatastore.cs index 0d2429d7a..2430046de 100644 --- a/TShockAPI/DB/ResearchDatastore.cs +++ b/TShockAPI/Database/ResearchDatastore.cs @@ -8,7 +8,7 @@ using Terraria; using Terraria.ID; -namespace TShockAPI.DB +namespace TShockAPI.Database { /// /// This class is used as the data interface for Journey mode research. @@ -26,7 +26,7 @@ public class ResearchDatastore private Dictionary _itemsSacrificed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// A valid connection to the TShock database public ResearchDatastore(IDbConnection db) diff --git a/TShockAPI/DB/SqlColumn.cs b/TShockAPI/Database/SqlColumn.cs similarity index 98% rename from TShockAPI/DB/SqlColumn.cs rename to TShockAPI/Database/SqlColumn.cs index 27c45435c..a3a677bd4 100644 --- a/TShockAPI/DB/SqlColumn.cs +++ b/TShockAPI/Database/SqlColumn.cs @@ -19,7 +19,7 @@ You should have received a copy of the GNU General Public License using MySql.Data.MySqlClient; using System; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class SqlColumn { diff --git a/TShockAPI/DB/SqlTable.cs b/TShockAPI/Database/SqlTable.cs similarity index 98% rename from TShockAPI/DB/SqlTable.cs rename to TShockAPI/Database/SqlTable.cs index 1f0c45936..e56e217c5 100644 --- a/TShockAPI/DB/SqlTable.cs +++ b/TShockAPI/Database/SqlTable.cs @@ -22,7 +22,7 @@ You should have received a copy of the GNU General Public License using System.Linq; using MySql.Data.MySqlClient; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class SqlTable { diff --git a/TShockAPI/DB/SqlValue.cs b/TShockAPI/Database/SqlValue.cs similarity index 98% rename from TShockAPI/DB/SqlValue.cs rename to TShockAPI/Database/SqlValue.cs index aa70e2d62..5ed0b5d43 100644 --- a/TShockAPI/DB/SqlValue.cs +++ b/TShockAPI/Database/SqlValue.cs @@ -19,7 +19,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Data; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class SqlValue { diff --git a/TShockAPI/DB/TileManager.cs b/TShockAPI/Database/TileManager.cs similarity index 99% rename from TShockAPI/DB/TileManager.cs rename to TShockAPI/Database/TileManager.cs index bd918e733..820d1a0ad 100644 --- a/TShockAPI/DB/TileManager.cs +++ b/TShockAPI/Database/TileManager.cs @@ -23,7 +23,7 @@ You should have received a copy of the GNU General Public License using MySql.Data.MySqlClient; using TShockAPI.Hooks; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class TileManager { diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/Database/UserManager.cs similarity index 99% rename from TShockAPI/DB/UserManager.cs rename to TShockAPI/Database/UserManager.cs index 0354989e7..611caacff 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/Database/UserManager.cs @@ -26,7 +26,7 @@ You should have received a copy of the GNU General Public License using BCrypt.Net; using System.Security.Cryptography; -namespace TShockAPI.DB +namespace TShockAPI.Database { /// UserAccountManager - Methods for dealing with database user accounts and other related functionality within TShock. public class UserAccountManager diff --git a/TShockAPI/DB/WarpsManager.cs b/TShockAPI/Database/WarpsManager.cs similarity index 99% rename from TShockAPI/DB/WarpsManager.cs rename to TShockAPI/Database/WarpsManager.cs index bd13ce4d1..954b2ec8f 100644 --- a/TShockAPI/DB/WarpsManager.cs +++ b/TShockAPI/Database/WarpsManager.cs @@ -25,7 +25,7 @@ You should have received a copy of the GNU General Public License using Terraria; using Microsoft.Xna.Framework; -namespace TShockAPI.DB +namespace TShockAPI.Database { public class WarpManager { diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs index b5a2c6182..df76ed5f8 100644 --- a/TShockAPI/Extensions/DbExt.cs +++ b/TShockAPI/Extensions/DbExt.cs @@ -21,7 +21,7 @@ You should have received a copy of the GNU General Public License using System.Data; using System.Diagnostics.CodeAnalysis; -namespace TShockAPI.DB +namespace TShockAPI.Database { /// /// Database extensions diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 1ef2f4f8b..ad10ca0a5 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -26,7 +26,7 @@ You should have received a copy of the GNU General Public License using System.Text; using System.Threading.Tasks; using Terraria.ID; -using TShockAPI.DB; +using TShockAPI.Database; using TShockAPI.Net; using Terraria; using Terraria.ObjectData; diff --git a/TShockAPI/Hooks/AccountHooks.cs b/TShockAPI/Hooks/AccountHooks.cs index 9c08b26dc..666d8efe3 100644 --- a/TShockAPI/Hooks/AccountHooks.cs +++ b/TShockAPI/Hooks/AccountHooks.cs @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -using TShockAPI.DB; +using TShockAPI.Database; namespace TShockAPI.Hooks { public class AccountDeleteEventArgs diff --git a/TShockAPI/Hooks/PlayerHooks.cs b/TShockAPI/Hooks/PlayerHooks.cs index 7a3e20679..b3451fae7 100644 --- a/TShockAPI/Hooks/PlayerHooks.cs +++ b/TShockAPI/Hooks/PlayerHooks.cs @@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.ComponentModel; -using TShockAPI.DB; +using TShockAPI.Database; namespace TShockAPI.Hooks { diff --git a/TShockAPI/Hooks/RegionHooks.cs b/TShockAPI/Hooks/RegionHooks.cs index 0322846ab..36270f782 100644 --- a/TShockAPI/Hooks/RegionHooks.cs +++ b/TShockAPI/Hooks/RegionHooks.cs @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -using TShockAPI.DB; +using TShockAPI.Database; namespace TShockAPI.Hooks { diff --git a/TShockAPI/ItemBans.cs b/TShockAPI/ItemBans.cs index df0d36b05..0a626dcb5 100644 --- a/TShockAPI/ItemBans.cs +++ b/TShockAPI/ItemBans.cs @@ -19,7 +19,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Linq; using Terraria.ID; -using TShockAPI.DB; +using TShockAPI.Database; using TShockAPI.Net; using Terraria; using Microsoft.Xna.Framework; diff --git a/TShockAPI/RegionHandler.cs b/TShockAPI/RegionHandler.cs index 9c61b7008..17add632b 100644 --- a/TShockAPI/RegionHandler.cs +++ b/TShockAPI/RegionHandler.cs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; -using TShockAPI.DB; +using TShockAPI.Database; using TShockAPI.Hooks; namespace TShockAPI diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index c41e7767b..b6508fd2f 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -27,7 +27,7 @@ You should have received a copy of the GNU General Public License using HttpServer; using Rests; using Terraria; -using TShockAPI.DB; +using TShockAPI.Database; using Newtonsoft.Json; namespace TShockAPI @@ -665,7 +665,7 @@ private object BanCreateV3(RestRequestArgs args) player.Kick(reason, true); } - return RestResponse(GetString($"Ban added. Ticket number: {banResult.Ban.TicketNumber}")); + return RestResponse(GetString($"Ban added. Ticket number: {banResult.Ban.BanId}")); } return RestError(GetString($"Failed to add ban. {banResult.Message}"), status: "500"); @@ -723,7 +723,7 @@ private object BanInfoV3(RestRequestArgs args) return new RestObject { - { "ticket_number", ban.TicketNumber }, + { "ticket_number", ban.BanId }, { "identifier", ban.Identifier }, { "reason", ban.Reason }, { "banning_user", ban.BanningUser }, @@ -746,7 +746,7 @@ private object BanListV3(RestRequestArgs args) banList.Add( new Dictionary { - { "ticket_number", ban.TicketNumber }, + { "ticket_number", ban.BanId }, { "identifier", ban.Identifier }, { "reason", ban.Reason }, { "banning_user", ban.BanningUser }, diff --git a/TShockAPI/Rest/SecureRest.cs b/TShockAPI/Rest/SecureRest.cs index 5d785219e..1e4f2047e 100644 --- a/TShockAPI/Rest/SecureRest.cs +++ b/TShockAPI/Rest/SecureRest.cs @@ -22,7 +22,7 @@ You should have received a copy of the GNU General Public License using System.Net; using HttpServer; using TShockAPI; -using TShockAPI.DB; +using TShockAPI.Database; using Microsoft.Xna.Framework; using Terraria; using System.Security.Cryptography; diff --git a/TShockAPI/SqlLog.cs b/TShockAPI/SqlLog.cs deleted file mode 100644 index 3e79aaaba..000000000 --- a/TShockAPI/SqlLog.cs +++ /dev/null @@ -1,358 +0,0 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011-2019 Pryaxis & TShock Contributors - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using MySql.Data.MySqlClient; -using TShockAPI.DB; - -namespace TShockAPI -{ - struct LogInfo - { - public string timestamp; - public string message; - public string caller; - public TraceLevel logLevel; - - public override string ToString() - { - return GetString("Message: {0}: {1}: {2}", - caller, logLevel.ToString().ToUpper(), message); - } - } - - /// - /// Class inheriting ILog for writing logs to TShock's SQL database - /// - public class SqlLog : ILog, IDisposable - { - private readonly IDbConnection _database; - private readonly TextLog _backupLog; - private readonly List _failures = new List(TShock.Config.Settings.RevertToTextLogsOnSqlFailures); - private bool _useTextLog; - - public string FileName { get; set; } - - /// - /// Sets the database connection and the initial log level. - /// - /// Database connection - /// File path to a backup text log in case the SQL log fails - /// - public SqlLog(IDbConnection db, string textlogFilepath, bool clearTextLog) - { - FileName = string.Format("{0}://database", db.GetSqlType()); - _database = db; - _backupLog = new TextLog(textlogFilepath, clearTextLog); - - var table = new SqlTable("Logs", - new SqlColumn("ID", MySqlDbType.Int32) {AutoIncrement = true, Primary = true}, - new SqlColumn("TimeStamp", MySqlDbType.Text), - new SqlColumn("LogLevel", MySqlDbType.Int32), - new SqlColumn("Caller", MySqlDbType.Text), - new SqlColumn("Message", MySqlDbType.Text) - ); - - var creator = new SqlTableCreator(db, - db.GetSqlType() == SqlType.Sqlite - ? (IQueryBuilder) new SqliteQueryCreator() - : new MysqlQueryCreator()); - creator.EnsureTableStructure(table); - } - - public bool MayWriteType(TraceLevel type) - { - return type != TraceLevel.Off; - } - - /// - /// Writes data to the log file. - /// - /// The message to be written. - public void Data(string message) - { - Write(message, TraceLevel.Verbose); - } - - /// - /// Writes data to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void Data(string format, params object[] args) - { - Data(string.Format(format, args)); - } - - /// - /// Writes an error to the log file. - /// - /// The message to be written. - public void Error(string message) - { - Write(message, TraceLevel.Error); - } - - /// - /// Writes an error to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void Error(string format, params object[] args) - { - Error(string.Format(format, args)); - } - - /// - /// Writes an error to the log file. - /// - /// The message to be written. - public void ConsoleError(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(message); - Console.ForegroundColor = ConsoleColor.Gray; - Write(message, TraceLevel.Error); - } - - /// - /// Writes an error to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void ConsoleError(string format, params object[] args) - { - ConsoleError(string.Format(format, args)); - } - - /// - /// Writes an error to the log file. - /// - /// The message to be written. - public void ConsoleWarn(string message) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(message); - Console.ForegroundColor = ConsoleColor.Gray; - Write(message, TraceLevel.Warning); - } - - /// - /// Writes an error to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void ConsoleWarn(string format, params object[] args) - { - ConsoleWarn(string.Format(format, args)); - } - - /// - /// Writes a warning to the log file. - /// - /// The message to be written. - public void Warn(string message) - { - Write(message, TraceLevel.Warning); - } - - /// - /// Writes a warning to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void Warn(string format, params object[] args) - { - Warn(string.Format(format, args)); - } - - /// - /// Writes an informative string to the log file. - /// - /// The message to be written. - public void Info(string message) - { - Write(message, TraceLevel.Info); - } - - /// - /// Writes an informative string to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void Info(string format, params object[] args) - { - Info(string.Format(format, args)); - } - - /// - /// Writes an informative string to the log file. Also outputs to the console. - /// - /// The message to be written. - public void ConsoleInfo(string message) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(message); - Console.ForegroundColor = ConsoleColor.Gray; - Write(message, TraceLevel.Info); - } - - /// - /// Writes an informative string to the log file. Also outputs to the console. - /// - /// The format of the message to be written. - /// The format arguments. - public void ConsoleInfo(string format, params object[] args) - { - ConsoleInfo(string.Format(format, args)); - } - - /// - /// Writes a debug string to the log file. Also outputs to the console. Requires config TShock.DebugLogs to be true. - /// - /// The message to be written. - public void ConsoleDebug(string message) - { - if (TShock.Config.Settings.DebugLogs) - { - Console.WriteLine("Debug: " + message); - Write(message, TraceLevel.Verbose); - } - } - - /// - /// Writes a debug string to the log file. Also outputs to the console. Requires config TShock.DebugLogs to be true. - /// - /// The format of the message to be written. - /// The format arguments. - public void ConsoleDebug(string format, params object[] args) - { - ConsoleDebug(string.Format(format, args)); - } - - /// - /// Writes a debug string to the log file. - /// - /// The message to be written. - public void Debug(string message) - { - if (TShock.Config.Settings.DebugLogs) - Write(message, TraceLevel.Verbose); - } - - /// - /// Writes a debug string to the log file. - /// - /// The format of the message to be written. - /// The format arguments. - public void Debug(string format, params object[] args) - { - if (TShock.Config.Settings.DebugLogs) - Debug(string.Format(format, args)); - } - - public void Write(string message, TraceLevel level) - { - if (!MayWriteType(level)) - return; - - var caller = "TShock"; - - var frame = new StackTrace().GetFrame(2); - if (frame != null) - { - var meth = frame.GetMethod(); - if (meth != null && meth.DeclaringType != null) - caller = meth.DeclaringType.Name; - } - - try - { - if (_useTextLog) - { - _backupLog.Write(message, level); - return; - } - - _database.Query("INSERT INTO Logs (TimeStamp, Caller, LogLevel, Message) VALUES (@0, @1, @2, @3)", - DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), caller, (int)level, message); - - var success = true; - while (_failures.Count > 0 && success) - { - var info = _failures.First(); - - try - { - _database.Query("INSERT INTO Logs (TimeStamp, Caller, LogLevel, Message) VALUES (@0, @1, @2, @3)", - info.timestamp, info.caller, (int)info.logLevel, info.message); - } - catch (Exception ex) - { - success = false; - _failures.Add(new LogInfo - { - caller = "TShock", - logLevel = TraceLevel.Error, - message = GetString("SQL Log insert query failed: {0}", ex), - timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) - }); - } - - if (success) - _failures.RemoveAt(0); - } - } - catch (Exception ex) - { - _backupLog.ConsoleError("SQL Log insert query failed: {0}", ex); - - _failures.Add(new LogInfo - { - logLevel = level, - message = message, - caller = caller, - timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) - }); - } - - if (_failures.Count >= TShock.Config.Settings.RevertToTextLogsOnSqlFailures) - { - _useTextLog = true; - _backupLog.ConsoleError("SQL Logging disabled due to errors. Reverting to text logging."); - - foreach (var logInfo in _failures) - { - _backupLog.Write(GetString("SQL log failed at: {0}. {1}", logInfo.timestamp, logInfo), - TraceLevel.Error); - } - _failures.Clear(); - } - } - - public void Dispose() - { - _backupLog.Dispose(); - } - } -} diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index c9194c368..e7e9b7b41 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -29,7 +29,7 @@ You should have received a copy of the GNU General Public License using Terraria.DataStructures; using Terraria.ID; using Terraria.Localization; -using TShockAPI.DB; +using TShockAPI.Database; using TShockAPI.Hooks; using TShockAPI.Net; using Timer = System.Timers.Timer; diff --git a/TShockAPI/TSServerPlayer.cs b/TShockAPI/TSServerPlayer.cs index 6c95de78e..bc0aec19b 100644 --- a/TShockAPI/TSServerPlayer.cs +++ b/TShockAPI/TSServerPlayer.cs @@ -22,7 +22,7 @@ You should have received a copy of the GNU General Public License using Terraria; using Terraria.Utilities; using TShockAPI; -using TShockAPI.DB; +using TShockAPI.Database; using Terraria.Localization; using System.Linq; using Terraria.DataStructures; diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 054e31dfa..0ee3a4332 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -35,7 +35,6 @@ You should have received a copy of the GNU General Public License using Terraria.ID; using Terraria.Localization; using TerrariaApi.Server; -using TShockAPI.DB; using TShockAPI.Hooks; using Terraria.Utilities; using Microsoft.Xna.Framework; @@ -45,11 +44,16 @@ You should have received a copy of the GNU General Public License using TShockAPI.Configuration; using Terraria.GameContent.Creative; using System.Runtime.InteropServices; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Entities; using MonoMod.Cil; using Terraria.Achievements; using Terraria.Initializers; using Terraria.UI.Chat; +using TShockAPI.Database; using TShockAPI.Modules; +using ServerApi = TerrariaApi.Server.ServerApi; namespace TShockAPI { @@ -62,20 +66,26 @@ public class TShock : TerrariaPlugin { /// VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info. public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; + /// VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions. public static readonly string VersionCodename = "Intensity"; /// SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins). public static string SavePath = "tshock"; + /// LogFormatDefault - This is the default log file naming format. Actually, this is the only log format, because it never gets set again. private const string LogFormatDefault = "yyyy-MM-dd_HH-mm-ss"; + //TODO: Set the log path in the config file. /// LogFormat - This is the log format, which is never set again. private static string LogFormat = LogFormatDefault; + /// LogPathDefault - The default log path. private const string LogPathDefault = "tshock/logs"; + /// This is the log path, which is initially set to the default log path, and then to the config file log path later. private static string LogPath = LogPathDefault; + /// LogClear - Determines whether or not the log file should be cleared on initialization. private static bool LogClear; @@ -84,58 +94,80 @@ public class TShock : TerrariaPlugin /// Players - Contains all TSPlayer objects for accessing TSPlayers currently on the server public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers]; + /// Bans - Static reference to the ban manager for accessing bans & related functions. public static BanManager Bans; + /// Warps - Static reference to the warp manager for accessing the warp system. public static WarpManager Warps; + /// Regions - Static reference to the region manager for accessing the region system. public static RegionManager Regions; + /// Backups - Static reference to the backup manager for accessing the backup system. public static BackupManager Backups; + /// Groups - Static reference to the group manager for accessing the group system. public static GroupManager Groups; + /// Users - Static reference to the user manager for accessing the user database system. public static UserAccountManager UserAccounts; + /// ProjectileBans - Static reference to the projectile ban system. - public static ProjectileManagager ProjectileBans; + public static ProjectileManager ProjectileBans; + /// TileBans - Static reference to the tile ban system. public static TileManager TileBans; + /// RememberedPos - Static reference to the remembered position manager. public static RememberedPosManager RememberedPos; + /// CharacterDB - Static reference to the SSC character manager. public static CharacterManager CharacterDB; + /// Contains the information about what research has been performed in Journey mode. public static ResearchDatastore ResearchDatastore; + /// Config - Static reference to the config system, for accessing values set in users' config files. public static TShockConfig Config { get; set; } + /// ServerSideCharacterConfig - Static reference to the server side character config, for accessing values set by users to modify SSC. public static ServerSideConfig ServerSideCharacterConfig; - /// DB - Static reference to the database. - public static IDbConnection DB; + /// OverridePort - Determines if TShock should override the server port. public static bool OverridePort; + /// Geo - Static reference to the GeoIP system which determines the location of an IP address. public static GeoIPCountry Geo; + /// RestApi - Static reference to the Rest API authentication manager. public static SecureRest RestApi; + /// RestManager - Static reference to the Rest API manager. public static RestManager RestManager; + /// Utils - Static reference to the utilities class, which contains a variety of utility functions. public static Utils Utils = Utils.Instance; + /// UpdateManager - Static reference to the update checker, which checks for updates and notifies server admins of updates. public static UpdateManager UpdateManager; + /// Log - Static reference to the log system, which outputs to either SQL or a text file, depending on user config. public static ILog Log; + /// instance - Static reference to the TerrariaPlugin instance. public static TerrariaPlugin instance; + /// /// Static reference to a used for simple command-line parsing /// public static CommandLineParser CliParser { get; } = new CommandLineParser(); + /// /// Used for implementing REST Tokens prior to the REST system starting up. /// - public static Dictionary RESTStartupTokens = new Dictionary(); + public static Dictionary RESTStartupTokens = + new Dictionary(); /// The TShock anti-cheat/anti-exploit system. internal Bouncer Bouncer; @@ -166,21 +198,19 @@ public override Version Version /// value - "TShock" public override string Name { - get { return "TShock"; } + get { return "TShockModified"; } } /// Author - The author of the plugin. - /// value - "The TShock Team" public override string Author { - get { return "The TShock Team"; } + get { return "TSD"; } } /// Description - The plugin description. - /// value - "The administration modification of the future." public override string Description { - get { return "The administration modification of the future."; } + get { return "A modified version of TShock to fit the needs of TSD."; } } /// TShock - The constructor for the TShock plugin. @@ -199,6 +229,7 @@ public TShock(Main game) static Dictionary _nativeCache = new Dictionary(); + static IntPtr ResolveNativeDep(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) { if (_nativeCache.TryGetValue(libraryName, out IntPtr cached)) @@ -246,16 +277,35 @@ static IntPtr ResolveNativeDep(string libraryName, Assembly assembly, DllImportS return handle; } + public void CrashDueToError(Exception? ex = null) + { + void SafeError(string message) + { + if (Log is not null) Log.ConsoleError(message); + else Console.WriteLine(message); + } + + ; + + SafeError(GetString( + "TShock encountered a problem from which it cannot recover. The following message may help diagnose the problem.")); + SafeError(GetString( + "Until the problem is resolved, TShock will not be able to start (and will crash on startup).")); + if (ex is not null) + { + SafeError(ex.ToString()); + } + + Environment.Exit(1); + } + /// Initialize - Called by the TerrariaServerAPI during initialization. [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] - public override void Initialize() + public override async Task Initialize() { string logFilename; - OTAPI.Hooks.Netplay.CreateTcpListener += (sender, args) => - { - args.Result = new LinuxTcpSocket(); - }; + OTAPI.Hooks.Netplay.CreateTcpListener += (sender, args) => { args.Result = new LinuxTcpSocket(); }; OTAPI.Hooks.NetMessage.PlayerAnnounce += (sender, args) => { //TShock handles this @@ -297,7 +347,10 @@ public override void Initialize() } catch (Exception ex) { - ServerApi.LogWriter.PluginWriteLine(this, GetString("Could not apply the given log path / log format, defaults will be used. Exception details:\n{0}", ex), TraceLevel.Error); + ServerApi.LogWriter.PluginWriteLine(this, + GetString( + "Could not apply the given log path / log format, defaults will be used. Exception details:\n{0}", + ex), TraceLevel.Error); // Problem with the log path or format use the default logFilename = Path.Combine(LogPathDefault, now.ToString(LogFormatDefault) + ".log"); @@ -311,45 +364,27 @@ public override void Initialize() throw new Exception("Fatal TShock initialization exception. See inner exception for details.", ex); } - // Further exceptions are written to TShock's log from now on. try { - if (Config.Settings.StorageType.ToLower() == "sqlite") - { - string sql = Path.Combine(SavePath, Config.Settings.SqliteDBPath); - Directory.CreateDirectory(Path.GetDirectoryName(sql)); - DB = new Microsoft.Data.Sqlite.SqliteConnection(string.Format("Data Source={0}", sql)); - } - else if (Config.Settings.StorageType.ToLower() == "mysql") - { - try - { - var hostport = Config.Settings.MySqlHost.Split(':'); - DB = new MySqlConnection(); - DB.ConnectionString = - String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};", - hostport[0], - hostport.Length > 1 ? hostport[1] : "3306", - Config.Settings.MySqlDbName, - Config.Settings.MySqlUsername, - Config.Settings.MySqlPassword - ); - } - catch (MySqlException ex) - { - ServerApi.LogWriter.PluginWriteLine(this, ex.ToString(), TraceLevel.Error); - throw new Exception("MySql not setup correctly"); - } - } - else + if (string.IsNullOrWhiteSpace(Config.Settings.MongoConnectionString)) { - throw new Exception("Invalid storage type"); + throw new Exception("The mongo connection string was left empty!"); } - if (Config.Settings.UseSqlLogs) - Log = new SqlLog(DB, logFilename, LogClear); - else - Log = new TextLog(logFilename, LogClear); + var mongoConnectionSettings = + MongoClientSettings.FromConnectionString(Config.Settings.MongoConnectionString); + + // attempt to make connection to global database + await DB.InitAsync(Config.Settings.DefaultGlobalDatabase, mongoConnectionSettings); + + // attempt to make connection to local database + await DB.InitAsync(Config.Settings.LocalDatabase, mongoConnectionSettings); + + // POSSIBLY REIMPLEMENT THIS LATER WITH MONGODB + /*if (Config.Settings.UseSqlLogs) + Log = new SqlLog(Database, logFilename, LogClear); + else*/ + Log = new TextLog(logFilename, LogClear); if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) { @@ -357,6 +392,7 @@ public override void Initialize() "TShock was improperly shut down. Please use the exit command in the future to prevent this.")); File.Delete(Path.Combine(SavePath, "tshock.pid")); } + File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); @@ -366,36 +402,39 @@ public override void Initialize() Backups = new BackupManager(Path.Combine(SavePath, "backups")); Backups.KeepFor = Config.Settings.BackupKeepFor; Backups.Interval = Config.Settings.BackupInterval; - Bans = new BanManager(DB); - Warps = new WarpManager(DB); - Regions = new RegionManager(DB); - UserAccounts = new UserAccountManager(DB); - Groups = new GroupManager(DB); - ProjectileBans = new ProjectileManagager(DB); - TileBans = new TileManager(DB); - RememberedPos = new RememberedPosManager(DB); - CharacterDB = new CharacterManager(DB); - ResearchDatastore = new ResearchDatastore(DB); + Bans = new BanManager(); + Warps = new WarpManager(); + Regions = new RegionManager(); + UserAccounts = new UserAccountManager(); + Groups = new GroupManager(); + ProjectileBans = new ProjectileManager(); + TileBans = new TileManager(); + RememberedPos = new RememberedPosManager(); + CharacterDB = new CharacterManager(); + ResearchDatastore = new ResearchDatastore(); RestApi = new SecureRest(Netplay.ServerIP, Config.Settings.RestApiPort); RestManager = new RestManager(RestApi); RestManager.RegisterRestfulCommands(); Bouncer = new Bouncer(); RegionSystem = new RegionHandler(Regions); - ItemBans = new ItemBans(this, DB); + ItemBans = new ItemBans(this); - var geoippath = "GeoIP.dat"; - if (Config.Settings.EnableGeoIP && File.Exists(geoippath)) - Geo = new GeoIPCountry(geoippath); + var geoIpData = "GeoIP.dat"; + if (Config.Settings.EnableGeoIP && File.Exists(geoIpData)) + Geo = new GeoIPCountry(geoIpData); + // NOTE FROM AVERAGE: THIS MAY BE WORTH EXPLORING // check if a custom tile provider is to be used - switch(Config.Settings.WorldTileProvider?.ToLower()) + switch (Config.Settings.WorldTileProvider?.ToLower()) { case "heaptile": - Log.ConsoleInfo(GetString($"Using {nameof(HeapTile)} for tile implementation"), TraceLevel.Info); + Log.ConsoleInfo(GetString($"Using {nameof(HeapTile)} for tile implementation"), + TraceLevel.Info); Main.tile = new TileProvider(); break; case "constileation": - Log.ConsoleInfo(GetString($"Using {nameof(ConstileationProvider)} for tile implementation"), TraceLevel.Info); + Log.ConsoleInfo(GetString($"Using {nameof(ConstileationProvider)} for tile implementation"), + TraceLevel.Info); Main.tile = new ConstileationProvider(); break; } @@ -479,20 +518,10 @@ public override void Initialize() Log.ConsoleInfo(GetString("Welcome to TShock for Terraria!")); Log.ConsoleInfo(GetString("TShock comes with no warranty & is free software.")); Log.ConsoleInfo(GetString("You can modify & distribute it under the terms of the GNU GPLv3.")); - } catch (Exception ex) { - // handle if Log was not initialised - void SafeError(string message) - { - if (Log is not null) Log.ConsoleError(message); - else Console.WriteLine(message); - }; - SafeError(GetString("TShock encountered a problem from which it cannot recover. The following message may help diagnose the problem.")); - SafeError(GetString("Until the problem is resolved, TShock will not be able to start (and will crash on startup).")); - SafeError(ex.ToString()); - Environment.Exit(1); + CrashDueToError(ex); } } @@ -522,6 +551,7 @@ protected override void Dispose(bool disposing) { Geo.Dispose(); } + SaveManager.Instance.Dispose(); IL.Terraria.Initializers.AchievementInitializer.Load -= OnAchievementInitializerLoad; @@ -561,6 +591,7 @@ protected override void Dispose(bool disposing) RegionSystem.Dispose(); } + base.Dispose(disposing); } @@ -595,7 +626,7 @@ private void OnPlayerLogin(PlayerPostLoginEventArgs args) args.Player.Account.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented); UserAccounts.UpdateLogin(args.Player.Account); - Bans.CheckBan(args.Player); + Bans.IsPlayerBanned(args.Player); } /// OnAccountDelete - Internal hook fired on account delete. @@ -640,6 +671,7 @@ private void NetHooks_NameCollision(NameCollisionEventArgs args) args.Handled = true; return; } + if (player.IsLoggedIn) { var ips = JsonConvert.DeserializeObject>(player.Account.KnownIps); @@ -678,7 +710,8 @@ private void OnItemForceIntoChest(ForceItemIntoChestEventArgs args) { // After checking for protected regions, no further range checking is necessarily because the client packet only specifies the // inventory slot to quick stack. The vanilla Terraria server itself determines what chests are close enough to the player. - if (Config.Settings.RegionProtectChests && !Regions.CanBuild((int)args.WorldPosition.X, (int)args.WorldPosition.Y, tsplr)) + if (Config.Settings.RegionProtectChests && + !Regions.CanBuild((int)args.WorldPosition.X, (int)args.WorldPosition.Y, tsplr)) { args.Handled = true; return; @@ -724,7 +757,7 @@ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionE Log.Error(e.ExceptionObject.ToString()); if (e.ExceptionObject.ToString().Contains("Terraria.Netplay.ListenForClients") || - e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop")) + e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop")) { var sb = new List(); for (int i = 0; i < Netplay.Clients.Length; i++) @@ -738,6 +771,7 @@ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionE sb.Add("Tcp[" + i + "]"); } } + Log.Error(string.Join(", ", sb)); } @@ -763,6 +797,7 @@ private void ConsoleCancelHandler(object sender, ConsoleCancelEventArgs args) System.Environment.Exit(1); return; } + // Cancel the default behavior args.Cancel = true; @@ -792,95 +827,95 @@ private void HandleCommandLine(string[] parms) //Prepare the parser with all the flags available CliParser .AddFlag("-configpath", pathChecker) - //The .After Action is run after the pathChecker Action - .After(() => + //The .After Action is run after the pathChecker Action + .After(() => + { + SavePath = path ?? "tshock"; + if (path != null) { - SavePath = path ?? "tshock"; - if (path != null) - { - ServerApi.LogWriter.PluginWriteLine(this, GetString("Config path has been set to {0}", path), TraceLevel.Info); - } - }) - + ServerApi.LogWriter.PluginWriteLine(this, GetString("Config path has been set to {0}", path), + TraceLevel.Info); + } + }) .AddFlag("-worldselectpath", pathChecker) - .After(() => + .After(() => + { + if (path != null) { - if (path != null) - { - Main.WorldPath = path; - ServerApi.LogWriter.PluginWriteLine(this, GetString("World path has been set to {0}", path), TraceLevel.Info); - } - }) - + Main.WorldPath = path; + ServerApi.LogWriter.PluginWriteLine(this, GetString("World path has been set to {0}", path), + TraceLevel.Info); + } + }) .AddFlag("-logpath", pathChecker) - .After(() => + .After(() => + { + if (path != null) { - if (path != null) - { - LogPath = path; - ServerApi.LogWriter.PluginWriteLine(this, GetString("Log path has been set to {0}", path), TraceLevel.Info); - } - }) - + LogPath = path; + ServerApi.LogWriter.PluginWriteLine(this, GetString("Log path has been set to {0}", path), + TraceLevel.Info); + } + }) .AddFlag("-logformat", (format) => + { + if (!string.IsNullOrWhiteSpace(format)) { - if (!string.IsNullOrWhiteSpace(format)) { LogFormat = format; } - }) - + LogFormat = format; + } + }) .AddFlag("-config", (cfg) => + { + if (!string.IsNullOrWhiteSpace(cfg)) { - if (!string.IsNullOrWhiteSpace(cfg)) - { - ServerApi.LogWriter.PluginWriteLine(this, GetString("Loading dedicated config file: {0}", cfg), TraceLevel.Verbose); - Main.instance.LoadDedConfig(cfg); - } - }) - + ServerApi.LogWriter.PluginWriteLine(this, GetString("Loading dedicated config file: {0}", cfg), + TraceLevel.Verbose); + Main.instance.LoadDedConfig(cfg); + } + }) .AddFlag("-port", (p) => + { + int port; + if (int.TryParse(p, out port)) { - int port; - if (int.TryParse(p, out port)) - { - Netplay.ListenPort = port; - ServerApi.LogWriter.PluginWriteLine(this, GetString("Listening on port {0}.", port), TraceLevel.Verbose); - } - }) - + Netplay.ListenPort = port; + ServerApi.LogWriter.PluginWriteLine(this, GetString("Listening on port {0}.", port), + TraceLevel.Verbose); + } + }) .AddFlag("-worldname", (world) => + { + if (!string.IsNullOrWhiteSpace(world)) { - if (!string.IsNullOrWhiteSpace(world)) - { - Main.instance.SetWorldName(world); - ServerApi.LogWriter.PluginWriteLine(this, GetString("World name will be overridden by: {0}", world), TraceLevel.Verbose); - } - }) - + Main.instance.SetWorldName(world); + ServerApi.LogWriter.PluginWriteLine(this, + GetString("World name will be overridden by: {0}", world), TraceLevel.Verbose); + } + }) .AddFlag("-ip", (ip) => + { + IPAddress addr; + if (IPAddress.TryParse(ip, out addr)) { - IPAddress addr; - if (IPAddress.TryParse(ip, out addr)) - { - Netplay.ServerIP = addr; - ServerApi.LogWriter.PluginWriteLine(this, GetString("Listening on IP {0}.", addr), TraceLevel.Verbose); - } - else - { - // The server should not start up if this argument is invalid. - throw new InvalidOperationException("Invalid value given for command line argument \"-ip\"."); - } - }) - + Netplay.ServerIP = addr; + ServerApi.LogWriter.PluginWriteLine(this, GetString("Listening on IP {0}.", addr), + TraceLevel.Verbose); + } + else + { + // The server should not start up if this argument is invalid. + throw new InvalidOperationException("Invalid value given for command line argument \"-ip\"."); + } + }) .AddFlag("-autocreate", (size) => + { + if (!string.IsNullOrWhiteSpace(size)) { - if (!string.IsNullOrWhiteSpace(size)) - { - Main.instance.autoCreate(size); - } - }) - + Main.instance.autoCreate(size); + } + }) .AddFlag("-worldevil", (value) => { - int worldEvil; switch (value.ToLower()) { @@ -894,10 +929,13 @@ private void HandleCommandLine(string[] parms) worldEvil = 1; break; default: - throw new InvalidOperationException("Invalid value given for command line argument \"-worldevil\"."); + throw new InvalidOperationException( + "Invalid value given for command line argument \"-worldevil\"."); } - ServerApi.LogWriter.PluginWriteLine(this, GetString("New worlds will be generated with the {0} world evil type!", value), TraceLevel.Verbose); + ServerApi.LogWriter.PluginWriteLine(this, + GetString("New worlds will be generated with the {0} world evil type!", value), + TraceLevel.Verbose); WorldGen.WorldGenParam_Evil = worldEvil; }) @@ -921,30 +959,31 @@ public static void HandleCommandLinePostConfigLoad(string[] parms) CliParser .AddFlags(portSet, (p) => + { + int port; + if (int.TryParse(p, out port)) { - int port; - if (int.TryParse(p, out port)) - { - Netplay.ListenPort = port; - Config.Settings.ServerPort = port; - OverridePort = true; - Log.ConsoleInfo(GetString("Port overridden by startup argument. Set to {0}", port)); - } - }) + Netplay.ListenPort = port; + Config.Settings.ServerPort = port; + OverridePort = true; + Log.ConsoleInfo(GetString("Port overridden by startup argument. Set to {0}", port)); + } + }) .AddFlags(restTokenSet, (token) => - { - RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" }); - Console.WriteLine(GetString("Startup parameter overrode REST token.")); - }) + { + RESTStartupTokens.Add(token, + new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" }); + Console.WriteLine(GetString("Startup parameter overrode REST token.")); + }) .AddFlags(restEnableSet, (e) => + { + bool enabled; + if (bool.TryParse(e, out enabled)) { - bool enabled; - if (bool.TryParse(e, out enabled)) - { - Config.Settings.RestApiEnabled = enabled; - Console.WriteLine(GetString("Startup parameter overrode REST enable.")); - } - }) + Config.Settings.RestApiEnabled = enabled; + Console.WriteLine(GetString("Startup parameter overrode REST enable.")); + } + }) .AddFlags(restPortSet, (p) => { int restPort; @@ -955,20 +994,22 @@ public static void HandleCommandLinePostConfigLoad(string[] parms) } }) .AddFlags(playerSet, (p) => + { + int slots; + if (int.TryParse(p, out slots)) { - int slots; - if (int.TryParse(p, out slots)) - { - Config.Settings.MaxSlots = slots; - Console.WriteLine(GetString("Startup parameter overrode maximum player slot configuration value.")); - } - }); + Config.Settings.MaxSlots = slots; + Console.WriteLine( + GetString("Startup parameter overrode maximum player slot configuration value.")); + } + }); CliParser.ParseFromSource(parms); } /// SetupToken - The auth token used by the setup system to grant temporary superadmin access to new admins. public static int SetupToken = -1; + private string _cliPassword = null; /// OnPostInit - Fired when the server loads a map, to perform world specific operations. @@ -984,19 +1025,24 @@ private void OnPostInit(EventArgs args) //CLI defined password overrides a config password if (!string.IsNullOrEmpty(Config.Settings.ServerPassword)) { - Log.ConsoleError(GetString("!!! The server password in config.json was overridden by the interactive prompt and will be ignored.")); + Log.ConsoleError(GetString( + "!!! The server password in config.json was overridden by the interactive prompt and will be ignored.")); } if (!Config.Settings.DisableUUIDLogin) { - Log.ConsoleError(GetString("!!! UUID login is enabled. If a user's UUID matches an account, the server password will be bypassed.")); - Log.ConsoleError(GetString("!!! > Set DisableUUIDLogin to true in the config file and /reload if this is a problem.")); + Log.ConsoleError(GetString( + "!!! UUID login is enabled. If a user's UUID matches an account, the server password will be bypassed.")); + Log.ConsoleError(GetString( + "!!! > Set DisableUUIDLogin to true in the config file and /reload if this is a problem.")); } if (!Config.Settings.DisableLoginBeforeJoin) { - Log.ConsoleError(GetString("!!! Login before join is enabled. Existing accounts can login & the server password will be bypassed.")); - Log.ConsoleError(GetString("!!! > Set DisableLoginBeforeJoin to true in the config file and /reload if this is a problem.")); + Log.ConsoleError(GetString( + "!!! Login before join is enabled. Existing accounts can login & the server password will be bypassed.")); + Log.ConsoleError(GetString( + "!!! > Set DisableLoginBeforeJoin to true in the config file and /reload if this is a problem.")); } _cliPassword = Netplay.ServerPassword; @@ -1013,13 +1059,15 @@ private void OnPostInit(EventArgs args) if (!Config.Settings.DisableLoginBeforeJoin) { - Log.ConsoleInfo(GetString("Login before join enabled. Users may be prompted for an account specific password instead of a server password on connect.")); + Log.ConsoleInfo(GetString( + "Login before join enabled. Users may be prompted for an account specific password instead of a server password on connect.")); } if (!Config.Settings.DisableUUIDLogin) { Log.ConsoleInfo(GetString("Login using UUID enabled. Users automatically login via UUID.")); - Log.ConsoleInfo(GetString("A malicious server can easily steal a user's UUID. You may consider turning this option off if you run a public server.")); + Log.ConsoleInfo(GetString( + "A malicious server can easily steal a user's UUID. You may consider turning this option off if you run a public server.")); } // Disable the auth system if "setup.lock" is present or a user account already exists @@ -1029,8 +1077,10 @@ private void OnPostInit(EventArgs args) if (File.Exists(Path.Combine(SavePath, "setup-code.txt"))) { - Log.ConsoleInfo(GetString("An account has been detected in the user database, but setup-code.txt is still present.")); - Log.ConsoleInfo(GetString("TShock will now disable the initial setup system and remove setup-code.txt as it is no longer needed.")); + Log.ConsoleInfo(GetString( + "An account has been detected in the user database, but setup-code.txt is still present.")); + Log.ConsoleInfo(GetString( + "TShock will now disable the initial setup system and remove setup-code.txt as it is no longer needed.")); File.Delete(Path.Combine(SavePath, "setup-code.txt")); } @@ -1045,8 +1095,10 @@ private void OnPostInit(EventArgs args) var r = new Random((int)DateTime.Now.ToBinary()); SetupToken = r.Next(100000, 10000000); Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(GetString("To setup the server, join the game and type {0}setup {1}", Commands.Specifier, SetupToken)); - Console.WriteLine(GetString("This token will display until disabled by verification. ({0}setup)", Commands.Specifier)); + Console.WriteLine(GetString("To setup the server, join the game and type {0}setup {1}", + Commands.Specifier, SetupToken)); + Console.WriteLine(GetString("This token will display until disabled by verification. ({0}setup)", + Commands.Specifier)); Console.ResetColor(); File.WriteAllText(Path.Combine(SavePath, "setup-code.txt"), SetupToken.ToString()); } @@ -1054,9 +1106,12 @@ private void OnPostInit(EventArgs args) { SetupToken = Convert.ToInt32(File.ReadAllText(Path.Combine(SavePath, "setup-code.txt"))); Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(GetString("TShock Notice: setup-code.txt is still present, and the code located in that file will be used.")); - Console.WriteLine(GetString("To setup the server, join the game and type {0}setup {1}", Commands.Specifier, SetupToken)); - Console.WriteLine(GetString("This token will display until disabled by verification. ({0}setup)", Commands.Specifier)); + Console.WriteLine(GetString( + "TShock Notice: setup-code.txt is still present, and the code located in that file will be used.")); + Console.WriteLine(GetString("To setup the server, join the game and type {0}setup {1}", + Commands.Specifier, SetupToken)); + Console.WriteLine(GetString("This token will display until disabled by verification. ({0}setup)", + Commands.Specifier)); Console.ResetColor(); } @@ -1100,17 +1155,18 @@ private void OnUpdate(EventArgs args) LastCheck = DateTime.UtcNow; } - if (Main.ServerSideCharacter && (DateTime.UtcNow - LastSave).TotalMinutes >= ServerSideCharacterConfig.Settings.ServerSideCharacterSave) + if (Main.ServerSideCharacter && (DateTime.UtcNow - LastSave).TotalMinutes >= + ServerSideCharacterConfig.Settings.ServerSideCharacterSave) { foreach (TSPlayer player in Players) { // prevent null point exceptions if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval) { - CharacterDB.InsertPlayerData(player); } } + LastSave = DateTime.UtcNow; } } @@ -1118,7 +1174,9 @@ private void OnUpdate(EventArgs args) /// OnSecondUpdate - Called effectively every second for all time based checks. private void OnSecondUpdate() { - DisableFlags flags = Config.Settings.DisableSecondUpdateLogs ? DisableFlags.WriteToConsole : DisableFlags.WriteToLogAndConsole; + DisableFlags flags = Config.Settings.DisableSecondUpdateLogs + ? DisableFlags.WriteToConsole + : DisableFlags.WriteToLogAndConsole; if (Config.Settings.ForceTime != "normal") { @@ -1146,6 +1204,7 @@ private void OnSecondUpdate() player.TilesDestroyed.Clear(); } } + if (player.TileKillThreshold > 0) { player.TileKillThreshold = 0; @@ -1166,6 +1225,7 @@ private void OnSecondUpdate() } } } + if (player.TilePlaceThreshold > 0) { player.TilePlaceThreshold = 0; @@ -1174,7 +1234,8 @@ private void OnSecondUpdate() if (player.RecentFuse > 0) player.RecentFuse--; - if ((Main.ServerSideCharacter) && (player.TPlayer.SpawnX > 0) && (player.sX != player.TPlayer.SpawnX)) + if ((Main.ServerSideCharacter) && (player.TPlayer.SpawnX > 0) && + (player.sX != player.TPlayer.SpawnX)) { player.sX = player.TPlayer.SpawnX; player.sY = player.TPlayer.SpawnY; @@ -1204,6 +1265,7 @@ private void OnSecondUpdate() { player.Disable(GetString("Reached TileLiquid threshold"), flags); } + if (player.TileLiquidThreshold > 0) { player.TileLiquidThreshold = 0; @@ -1213,6 +1275,7 @@ private void OnSecondUpdate() { player.Disable(GetString("Reached projectile threshold"), flags); } + if (player.ProjectileThreshold > 0) { player.ProjectileThreshold = 0; @@ -1222,6 +1285,7 @@ private void OnSecondUpdate() { player.Disable(GetString("Reached paint threshold"), flags); } + if (player.PaintThreshold > 0) { player.PaintThreshold = 0; @@ -1231,6 +1295,7 @@ private void OnSecondUpdate() { player.Disable(GetString("Reached HealOtherPlayer threshold"), flags); } + if (player.HealOtherThreshold > 0) { player.HealOtherThreshold = 0; @@ -1294,13 +1359,13 @@ private void OnWorldGrassSpread(GrassSpreadEventArgs args) private bool OnCreep(int tileType) { if (!Config.Settings.AllowCrimsonCreep && (tileType == TileID.Dirt || tileType == TileID.CrimsonGrass - || TileID.Sets.Crimson[tileType])) + || TileID.Sets.Crimson[tileType])) { return false; } if (!Config.Settings.AllowCorruptionCreep && (tileType == TileID.Dirt || tileType == TileID.CorruptThorns - || TileID.Sets.Corrupt[tileType])) + || TileID.Sets.Corrupt[tileType])) { return false; } @@ -1317,7 +1382,8 @@ private bool OnCreep(int tileType) /// args - The StatueSpawnEventArgs object. private void OnStatueSpawn(StatueSpawnEventArgs args) { - if (args.Within200 < Config.Settings.StatueSpawn200 && args.Within600 < Config.Settings.StatueSpawn600 && args.WorldWide < Config.Settings.StatueSpawnWorld) + if (args.Within200 < Config.Settings.StatueSpawn200 && args.Within600 < Config.Settings.StatueSpawn600 && + args.WorldWide < Config.Settings.StatueSpawnWorld) { args.Handled = true; } @@ -1333,7 +1399,8 @@ private void OnConnect(ConnectEventArgs args) { if (ShuttingDown) { - NetMessage.SendData((int)PacketTypes.Disconnect, args.Who, -1, NetworkText.FromLiteral(GetString("Server is shutting down..."))); + NetMessage.SendData((int)PacketTypes.Disconnect, args.Who, -1, + NetworkText.FromLiteral(GetString("Server is shutting down..."))); args.Handled = true; return; } @@ -1368,6 +1435,7 @@ private void OnConnect(ConnectEventArgs args) } } } + Players[args.Who] = player; } @@ -1384,12 +1452,14 @@ private void OnJoin(JoinEventArgs args) if (Config.Settings.KickEmptyUUID && String.IsNullOrWhiteSpace(player.UUID)) { - player.Kick(GetString("Your client sent a blank UUID. Configure it to send one or use a different client."), true, true, null, false); + player.Kick( + GetString("Your client sent a blank UUID. Configure it to send one or use a different client."), + true, true, null, false); args.Handled = true; return; } - Bans.CheckBan(player); + Bans.IsPlayerBanned(player); } /// OnLeave - Called when a player leaves the server. @@ -1433,7 +1503,8 @@ private void OnLeave(LeaveEventArgs args) Utils.Broadcast(GetString("{0} has left.", tsplr.Name), Color.Yellow); Log.Info(GetString("{0} disconnected.", tsplr.Name)); - if (tsplr.IsLoggedIn && !tsplr.IsDisabledPendingTrashRemoval && Main.ServerSideCharacter && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2)) + if (tsplr.IsLoggedIn && !tsplr.IsDisabledPendingTrashRemoval && Main.ServerSideCharacter && + (!tsplr.Dead || tsplr.TPlayer.difficulty != 2)) { tsplr.PlayerData.CopyCharacter(tsplr); CharacterDB.InsertPlayerData(tsplr); @@ -1503,12 +1574,14 @@ private void OnChat(ServerChatEventArgs args) { text = item.Key.Value; } + break; } } - if ((text.StartsWith(Config.Settings.CommandSpecifier) || text.StartsWith(Config.Settings.CommandSilentSpecifier)) - && !string.IsNullOrWhiteSpace(text.Substring(1))) + if ((text.StartsWith(Config.Settings.CommandSpecifier) || + text.StartsWith(Config.Settings.CommandSilentSpecifier)) + && !string.IsNullOrWhiteSpace(text.Substring(1))) { try { @@ -1516,7 +1589,8 @@ private void OnChat(ServerChatEventArgs args) if (!Commands.HandleCommand(tsplr, text)) { // This is required in case anyone makes HandleCommand return false again - tsplr.SendErrorMessage(GetString("Unable to parse command. Please contact an administrator for assistance.")); + tsplr.SendErrorMessage( + GetString("Unable to parse command. Please contact an administrator for assistance.")); Log.ConsoleError(GetString("Unable to parse command '{0}' from player {1}."), text, tsplr.Name); } } @@ -1539,8 +1613,9 @@ private void OnChat(ServerChatEventArgs args) } else if (!TShock.Config.Settings.EnableChatAboveHeads) { - text = String.Format(Config.Settings.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, - args.Text); + text = String.Format(Config.Settings.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, + tsplr.Group.Suffix, + args.Text); //Invoke the PlayerChat hook. If this hook event is handled then we need to prevent sending the chat message bool cancelChat = PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text); @@ -1557,9 +1632,11 @@ private void OnChat(ServerChatEventArgs args) { Player ply = Main.player[args.Who]; string name = ply.name; - ply.name = String.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix); + ply.name = String.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, + tsplr.Name, tsplr.Group.Suffix); //Update the player's name to format text nicely. This needs to be done because Terraria automatically formats messages against our will - NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(ply.name), args.Who, 0, 0, 0, 0); + NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(ply.name), + args.Who, 0, 0, 0, 0); //Give that poor player their name back :'c ply.name = name; @@ -1572,18 +1649,22 @@ private void OnChat(ServerChatEventArgs args) } //This netpacket is used to send chat text from the server to clients, in this case on behalf of a client - Terraria.Net.NetPacket packet = Terraria.GameContent.NetModules.NetTextModule.SerializeServerMessage( - NetworkText.FromLiteral(text), new Color(tsplr.Group.R, tsplr.Group.G, tsplr.Group.B), (byte)args.Who - ); + Terraria.Net.NetPacket packet = + Terraria.GameContent.NetModules.NetTextModule.SerializeServerMessage( + NetworkText.FromLiteral(text), new Color(tsplr.Group.R, tsplr.Group.G, tsplr.Group.B), + (byte)args.Who + ); //Broadcast to everyone except the player who sent the message. //This is so that we can send them the same nicely formatted message that everyone else gets Terraria.Net.NetManager.Instance.Broadcast(packet, args.Who); //Reset their name - NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(name), args.Who, 0, 0, 0, 0); + NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, NetworkText.FromLiteral(name), args.Who, 0, + 0, 0, 0); string msg = String.Format("<{0}> {1}", - String.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix), + String.Format(Config.Settings.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, + tsplr.Name, tsplr.Group.Suffix), text ); @@ -1633,6 +1714,7 @@ private void ServerHooks_OnCommand(CommandEventArgs args) { Commands.HandleCommand(TSPlayer.Server, "/" + args.Command); } + args.Handled = true; } @@ -1658,8 +1740,9 @@ private void OnGetData(GetDataEventArgs e) return; } - if ((player.State < 10 || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 && - (int)type != 38 && (int)type != 21 && (int)type != 22 && type != PacketTypes.SyncLoadout) + if ((player.State < 10 || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && + (int)type != 50 && + (int)type != 38 && (int)type != 21 && (int)type != 22 && type != PacketTypes.SyncLoadout) { e.Handled = true; return; @@ -1670,6 +1753,7 @@ private void OnGetData(GetDataEventArgs e) { length = 0; } + using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length - 1)) { // Exceptions are already handled @@ -1693,15 +1777,15 @@ private void OnGreetPlayer(GreetPlayerEventArgs args) if (Config.Settings.EnableGeoIP && TShock.Geo != null) { Log.Info(GetString("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", player.Name, player.IP, - player.Group.Name, player.Country, TShock.Utils.GetActivePlayerCount(), - TShock.Config.Settings.MaxSlots)); + player.Group.Name, player.Country, TShock.Utils.GetActivePlayerCount(), + TShock.Config.Settings.MaxSlots)); if (!player.SilentJoinInProgress) Utils.Broadcast(GetString("{0} ({1}) has joined.", player.Name, player.Country), Color.Yellow); } else { Log.Info(GetString("{0} ({1}) from '{2}' group joined. ({3}/{4})", player.Name, player.IP, - player.Group.Name, TShock.Utils.GetActivePlayerCount(), TShock.Config.Settings.MaxSlots)); + player.Group.Name, TShock.Utils.GetActivePlayerCount(), TShock.Config.Settings.MaxSlots)); if (!player.SilentJoinInProgress) Utils.Broadcast(GetString("{0} has joined.", player.Name), Color.Yellow); } @@ -1724,7 +1808,9 @@ private void OnGreetPlayer(GreetPlayerEventArgs args) if (Main.ServerSideCharacter) { player.IsDisabledForSSC = true; - player.SendErrorMessage(GetString("Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier)); + player.SendErrorMessage(GetString( + "Server side characters is enabled! Please {0}register or {0}login to play!", + Commands.Specifier)); player.LoginHarassed = true; } else if (Config.Settings.RequireLogin) @@ -1736,7 +1822,8 @@ private void OnGreetPlayer(GreetPlayerEventArgs args) player.LastNetPosition = new Vector2(Main.spawnTileX * 16f, Main.spawnTileY * 16f); - if (Config.Settings.RememberLeavePos && (RememberedPos.GetLeavePos(player.Name, player.IP) != Vector2.Zero) && !player.LoginHarassed) + if (Config.Settings.RememberLeavePos && + (RememberedPos.GetLeavePos(player.Name, player.IP) != Vector2.Zero) && !player.LoginHarassed) { player.RPPending = 3; player.SendInfoMessage(GetString("You will be teleported to your last known location...")); @@ -1763,7 +1850,9 @@ private void NpcHooks_OnStrikeNpc(NpcStrikeEventArgs e) private void OnProjectileSetDefaults(SetDefaultsEventArgs e) { //tombstone fix. - if (e.Info == ProjectileID.Tombstone || (e.Info >= ProjectileID.GraveMarker && e.Info <= ProjectileID.Obelisk) || (e.Info >= ProjectileID.RichGravestone1 && e.Info <= ProjectileID.RichGravestone5)) + if (e.Info == ProjectileID.Tombstone || + (e.Info >= ProjectileID.GraveMarker && e.Info <= ProjectileID.Obelisk) || + (e.Info >= ProjectileID.RichGravestone1 && e.Info <= ProjectileID.RichGravestone5)) if (Config.Settings.DisableTombstones) e.Object.SetDefaults(0); if (e.Info == ProjectileID.HappyBomb) @@ -1795,7 +1884,8 @@ private void NetHooks_SendData(SendDataEventArgs e) { var projectile = Main.projectile[e.number]; if (projectile.active && projectile.owner >= 0 && - (GetDataHandlers.projectileCreatesLiquid.ContainsKey(projectile.type) || GetDataHandlers.projectileCreatesTile.ContainsKey(projectile.type))) + (GetDataHandlers.projectileCreatesLiquid.ContainsKey(projectile.type) || + GetDataHandlers.projectileCreatesTile.ContainsKey(projectile.type))) { var player = Players[projectile.owner]; if (player != null) @@ -1841,6 +1931,7 @@ public void OnConfigRead(ConfigFile file) Backups.KeepFor = file.Settings.BackupKeepFor; Backups.Interval = file.Settings.BackupInterval; } + if (!OverridePort) { Netplay.ListenPort = file.Settings.ServerPort; diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index a37317092..f4beabedc 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -28,12 +28,15 @@ GPL-3.0-or-later Pryaxis & TShock Contributors TShock is a toolbox for Terraria servers and communities. - TShock + TShock + enable + + diff --git a/TShockLauncher.Tests/GroupTests.cs b/TShockLauncher.Tests/GroupTests.cs index 5eff651fa..99e362802 100644 --- a/TShockLauncher.Tests/GroupTests.cs +++ b/TShockLauncher.Tests/GroupTests.cs @@ -1,6 +1,6 @@ using NUnit.Framework; using TShockAPI; -using TShockAPI.DB; +using TShockAPI.Database; namespace TShockLauncher.Tests; diff --git a/TShockLauncher.Tests/TestSetup.cs b/TShockLauncher.Tests/TestSetup.cs index 016f26790..ec39de2da 100644 --- a/TShockLauncher.Tests/TestSetup.cs +++ b/TShockLauncher.Tests/TestSetup.cs @@ -23,6 +23,6 @@ public static void SetupTShock() Lang.InitializeLegacyLocalization(); // TShockAPI.Localization will fail without preparing NPC names etc var ts = new TShock(null); // prepares configs etc - ts.Initialize(); // used to prepare tshocks own static variables, such as the TShock.DB instance + ts.Initialize(); // used to prepare tshocks own static variables, such as the TShock.Database instance } } diff --git a/docs/changelog.md b/docs/changelog.md index 035df8ada..ca88a35a7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -90,6 +90,7 @@ Use past tense when adding new entries; sign your name off when you add or chang * Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace) * Fixed typo in `/gbuff`. (@sgkoishi, #2955) * Rewrote the `.dockerignore` file into a denylist. (@timschumi) +* Fixed typo with class name `ProjectileManager`. (@renderbr) ## TShock 5.2 * An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK)