Ban rewrite and various other adjustments
This commit is contained in:
parent
ce523e1436
commit
cde4cc5f04
11 changed files with 742 additions and 276 deletions
|
|
@ -13,6 +13,14 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
|
||||||
* If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change.
|
* If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change.
|
||||||
|
|
||||||
## Upcoming Changes
|
## Upcoming Changes
|
||||||
|
* Overhauled Bans system. Bans are now based on 'identifiers'.(@QuiCM)
|
||||||
|
* The old Bans table (`Bans`) has been deprecated. New bans will go in `PlayerBans`. Use `/ban convert` to convert to the new system.
|
||||||
|
* All old ban routes in REST are now redirected. Please use `/v3/bans/*` for REST-based ban management.
|
||||||
|
* TShock recognizes and acts upon 4 main identifiers: UUID, IP, Player Name, Account name. This can be extended by plugins.
|
||||||
|
* By default, bans are no longer removed upon expiry or 'deletion'. Instead, they remain in the system. A new ban for an indentifier can be added once an existing ban has expired.
|
||||||
|
* Server Console now understands Terraria color codes (e.g., `[c/FF00FF:Words]`) and prints the colored text to the console. Note that console colors are limited and thus only approximations. (@QuiCM)
|
||||||
|
* Fixed a bug in `/sudo` that prevented quoted arguments being forwarded properly. Example: `/sudo /user group "user name" "user group"` should now work correctly. (@QuiCM)
|
||||||
|
* Shutting down the server should now correctly display the shutdown message to players rather than 'Lost connection'. (@QuiCM)
|
||||||
|
|
||||||
## TShock 4.4.0 (Pre-release 14)
|
## TShock 4.4.0 (Pre-release 14)
|
||||||
* Terraria v1.4.1.2 (Thanks @Patrikkk and @DeathCradle <3)
|
* Terraria v1.4.1.2 (Thanks @Patrikkk and @DeathCradle <3)
|
||||||
|
|
|
||||||
|
|
@ -1271,15 +1271,15 @@ namespace TShockAPI
|
||||||
// ban add <target> [reason] [duration] [flags (default: -a -u -ip)]
|
// ban add <target> [reason] [duration] [flags (default: -a -u -ip)]
|
||||||
// Valid flags: -a (ban account name), -u (ban UUID), -n (ban character name), -ip (ban IP address), -e (exact, ban the identifier provided as 'target')
|
// Valid flags: -a (ban account name), -u (ban UUID), -n (ban character name), -ip (ban IP address), -e (exact, ban the identifier provided as 'target')
|
||||||
// Unless -e is passed to the command, <target> is assumed to be a player or player index.
|
// Unless -e is passed to the command, <target> is assumed to be a player or player index.
|
||||||
// ban del <target>
|
// ban del <ban ID>
|
||||||
// Target is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg acc:MyAccountName
|
// Target is expected to be a ban Unique ID
|
||||||
// ban list [page]
|
// ban list [page]
|
||||||
// Displays a paginated list of bans
|
// Displays a paginated list of bans
|
||||||
// ban details <target>
|
// ban details <ban ID>
|
||||||
// Target is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg acc:MyAccountName
|
// Target is expected to be a ban Unique ID
|
||||||
// Output: Banned Identifier - expiration
|
// ban convert
|
||||||
// Reason: text
|
// ban-convert-confirm
|
||||||
// Banned by: name
|
// Converts all old bans to new ban system.
|
||||||
|
|
||||||
void Help()
|
void Help()
|
||||||
{
|
{
|
||||||
|
|
@ -1291,11 +1291,11 @@ namespace TShockAPI
|
||||||
|
|
||||||
args.Player.SendMessage("TShock Ban Help", Color.White);
|
args.Player.SendMessage("TShock Ban Help", Color.White);
|
||||||
args.Player.SendMessage("Available Ban commands:", Color.White);
|
args.Player.SendMessage("Available Ban commands:", Color.White);
|
||||||
args.Player.SendMessage("ban [c/FFAAAA:add] <target> [flags]", Color.White);
|
args.Player.SendMessage($"ban {"add".Color(Utils.RedHighlight)} <Target> [Flags]", Color.White);
|
||||||
args.Player.SendMessage("ban [c/FFAAAA:del] <target>", Color.White);
|
args.Player.SendMessage($"ban {"del".Color(Utils.RedHighlight)} <Ban ID>", Color.White);
|
||||||
args.Player.SendMessage("ban [c/FFAAAA:list]", Color.White);
|
args.Player.SendMessage($"ban {"list".Color(Utils.RedHighlight)}", Color.White);
|
||||||
args.Player.SendMessage("ban [c/FFAAAA:details] <target>", Color.White);
|
args.Player.SendMessage($"ban {"details".Color(Utils.RedHighlight)} <Ban ID>", Color.White);
|
||||||
args.Player.SendMessage("For more info, use [c/AAAAFF:ban help] [c/FFAAAA:command]", Color.White);
|
args.Player.SendMessage($"For more info, use {"ban help".Color(Utils.BoldHighlight)} {"command".Color(Utils.RedHighlight)}", Color.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoreHelp(string cmd)
|
void MoreHelp(string cmd)
|
||||||
|
|
@ -1305,49 +1305,76 @@ namespace TShockAPI
|
||||||
case "add":
|
case "add":
|
||||||
args.Player.SendMessage("", Color.White);
|
args.Player.SendMessage("", Color.White);
|
||||||
args.Player.SendMessage("Ban Add Syntax", Color.White);
|
args.Player.SendMessage("Ban Add Syntax", Color.White);
|
||||||
args.Player.SendMessage("[c/AAAAFF:ban add] [c/FFAAAA:<target>] [[c/AAAAFF:reason]] [[c/FFAAFF:duration]] [[c/AAFFAA:flags]]", Color.White);
|
args.Player.SendMessage($"{"ban add".Color(Utils.BoldHighlight)} <{"Target".Color(Utils.RedHighlight)}> [{"Reason".Color(Utils.BoldHighlight)}] [{"Duration".Color(Utils.PinkHighlight)}] [{"Flags".Color(Utils.GreenHighlight)}]", Color.White);
|
||||||
args.Player.SendMessage("- [c/FFAAFF:Duration]: uses the format [c/FFAAFF:0d0m0s] to determine the length of the ban. Eg a value of [c/FFAAFF:10d30m0s] would represent 10 days, 30 minutes, 0 seconds.", Color.White);
|
args.Player.SendMessage($"- {"Duration".Color(Utils.PinkHighlight)}: uses the format {"0d0m0s".Color(Utils.PinkHighlight)} to determine the length of the ban.", Color.White);
|
||||||
args.Player.SendMessage("- [c/AAFFAA:flags]: -a (account name), -u (UUID), -n (character name), -ip (IP address), -e (exact, [c/FFAAAA:target] will be treated as identifier)", Color.White);
|
args.Player.SendMessage($" Eg a value of {"10d30m0s".Color(Utils.PinkHighlight)} would represent 10 days, 30 minutes, 0 seconds.", Color.White);
|
||||||
args.Player.SendMessage(" Unless [c/AAFFAA:-e] is passed to the command, [c/FFAAAA:target] is assumed to be a player or player index", Color.White);
|
args.Player.SendMessage($"- {"Flags".Color(Utils.GreenHighlight)}: -a (account name), -u (UUID), -n (character name), -ip (IP address), -e (exact, {"Target".Color(Utils.RedHighlight)} will be treated as identifier)", Color.White);
|
||||||
args.Player.SendMessage(" If no [c/AAFFAA:flags] are specified, the command uses [c/AAFFAA:-a -u -ip] by default.", Color.White);
|
args.Player.SendMessage($" Unless {"-e".Color(Utils.GreenHighlight)} is passed to the command, {"Target".Color(Utils.RedHighlight)} is assumed to be a player or player index", Color.White);
|
||||||
args.Player.SendMessage("Example usage: [c/AAAAFF:ban add] [c/FFAAAA:ExamplePlayer] [c/AAAAFF:\"Cheating\"] 10d30m0s [c/AAFFAA:-a -u -ip]", Color.White);
|
args.Player.SendMessage($" If no {"Flags".Color(Utils.GreenHighlight)} are specified, the command uses {"-a -u -ip".Color(Utils.GreenHighlight)} by default.", Color.White);
|
||||||
|
args.Player.SendMessage($"Example usage: {"ban add".Color(Utils.BoldHighlight)} {args.Player.Name.Color(Utils.RedHighlight)} {"\"Cheating\"".Color(Utils.BoldHighlight)} {"10d30m0s".Color(Utils.PinkHighlight)} {"-a -u -ip".Color(Utils.GreenHighlight)}", Color.White);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "del":
|
case "del":
|
||||||
args.Player.SendMessage("", Color.White);
|
args.Player.SendMessage("", Color.White);
|
||||||
args.Player.SendMessage("Ban Del Syntax", Color.White);
|
args.Player.SendMessage("Ban Del Syntax", Color.White);
|
||||||
args.Player.SendMessage("[c/AAAAFF:ban del] [c/FFAAAA:target]", Color.White);
|
args.Player.SendMessage($"{"ban del".Color(Utils.BoldHighlight)} <{"Ban ID".Color(Utils.RedHighlight)}>", Color.White);
|
||||||
args.Player.SendMessage("- [c/FFAAAA:Target] is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg [c/FFAAAA:acc:MyAccountName]", Color.White);
|
args.Player.SendMessage($"- {"Ban IDs".Color(Utils.RedHighlight)}s are provided when you add a ban, and can be found with the {"ban list".Color(Utils.BoldHighlight)} command.", Color.White);
|
||||||
args.Player.SendMessage("Example usage: [c/AAAAFF:ban del] [c/FFAAAA:acc:ExampleAccount]", Color.White);
|
args.Player.SendMessage($"Example usage: {"ban del".Color(Utils.BoldHighlight)} {"12345".Color(Utils.RedHighlight)}", Color.White);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "list":
|
case "list":
|
||||||
args.Player.SendMessage("", Color.White);
|
args.Player.SendMessage("", Color.White);
|
||||||
args.Player.SendMessage("Ban List Syntax", Color.White);
|
args.Player.SendMessage("Ban List Syntax", Color.White);
|
||||||
args.Player.SendMessage("[c/AAAAFF:ban list] [[c/FFAAFF:page]]", Color.White);
|
args.Player.SendMessage($"{"ban list".Color(Utils.BoldHighlight)} [{"Page".Color(Utils.PinkHighlight)}]", Color.White);
|
||||||
args.Player.SendMessage("- Lists active bans. Color trends towards green as the ban approaches expiration", Color.White);
|
args.Player.SendMessage("- Lists active bans. Color trends towards green as the ban approaches expiration", Color.White);
|
||||||
args.Player.SendMessage("Example usage: [c/AAAAFF:ban list]", Color.White);
|
args.Player.SendMessage($"Example usage: {"ban list".Color(Utils.BoldHighlight)}", Color.White);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "details":
|
case "details":
|
||||||
args.Player.SendMessage("", Color.White);
|
args.Player.SendMessage("", Color.White);
|
||||||
args.Player.SendMessage("Ban Details Syntax", Color.White);
|
args.Player.SendMessage("Ban Details Syntax", Color.White);
|
||||||
args.Player.SendMessage("[c/AAAAFF:ban details] [c/FFAAAA:target]", Color.White);
|
args.Player.SendMessage($"{"ban details".Color(Utils.BoldHighlight)} <{"Ban ID".Color(Utils.RedHighlight)}>", Color.White);
|
||||||
args.Player.SendMessage("- [c/FFAAAA:Target] is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg [c/FFAAAA:acc:MyAccountName]", Color.White);
|
args.Player.SendMessage($"- {"Ban IDs".Color(Utils.RedHighlight)}s are provided when you add a ban, and can be found with the {"ban list".Color(Utils.BoldHighlight)} command.", Color.White);
|
||||||
args.Player.SendMessage("Example usage: [c/AAAAFF:ban details] [c/FFAAAA:acc:ExampleAccount]", Color.White);
|
args.Player.SendMessage($"Example usage: {"ban details".Color(Utils.BoldHighlight)} {"12345".Color(Utils.RedHighlight)}", Color.White);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
args.Player.SendMessage("Unknown ban command. Try 'add', 'del', 'list', or 'details'", Color.White);
|
args.Player.SendMessage($"Unknown ban command. Try {"add".Color(Utils.RedHighlight)}, {"del".Color(Utils.RedHighlight)}, {"list".Color(Utils.RedHighlight)}, or {"details".Color(Utils.RedHighlight)}.", Color.White);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayBanDetails(Ban ban)
|
||||||
|
{
|
||||||
|
args.Player.SendMessage($"{"Ban Details".Color(Utils.BoldHighlight)} - Unique ID: {ban.UniqueId.Color(Utils.GreenHighlight)}", Color.White);
|
||||||
|
args.Player.SendMessage($"{"Identifier:".Color(Utils.BoldHighlight)} {ban.Identifier}", Color.White);
|
||||||
|
args.Player.SendMessage($"{"Reason:".Color(Utils.BoldHighlight)} {ban.Reason}", Color.White);
|
||||||
|
args.Player.SendMessage($"{"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);
|
||||||
|
if (ban.ExpirationDateTime < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
args.Player.SendMessage($"{"Banned expired:".Color(Utils.BoldHighlight)} {ban.ExpirationDateTime.ToString("yyyy/MM/dd").Color(Utils.RedHighlight)} ({ban.GetPrettyExpirationString().Color(Utils.YellowHighlight)} ago)", Color.White);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string remaining;
|
||||||
|
if (ban.ExpirationDateTime == DateTime.MaxValue)
|
||||||
|
{
|
||||||
|
remaining = "Never".Color(Utils.YellowHighlight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remaining = $"{ban.GetPrettyExpirationString().Color(Utils.YellowHighlight)} remaining";
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Player.SendMessage($"{"Banned expires:".Color(Utils.BoldHighlight)} {ban.ExpirationDateTime.ToString("yyyy/MM/dd").Color(Utils.RedHighlight)} ({remaining})", Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AddBan()
|
void AddBan()
|
||||||
{
|
{
|
||||||
if (!args.Parameters.TryGetValue(1, out string target))
|
if (!args.Parameters.TryGetValue(1, out string target))
|
||||||
{
|
{
|
||||||
args.Player.SendMessage("Invalid Ban Add syntax. Refer to [c/AAAAFF:ban help add] for details on how to use the [c/AAAAFF:ban add] command", Color.White);
|
args.Player.SendMessage($"Invalid Ban Add syntax. Refer to {"ban help add".Color(Utils.BoldHighlight)} for details on how to use the {"ban add".Color(Utils.BoldHighlight)} command", Color.White);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1357,9 +1384,31 @@ namespace TShockAPI
|
||||||
bool banName = args.Parameters.Any(p => p == "-n");
|
bool banName = args.Parameters.Any(p => p == "-n");
|
||||||
bool banIp = args.Parameters.Any(p => p == "-ip");
|
bool banIp = args.Parameters.Any(p => p == "-ip");
|
||||||
|
|
||||||
args.Parameters.TryGetValue(2, out string reason);
|
string reason = "Banned.";
|
||||||
args.Parameters.TryGetValue(3, out string duration);
|
string duration = null;
|
||||||
DateTime expiration = DateTime.MaxValue;
|
DateTime expiration = DateTime.MaxValue;
|
||||||
|
AddBanResult banResult;
|
||||||
|
|
||||||
|
//This is hacky, but because the flag values can go at any parameter, this is forcing the ordering of 'reason' and 'duration'
|
||||||
|
//while still allowing them to be arbitrarily placed in the parameter list
|
||||||
|
for (int i = 2; i < args.Parameters.Count; i++)
|
||||||
|
{
|
||||||
|
var param = args.Parameters[i];
|
||||||
|
if (param != "-e" && param != "-a" && param != "-u" && param != "-n" && param != "-ip")
|
||||||
|
{
|
||||||
|
reason = param;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 3; i < args.Parameters.Count; i++)
|
||||||
|
{
|
||||||
|
var param = args.Parameters[i];
|
||||||
|
if (param != "-e" && param != "-a" && param != "-u" && param != "-n" && param != "-ip")
|
||||||
|
{
|
||||||
|
duration = param;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (TShock.Utils.TryParseTime(duration, out int seconds))
|
if (TShock.Utils.TryParseTime(duration, out int seconds))
|
||||||
{
|
{
|
||||||
|
|
@ -1374,13 +1423,15 @@ namespace TShockAPI
|
||||||
|
|
||||||
if (exactTarget)
|
if (exactTarget)
|
||||||
{
|
{
|
||||||
if (TShock.Bans.InsertBan(target, reason ?? "Banned", args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
|
banResult = TShock.Bans.InsertBan(target, reason ?? "Banned", args.Player.Account.Name, DateTime.UtcNow, expiration);
|
||||||
|
if (banResult.Ban != null)
|
||||||
{
|
{
|
||||||
args.Player.SendSuccessMessage("Ban added.");
|
args.Player.SendSuccessMessage($"Ban ID {banResult.Ban.UniqueId} added.");
|
||||||
|
DisplayBanDetails(banResult.Ban);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
args.Player.SendErrorMessage("Failed to insert ban. Ban may already exist, or an error occured.");
|
args.Player.SendErrorMessage($"Failed to add ban. {banResult.Message}");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1400,62 +1451,76 @@ namespace TShockAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = players[0];
|
var player = players[0];
|
||||||
var identifiers = new List<string>();
|
|
||||||
string identifier;
|
if (player.HasPermission(Permissions.immunetoban))
|
||||||
|
{
|
||||||
|
args.Player.SendErrorMessage("That player is immune to bans.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string banReason = null;
|
||||||
|
void DoBan(string ident)
|
||||||
|
{
|
||||||
|
banResult = TShock.Bans.InsertBan(ident, reason, args.Player.Account.Name, DateTime.UtcNow, expiration);
|
||||||
|
if (banResult.Ban != null)
|
||||||
|
{
|
||||||
|
args.Player.SendSuccessMessage($"Ban ID {banResult.Ban.UniqueId} added for identifier {ident}.");
|
||||||
|
banReason = banResult.Ban.Reason;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.Player.SendWarningMessage($"Ban skipped for identifier: {ident}");
|
||||||
|
args.Player.SendWarningMessage($"Reason: {banResult.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (banAccount)
|
if (banAccount)
|
||||||
{
|
{
|
||||||
if (player.Account != null)
|
if (player.Account != null)
|
||||||
{
|
{
|
||||||
identifier = $"{DB.Ban.Identifiers.Account}{player.Account.Name}";
|
DoBan($"{Identifiers.Account}{player.Account.Name}");
|
||||||
if (TShock.Bans.InsertBan(identifier, reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
|
|
||||||
{
|
|
||||||
identifiers.Add(identifier);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (banUuid)
|
if (banUuid)
|
||||||
{
|
{
|
||||||
identifier = $"{DB.Ban.Identifiers.UUID}{player.UUID}";
|
DoBan($"{Identifiers.UUID}{player.UUID}");
|
||||||
if (TShock.Bans.InsertBan($"{DB.Ban.Identifiers.UUID}{player.UUID}", reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
|
|
||||||
{
|
|
||||||
identifiers.Add(identifier);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (banName)
|
if (banName)
|
||||||
{
|
{
|
||||||
identifier = $"{DB.Ban.Identifiers.Name}{player.Name}";
|
DoBan($"{Identifiers.Name}{player.Name}");
|
||||||
if (TShock.Bans.InsertBan($"{DB.Ban.Identifiers.Name}{player.Name}", reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
|
|
||||||
{
|
|
||||||
identifiers.Add(identifier);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (banIp)
|
if (banIp)
|
||||||
{
|
{
|
||||||
identifier = $"{DB.Ban.Identifiers.IP}{player.IP}";
|
DoBan($"{Identifiers.IP}{player.IP}");
|
||||||
if (TShock.Bans.InsertBan($"{DB.Ban.Identifiers.IP}{player.IP}", reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
|
|
||||||
{
|
|
||||||
identifiers.Add(identifier);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Player.SendSuccessMessage("Bans added for identifiers: ", string.Join(", ", identifiers));
|
//Using the ban reason to determine if a ban actually happened or not is messy, but it works
|
||||||
|
if (banReason != null)
|
||||||
|
{
|
||||||
|
player.Disconnect($"You have been banned: {banReason}.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DelBan()
|
void DelBan()
|
||||||
{
|
{
|
||||||
if (!args.Parameters.TryGetValue(1, out string target))
|
if (!args.Parameters.TryGetValue(1, out string target))
|
||||||
{
|
{
|
||||||
args.Player.SendMessage("Invalid Ban Del syntax. Refer to [c/AAAAFF:ban help del] for details on how to use the [c/AAAAFF:ban del] command", Color.White);
|
args.Player.SendMessage($"Invalid Ban Del syntax. Refer to {"ban help del".Color(Utils.BoldHighlight)} for details on how to use the {"ban del".Color(Utils.BoldHighlight)} command", Color.White);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TShock.Bans.RemoveBan(target))
|
if (!int.TryParse(target, out int banId))
|
||||||
{
|
{
|
||||||
args.Player.SendSuccessMessage("Ban removed.");
|
args.Player.SendMessage($"Invalid Ban ID. Refer to {"ban help del".Color(Utils.BoldHighlight)} for details on how to use the {"ban del".Color(Utils.BoldHighlight)} command", Color.White);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TShock.Bans.RemoveBan(banId))
|
||||||
|
{
|
||||||
|
args.Player.SendSuccessMessage($"Ban {banId} has now been marked as expired.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -1463,26 +1528,37 @@ namespace TShockAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string PickColorForBan(Ban ban)
|
||||||
|
{
|
||||||
|
double hoursRemaining = (ban.ExpirationDateTime - DateTime.UtcNow).TotalHours;
|
||||||
|
double hoursTotal = (ban.ExpirationDateTime - ban.BanDateTime).TotalHours;
|
||||||
|
double percentRemaining = TShock.Utils.Clamp(hoursRemaining / hoursTotal, 100, 0);
|
||||||
|
|
||||||
|
int red = TShock.Utils.Clamp((int)(255 * 2.0f * percentRemaining), 255, 0);
|
||||||
|
int green = TShock.Utils.Clamp((int)(255 * (2.0f * (1 - percentRemaining))), 255, 0);
|
||||||
|
|
||||||
|
return $"{red:X2}{green:X2}{0:X2}";
|
||||||
|
}
|
||||||
|
|
||||||
void ListBans()
|
void ListBans()
|
||||||
{
|
{
|
||||||
int pageNumber;
|
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out int pageNumber))
|
||||||
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
|
|
||||||
{
|
{
|
||||||
args.Player.SendMessage("Invalid Ban List syntax. Refer to [c/AAAAFF:ban help list] for details on how to use the [c/AAAAFF:ban list] command", Color.White);
|
args.Player.SendMessage($"Invalid Ban List syntax. Refer to {"ban help list".Color(Utils.BoldHighlight)} for details on how to use the {"ban list".Color(Utils.BoldHighlight)} command", Color.White);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bans = from ban in TShock.Bans.Bans
|
||||||
|
where ban.Value.ExpirationDateTime > DateTime.UtcNow
|
||||||
|
orderby ban.Value.ExpirationDateTime ascending
|
||||||
|
select $"[{ban.Key.Color(Utils.GreenHighlight)}] {ban.Value.Identifier.Color(PickColorForBan(ban.Value))}";
|
||||||
|
|
||||||
List<Ban> bans = TShock.Bans.GetAllBans();
|
PaginationTools.SendPage(args.Player, pageNumber, bans.ToList(),
|
||||||
|
|
||||||
var nameBans = from ban in bans
|
|
||||||
select ban.Identifier;
|
|
||||||
|
|
||||||
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(nameBans),
|
|
||||||
new PaginationTools.Settings
|
new PaginationTools.Settings
|
||||||
{
|
{
|
||||||
HeaderFormat = "Bans ({0}/{1}):",
|
HeaderFormat = "Bans ({0}/{1}):",
|
||||||
FooterFormat = "Type {0}ban list {{0}} for more.".SFormat(Specifier),
|
FooterFormat = "Type {0}ban list {{0}} for more.".SFormat(Specifier),
|
||||||
NothingToDisplayString = "There are currently no bans."
|
NothingToDisplayString = "There are currently no active bans."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1490,11 +1566,17 @@ namespace TShockAPI
|
||||||
{
|
{
|
||||||
if (!args.Parameters.TryGetValue(1, out string target))
|
if (!args.Parameters.TryGetValue(1, out string target))
|
||||||
{
|
{
|
||||||
args.Player.SendMessage("Invalid Ban Details syntax. Refer to [c/AAAAFF:ban help details] for details on how to use the [c/AAAAFF:ban details] command", Color.White);
|
args.Player.SendMessage($"Invalid Ban Details syntax. Refer to {"ban help details".Color(Utils.BoldHighlight)} for details on how to use the {"ban details".Color(Utils.BoldHighlight)} command", Color.White);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ban ban = TShock.Bans.GetBanByIdentifier(target);
|
if (!int.TryParse(target, out int banId))
|
||||||
|
{
|
||||||
|
args.Player.SendMessage($"Invalid Ban ID. Refer to {"ban help details".Color(Utils.BoldHighlight)} for details on how to use the {"ban details".Color(Utils.BoldHighlight)} command", Color.White);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ban ban = TShock.Bans.GetBanById(banId);
|
||||||
|
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
|
|
@ -1502,9 +1584,20 @@ namespace TShockAPI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Player.SendMessage($"{ban.Identifier}", Color.White);
|
DisplayBanDetails(ban);
|
||||||
args.Player.SendMessage($"Reason: {ban.Reason}", Color.White);
|
}
|
||||||
args.Player.SendMessage($"Banned by: [c/AAFFAA:{ban.BanningUser}] at [c/AAAAFF:time]", Color.White);
|
|
||||||
|
void ConvertBans()
|
||||||
|
{
|
||||||
|
args.Player.SendWarningMessage("This will convert all bans from the old ban system to the new identifier-based bans.");
|
||||||
|
args.Player.SendWarningMessage($"If you are sure you wish to proceed, please execute {"ban-convert-confirm".Color(Utils.WhiteHighlight)} to continue.");
|
||||||
|
args.Player.AddResponse("ban-convert-confirm", (obj) =>
|
||||||
|
{
|
||||||
|
TShock.Bans.ConvertBans();
|
||||||
|
|
||||||
|
var cmdArgs = (CommandArgs)obj;
|
||||||
|
cmdArgs.Player.SendSuccessMessage("Bans have been converted.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
|
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
|
||||||
|
|
@ -1530,6 +1623,10 @@ namespace TShockAPI
|
||||||
BanDetails();
|
BanDetails();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "convert":
|
||||||
|
ConvertBans();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1787,7 +1884,7 @@ namespace TShockAPI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string replacementCommand = String.Join(" ", args.Parameters);
|
string replacementCommand = String.Join(" ", args.Parameters.Select(p => p.Contains(" ") ? $"\"{p}\"" : p));
|
||||||
args.Player.tempGroup = new SuperAdminGroup();
|
args.Player.tempGroup = new SuperAdminGroup();
|
||||||
HandleCommand(args.Player, replacementCommand);
|
HandleCommand(args.Player, replacementCommand);
|
||||||
args.Player.tempGroup = null;
|
args.Player.tempGroup = null;
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,36 @@ namespace TShockAPI.DB
|
||||||
{
|
{
|
||||||
private IDbConnection database;
|
private IDbConnection database;
|
||||||
|
|
||||||
|
private Dictionary<int, Ban> _bans;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event invoked when a ban check occurs
|
/// Dictionary of Bans, keyed on unique ban ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event EventHandler<BanEventArgs> OnBanCheck;
|
public Dictionary<int, Ban> Bans
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_bans == null)
|
||||||
|
{
|
||||||
|
_bans = RetrieveAllBans().ToDictionary(b => b.UniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bans;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event invoked when a ban is added
|
/// Event invoked when a ban is checked for validity
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event EventHandler<BanEventArgs> OnBanAdded;
|
public static event EventHandler<BanEventArgs> OnBanValidate;
|
||||||
|
/// <summary>
|
||||||
|
/// Event invoked before a ban is added
|
||||||
|
/// </summary>
|
||||||
|
public static event EventHandler<BanPreAddEventArgs> OnBanPreAdd;
|
||||||
|
/// <summary>
|
||||||
|
/// Event invoked after a ban is added
|
||||||
|
/// </summary>
|
||||||
|
public static event EventHandler<BanEventArgs> OnBanPostAdd;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TShockAPI.DB.BanManager"/> class.
|
/// Initializes a new instance of the <see cref="TShockAPI.DB.BanManager"/> class.
|
||||||
|
|
@ -49,11 +71,12 @@ namespace TShockAPI.DB
|
||||||
database = db;
|
database = db;
|
||||||
|
|
||||||
var table = new SqlTable("PlayerBans",
|
var table = new SqlTable("PlayerBans",
|
||||||
new SqlColumn("Identifier", MySqlDbType.Text) { Primary = true, Unique = true },
|
new SqlColumn("Id", MySqlDbType.Int32) { Primary = true, AutoIncrement = true },
|
||||||
|
new SqlColumn("Identifier", MySqlDbType.Text),
|
||||||
new SqlColumn("Reason", MySqlDbType.Text),
|
new SqlColumn("Reason", MySqlDbType.Text),
|
||||||
new SqlColumn("BanningUser", MySqlDbType.Text),
|
new SqlColumn("BanningUser", MySqlDbType.Text),
|
||||||
new SqlColumn("Date", MySqlDbType.Text),
|
new SqlColumn("Date", MySqlDbType.Int64),
|
||||||
new SqlColumn("Expiration", MySqlDbType.Text)
|
new SqlColumn("Expiration", MySqlDbType.Int64)
|
||||||
);
|
);
|
||||||
var creator = new SqlTableCreator(db,
|
var creator = new SqlTableCreator(db,
|
||||||
db.GetSqlType() == SqlType.Sqlite
|
db.GetSqlType() == SqlType.Sqlite
|
||||||
|
|
@ -69,7 +92,8 @@ namespace TShockAPI.DB
|
||||||
throw new Exception("Could not find a database library (probably Sqlite3.dll)");
|
throw new Exception("Could not find a database library (probably Sqlite3.dll)");
|
||||||
}
|
}
|
||||||
|
|
||||||
OnBanCheck += CheckBanValid;
|
OnBanValidate += BanValidateCheck;
|
||||||
|
OnBanPreAdd += BanAddedCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -89,19 +113,29 @@ namespace TShockAPI.DB
|
||||||
var date = reader.Get<string>("Date");
|
var date = reader.Get<string>("Date");
|
||||||
var expiration = reader.Get<string>("Expiration");
|
var expiration = reader.Get<string>("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))
|
if (!string.IsNullOrWhiteSpace(ip))
|
||||||
{
|
{
|
||||||
InsertBan($"{Ban.Identifiers.IP}{ip}", reason, banningUser, date, expiration);
|
InsertBan($"{Identifiers.IP}{ip}", reason, banningUser, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(account))
|
if (!string.IsNullOrWhiteSpace(account))
|
||||||
{
|
{
|
||||||
InsertBan($"{Ban.Identifiers.Account}{account}", reason, banningUser, date, expiration);
|
InsertBan($"{Identifiers.Account}{account}", reason, banningUser, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(uuid))
|
if (!string.IsNullOrWhiteSpace(uuid))
|
||||||
{
|
{
|
||||||
InsertBan($"{Ban.Identifiers.UUID}{uuid}", reason, banningUser, date, expiration);
|
InsertBan($"{Identifiers.UUID}{uuid}", reason, banningUser, start, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,99 +145,145 @@ namespace TShockAPI.DB
|
||||||
/// Determines whether or not a ban is valid
|
/// Determines whether or not a ban is valid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ban"></param>
|
/// <param name="ban"></param>
|
||||||
|
/// <param name="player"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool IsValidBan(Ban ban)
|
public bool IsValidBan(Ban ban, TSPlayer player)
|
||||||
{
|
{
|
||||||
BanEventArgs args = new BanEventArgs { Ban = ban };
|
BanEventArgs args = new BanEventArgs
|
||||||
OnBanCheck?.Invoke(this, args);
|
{
|
||||||
|
Ban = ban,
|
||||||
|
Player = player
|
||||||
|
};
|
||||||
|
|
||||||
|
OnBanValidate?.Invoke(this, args);
|
||||||
|
|
||||||
return args.Valid;
|
return args.Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CheckBanValid(object sender, BanEventArgs args)
|
internal void BanValidateCheck(object sender, BanEventArgs args)
|
||||||
{
|
{
|
||||||
//We consider a ban to be valid if the start time is before now and the end time is after now
|
//Only perform validation if the event has not been cancelled before we got here
|
||||||
args.Valid = args.Ban.BanDateTime < DateTime.UtcNow && args.Ban.ExpirationDateTime > DateTime.UtcNow;
|
if (args.Valid)
|
||||||
|
{
|
||||||
|
//We consider a ban to be valid if the start time is before now and the end time is after now, and the player is not immune to bans
|
||||||
|
args.Valid = (DateTime.UtcNow > args.Ban.BanDateTime && DateTime.UtcNow < args.Ban.ExpirationDateTime) && !args.Player.HasPermission(Permissions.immunetoban);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal void BanAddedCheck(object sender, BanPreAddEventArgs args)
|
||||||
/// Adds a new ban for the given identifier. If the addition succeeds, returns a ban object with the ban details. Else returns null
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="identifier"></param>
|
|
||||||
/// <param name="reason"></param>
|
|
||||||
/// <param name="banningUser"></param>
|
|
||||||
/// <param name="fromDate"></param>
|
|
||||||
/// <param name="toDate"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Ban InsertBan(string identifier, string reason, string banningUser, DateTime fromDate, DateTime toDate)
|
|
||||||
=> InsertBan(identifier, reason, banningUser, fromDate.ToString("s"), toDate.ToString("s"));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a new ban for the given identifier. If the addition succeeds, returns a ban object with the ban details. Else returns null
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="identifier"></param>
|
|
||||||
/// <param name="reason"></param>
|
|
||||||
/// <param name="banningUser"></param>
|
|
||||||
/// <param name="fromDate"></param>
|
|
||||||
/// <param name="toDate"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public Ban InsertBan(string identifier, string reason, string banningUser, string fromDate, string toDate)
|
|
||||||
{
|
{
|
||||||
Ban b = new Ban(identifier, reason, banningUser, fromDate, toDate);
|
//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 : "a current ban for this identifier already exists.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new ban for the given identifier. Returns a Ban object if the ban was added, else null
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="identifier"></param>
|
||||||
|
/// <param name="reason"></param>
|
||||||
|
/// <param name="banningUser"></param>
|
||||||
|
/// <param name="fromDate"></param>
|
||||||
|
/// <param name="toDate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
BanEventArgs args = new BanEventArgs { Ban = b };
|
OnBanPreAdd?.Invoke(this, args);
|
||||||
|
|
||||||
OnBanAdded?.Invoke(this, args);
|
|
||||||
|
|
||||||
if (!args.Valid)
|
if (!args.Valid)
|
||||||
{
|
{
|
||||||
return null;
|
string message = $"Ban was invalidated: {(args.Message ?? "no further information provided.")}";
|
||||||
|
return new AddBanResult { Message = message };
|
||||||
}
|
}
|
||||||
|
|
||||||
int rowsModified = database.Query("INSERT OR IGNORE INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4);", identifier, reason, banningUser, fromDate, toDate);
|
string query = "INSERT INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4);";
|
||||||
//Return the ban if the query actually inserted the row. If the given identifier already exists, the INSERT is ignored and 0 rows are modified.
|
|
||||||
return rowsModified != 0 ? b : null;
|
if (database.GetSqlType() == SqlType.Mysql)
|
||||||
|
{
|
||||||
|
query += "SELECT CAST(LAST_INSERT_ID() as INT);";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query += "SELECT CAST(last_insert_rowid() as INT);";
|
||||||
|
}
|
||||||
|
|
||||||
|
int uniqueId = database.QueryScalar<int>(query, identifier, reason, banningUser, fromDate.Ticks, toDate.Ticks);
|
||||||
|
|
||||||
|
if (uniqueId == 0)
|
||||||
|
{
|
||||||
|
return new AddBanResult { Message = "Database insert failed." };
|
||||||
|
}
|
||||||
|
|
||||||
|
Ban b = new Ban(uniqueId, identifier, reason, banningUser, fromDate, toDate);
|
||||||
|
_bans.Add(uniqueId, b);
|
||||||
|
|
||||||
|
OnBanPostAdd?.Invoke(this, new BanEventArgs { Ban = b });
|
||||||
|
|
||||||
|
return new AddBanResult { Ban = b };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to remove a ban. Returns true if the ban was removed or expired. False if the ban could not be removed or expired
|
/// Attempts to remove a ban. Returns true if the ban was removed or expired. False if the ban could not be removed or expired
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identifier"></param>
|
/// <param name="uniqueId">The unique ID of the ban to change</param>
|
||||||
/// <param name="fullDelete">If true, deletes the ban from the database. If false, marks the expiration time as now, rendering the ban expired. Defaults to false</param>
|
/// <param name="fullDelete">If true, deletes the ban from the database. If false, marks the expiration time as now, rendering the ban expired. Defaults to false</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool RemoveBan(string identifier, bool fullDelete = false)
|
public bool RemoveBan(int uniqueId, bool fullDelete = false)
|
||||||
{
|
{
|
||||||
int rowsModified;
|
int rowsModified;
|
||||||
if (fullDelete)
|
if (fullDelete)
|
||||||
{
|
{
|
||||||
rowsModified = database.Query("DELETE FROM PlayerBans WHERE Identifier=@0", identifier);
|
rowsModified = database.Query("DELETE FROM PlayerBans WHERE Id=@0", uniqueId);
|
||||||
|
_bans.Remove(uniqueId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rowsModified = database.Query("UPDATE PlayerBans SET Expiration=@0 WHERE Identifier=@1", DateTime.UtcNow.ToString("s"), identifier);
|
rowsModified = database.Query("UPDATE PlayerBans SET Expiration=@0 WHERE Id=@1", DateTime.UtcNow.Ticks, uniqueId);
|
||||||
|
_bans[uniqueId].ExpirationDateTime = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rowsModified > 0;
|
return rowsModified > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a ban for a given identifier, or null if no matches are found
|
/// Retrieves a single ban from a unique ban ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identifier"></param>
|
/// <param name="id"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Ban GetBanByIdentifier(string identifier)
|
public Ban GetBanById(int id)
|
||||||
{
|
{
|
||||||
using (var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE Identifier=@0", identifier))
|
if (Bans.ContainsKey(id))
|
||||||
|
{
|
||||||
|
return Bans[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE Id=@0", id))
|
||||||
{
|
{
|
||||||
if (reader.Read())
|
if (reader.Read())
|
||||||
{
|
{
|
||||||
var id = reader.Get<string>("Identifier");
|
var uniqueId = reader.Get<int>("Id");
|
||||||
|
var identifier = reader.Get<string>("Identifier");
|
||||||
var reason = reader.Get<string>("Reason");
|
var reason = reader.Get<string>("Reason");
|
||||||
var banningUser = reader.Get<string>("BanningUser");
|
var banningUser = reader.Get<string>("BanningUser");
|
||||||
var date = reader.Get<string>("Date");
|
var date = reader.Get<long>("Date");
|
||||||
var expiration = reader.Get<string>("Expiration");
|
var expiration = reader.Get<long>("Expiration");
|
||||||
|
|
||||||
return new Ban(id, reason, banningUser, date, expiration);
|
return new Ban(uniqueId, identifier, reason, banningUser, date, expiration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,41 +291,80 @@ namespace TShockAPI.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves an enumerable of bans for a given set of identifiers
|
/// Retrieves an enumerable of all bans for a given identifier
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="identifiers"></param>
|
/// <param name="identifier">Identifier to search with</param>
|
||||||
|
/// <param name="currentOnly">Whether or not to exclude expired bans</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IEnumerable<Ban> GetBansByIdentifiers(params string[] identifiers)
|
public IEnumerable<Ban> RetrieveBansByIdentifier(string identifier, bool currentOnly = true)
|
||||||
{
|
{
|
||||||
//Generate a sequence of '@0, @1, @2, ... etc'
|
string query = "SELECT * FROM PlayerBans WHERE Identifier=@0";
|
||||||
var parameters = string.Join(", ", Enumerable.Range(0, identifiers.Count()).Select(p => $"@{p}"));
|
if (currentOnly)
|
||||||
|
{
|
||||||
|
query += $" AND Expiration > {DateTime.UtcNow.Ticks}";
|
||||||
|
}
|
||||||
|
|
||||||
using (var reader = database.QueryReader($"SELECT * FROM PlayerBans WHERE Identifier IN ({parameters})", identifiers))
|
using (var reader = database.QueryReader(query, identifier))
|
||||||
{
|
{
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
|
var uniqueId = reader.Get<int>("Id");
|
||||||
|
var ident = reader.Get<string>("Identifier");
|
||||||
var id = reader.Get<string>("Identifier");
|
var id = reader.Get<string>("Identifier");
|
||||||
var reason = reader.Get<string>("Reason");
|
var reason = reader.Get<string>("Reason");
|
||||||
var banningUser = reader.Get<string>("BanningUser");
|
var banningUser = reader.Get<string>("BanningUser");
|
||||||
var date = reader.Get<string>("Date");
|
var date = reader.Get<long>("Date");
|
||||||
var expiration = reader.Get<string>("Expiration");
|
var expiration = reader.Get<long>("Expiration");
|
||||||
|
|
||||||
yield return new Ban(id, reason, banningUser, date, expiration);
|
yield return new Ban(uniqueId, ident, reason, banningUser, date, expiration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of bans sorted by their addition date from newest to oldest
|
/// Retrieves an enumerable of bans for a given set of identifiers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Ban> GetAllBans() => GetAllBansSorted(BanSortMethod.AddedNewestToOldest).ToList();
|
/// <param name="currentOnly">Whether or not to exclude expired bans</param>
|
||||||
|
/// <param name="identifiers"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerable<Ban> 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 uniqueId = reader.Get<int>("Id");
|
||||||
|
var identifier = reader.Get<string>("Identifier");
|
||||||
|
var reason = reader.Get<string>("Reason");
|
||||||
|
var banningUser = reader.Get<string>("BanningUser");
|
||||||
|
var date = reader.Get<long>("Date");
|
||||||
|
var expiration = reader.Get<long>("Expiration");
|
||||||
|
|
||||||
|
yield return new Ban(uniqueId, identifier, reason, banningUser, date, expiration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves an enumerable of <see cref="Ban"/> objects, sorted using the provided sort method
|
/// Retrieves a list of bans from the database, sorted by their addition date from newest to oldest
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<Ban> RetrieveAllBans() => RetrieveAllBansSorted(BanSortMethod.AddedNewestToOldest);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an enumerable of <see cref="Ban"/>s from the database, sorted using the provided sort method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sortMethod"></param>
|
/// <param name="sortMethod"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IEnumerable<Ban> GetAllBansSorted(BanSortMethod sortMethod)
|
public IEnumerable<Ban> RetrieveAllBansSorted(BanSortMethod sortMethod)
|
||||||
{
|
{
|
||||||
List<Ban> banlist = new List<Ban>();
|
List<Ban> banlist = new List<Ban>();
|
||||||
try
|
try
|
||||||
|
|
@ -255,24 +374,25 @@ namespace TShockAPI.DB
|
||||||
{
|
{
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
|
var uniqueId = reader.Get<int>("Id");
|
||||||
var identifier = reader.Get<string>("Identifier");
|
var identifier = reader.Get<string>("Identifier");
|
||||||
var reason = reader.Get<string>("Reason");
|
var reason = reader.Get<string>("Reason");
|
||||||
var banningUser = reader.Get<string>("BanningUser");
|
var banningUser = reader.Get<string>("BanningUser");
|
||||||
var date = reader.Get<string>("Date");
|
var date = reader.Get<long>("Date");
|
||||||
var expiration = reader.Get<string>("Expiration");
|
var expiration = reader.Get<long>("Expiration");
|
||||||
|
|
||||||
var ban = new Ban(identifier, reason, banningUser, date, expiration);
|
var ban = new Ban(uniqueId, identifier, reason, banningUser, date, expiration);
|
||||||
banlist.Add(ban);
|
banlist.Add(ban);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return banlist;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
TShock.Log.Error(ex.ToString());
|
TShock.Log.Error(ex.ToString());
|
||||||
Console.WriteLine(ex.StackTrace);
|
Console.WriteLine(ex.StackTrace);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return banlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -321,51 +441,124 @@ namespace TShockAPI.DB
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bans will be sorted by the date they were added, from oldest to newest
|
/// Bans will be sorted by the date they were added, from oldest to newest
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AddedOldestToNewest
|
AddedOldestToNewest,
|
||||||
|
/// <summary>
|
||||||
|
/// Bans will be sorted by their unique ID
|
||||||
|
/// </summary>
|
||||||
|
UniqueId
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event args used when a ban check is invoked, or a new ban is added
|
/// Result of an attempt to add a ban
|
||||||
|
/// </summary>
|
||||||
|
public class AddBanResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message generated from the attempt
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Ban object generated from the attempt, or null if the attempt failed
|
||||||
|
/// </summary>
|
||||||
|
public Ban Ban { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args used for formalized bans
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BanEventArgs : EventArgs
|
public class BanEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ban being checked or added
|
/// Complete ban object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Ban Ban { get; set; }
|
public Ban Ban { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the operation is valid
|
/// Player ban is being applied to
|
||||||
|
/// </summary>
|
||||||
|
public TSPlayer Player { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the operation should be considered to be valid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Valid { get; set; } = true;
|
public bool Valid { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event args used for ban data prior to a ban being formalized
|
||||||
|
/// </summary>
|
||||||
|
public class BanPreAddEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An identifiable piece of information to ban
|
||||||
|
/// </summary>
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ban reason.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The ban reason.</value>
|
||||||
|
public string Reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the name of the user who added this ban entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The banning user.</value>
|
||||||
|
public string BanningUser { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DateTime from which the ban will take effect
|
||||||
|
/// </summary>
|
||||||
|
public DateTime BanDateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DateTime at which the ban will end
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ExpirationDateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the operation should be considered to be valid
|
||||||
|
/// </summary>
|
||||||
|
public bool Valid { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional message to explain why the event was invalidated, if it was
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains constants for different identifier types known to TShock
|
||||||
|
/// </summary>
|
||||||
|
public static class Identifiers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IP identifier prefix constant
|
||||||
|
/// </summary>
|
||||||
|
public const string IP = "ip:";
|
||||||
|
/// <summary>
|
||||||
|
/// UUID identifier prefix constant
|
||||||
|
/// </summary>
|
||||||
|
public const string UUID = "uuid:";
|
||||||
|
/// <summary>
|
||||||
|
/// Player name identifier prefix constant
|
||||||
|
/// </summary>
|
||||||
|
public const string Name = "name:";
|
||||||
|
/// <summary>
|
||||||
|
/// User account identifier prefix constant
|
||||||
|
/// </summary>
|
||||||
|
public const string Account = "acc:";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Model class that represents a ban entry in the TShock database.
|
/// Model class that represents a ban entry in the TShock database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Ban
|
public class Ban
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains constants for different identifier types known to TShock
|
/// A unique ID assigned to this ban
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Identifiers
|
public int UniqueId { get; set; }
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// IP identifier prefix constant
|
|
||||||
/// </summary>
|
|
||||||
public const string IP = "ip:";
|
|
||||||
/// <summary>
|
|
||||||
/// UUID identifier prefix constant
|
|
||||||
/// </summary>
|
|
||||||
public const string UUID = "uuid:";
|
|
||||||
/// <summary>
|
|
||||||
/// Player name identifier prefix constant
|
|
||||||
/// </summary>
|
|
||||||
public const string Name = "name:";
|
|
||||||
/// <summary>
|
|
||||||
/// User account identifier prefix constant
|
|
||||||
/// </summary>
|
|
||||||
public const string Account = "acc:";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An identifiable piece of information to ban
|
/// An identifiable piece of information to ban
|
||||||
|
|
@ -387,44 +580,71 @@ namespace TShockAPI.DB
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DateTime from which the ban will take effect
|
/// DateTime from which the ban will take effect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime BanDateTime { get; }
|
public DateTime BanDateTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DateTime at which the ban will end
|
/// DateTime at which the ban will end
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime ExpirationDateTime { get; }
|
public DateTime ExpirationDateTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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'
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string in the format dd:mm:hh:ss indicating the time elapsed since the ban was added.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetPrettyTimeSinceBanString()
|
||||||
|
{
|
||||||
|
TimeSpan ts = (DateTime.UtcNow - BanDateTime).Duration();
|
||||||
|
return $"{ts.Days:00}:{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TShockAPI.DB.Ban"/> class.
|
/// Initializes a new instance of the <see cref="TShockAPI.DB.Ban"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="uniqueId">Unique ID assigned to the ban</param>
|
||||||
/// <param name="identifier">Identifier to apply the ban to</param>
|
/// <param name="identifier">Identifier to apply the ban to</param>
|
||||||
/// <param name="reason">Reason for the ban</param>
|
/// <param name="reason">Reason for the ban</param>
|
||||||
/// <param name="banningUser">Account name that executed the ban</param>
|
/// <param name="banningUser">Account name that executed the ban</param>
|
||||||
/// <param name="start">Ban start datetime</param>
|
/// <param name="start">System ticks at which the ban began</param>
|
||||||
/// <param name="end">Ban end datetime</param>
|
/// <param name="end">System ticks at which the ban will end</param>
|
||||||
public Ban(string identifier, string reason, string banningUser, string start, string end)
|
public Ban(int uniqueId, string identifier, string reason, string banningUser, long start, long end)
|
||||||
|
: this(uniqueId, identifier, reason, banningUser, new DateTime(start), new DateTime(end))
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TShockAPI.DB.Ban"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uniqueId">Unique ID assigned to the ban</param>
|
||||||
|
/// <param name="identifier">Identifier to apply the ban to</param>
|
||||||
|
/// <param name="reason">Reason for the ban</param>
|
||||||
|
/// <param name="banningUser">Account name that executed the ban</param>
|
||||||
|
/// <param name="start">DateTime at which the ban will start</param>
|
||||||
|
/// <param name="end">DateTime at which the ban will end</param>
|
||||||
|
public Ban(int uniqueId, string identifier, string reason, string banningUser, DateTime start, DateTime end)
|
||||||
|
{
|
||||||
|
UniqueId = uniqueId;
|
||||||
Identifier = identifier;
|
Identifier = identifier;
|
||||||
Reason = reason;
|
Reason = reason;
|
||||||
BanningUser = banningUser;
|
BanningUser = banningUser;
|
||||||
|
BanDateTime = start;
|
||||||
if (DateTime.TryParse(start, out DateTime d))
|
ExpirationDateTime = end;
|
||||||
{
|
|
||||||
BanDateTime = d;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BanDateTime = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DateTime.TryParse(end, out DateTime e))
|
|
||||||
{
|
|
||||||
ExpirationDateTime = e;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ExpirationDateTime = DateTime.MaxValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ namespace TShockAPI.DB
|
||||||
com.CommandText = query;
|
com.CommandText = query;
|
||||||
for (int i = 0; i < args.Length; i++)
|
for (int i = 0; i < args.Length; i++)
|
||||||
com.AddParameter("@" + i, args[i]);
|
com.AddParameter("@" + i, args[i]);
|
||||||
|
|
||||||
return com.ExecuteNonQuery();
|
return com.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +80,36 @@ namespace TShockAPI.DB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a query on a database, returning the first column of the first row of the result set.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="olddb">Database to query</param>
|
||||||
|
/// <param name="query">Query string with parameters as @0, @1, etc.</param>
|
||||||
|
/// <param name="args">Parameters to be put in the query</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T QueryScalar<T>(this IDbConnection olddb, string query, params object[] args)
|
||||||
|
{
|
||||||
|
using (var db = olddb.CloneEx())
|
||||||
|
{
|
||||||
|
db.Open();
|
||||||
|
using (var com = db.CreateCommand())
|
||||||
|
{
|
||||||
|
com.CommandText = query;
|
||||||
|
for (int i = 0; i < args.Length; i++)
|
||||||
|
com.AddParameter("@" + i, args[i]);
|
||||||
|
|
||||||
|
object output = com.ExecuteScalar();
|
||||||
|
if (typeof(IConvertible).IsAssignableFrom(output.GetType()))
|
||||||
|
{
|
||||||
|
return (T)Convert.ChangeType(output, typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static QueryResult QueryReaderDict(this IDbConnection olddb, string query, Dictionary<string, object> values)
|
public static QueryResult QueryReaderDict(this IDbConnection olddb, string query, Dictionary<string, object> values)
|
||||||
{
|
{
|
||||||
var db = olddb.CloneEx();
|
var db = olddb.CloneEx();
|
||||||
|
|
@ -156,10 +185,10 @@ namespace TShockAPI.DB
|
||||||
typeof (Int32?),
|
typeof (Int32?),
|
||||||
(s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i)
|
(s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i)
|
||||||
},
|
},
|
||||||
{
|
/*{
|
||||||
typeof (Int64),
|
typeof (Int64),
|
||||||
(s, i) => s.GetInt64(i)
|
(s, i) => s.GetInt64(i)
|
||||||
},
|
},*/
|
||||||
{
|
{
|
||||||
typeof (Int64?),
|
typeof (Int64?),
|
||||||
(s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i)
|
(s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i)
|
||||||
|
|
@ -210,12 +239,24 @@ namespace TShockAPI.DB
|
||||||
public static T Get<T>(this IDataReader reader, int column)
|
public static T Get<T>(this IDataReader reader, int column)
|
||||||
{
|
{
|
||||||
if (reader.IsDBNull(column))
|
if (reader.IsDBNull(column))
|
||||||
return default(T);
|
return default;
|
||||||
|
|
||||||
if (ReadFuncs.ContainsKey(typeof(T)))
|
if (ReadFuncs.ContainsKey(typeof(T)))
|
||||||
return (T)ReadFuncs[typeof(T)](reader, column);
|
return (T)ReadFuncs[typeof(T)](reader, column);
|
||||||
|
|
||||||
throw new NotImplementedException();
|
Type t;
|
||||||
|
if (typeof(T) != (t = reader.GetFieldType(column)))
|
||||||
|
{
|
||||||
|
string columnName = reader.GetName(column);
|
||||||
|
throw new InvalidCastException($"Received type '{typeof(T).Name}', however column '{columnName}' expects type '{t.Name}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.IsDBNull(column))
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)reader.GetValue(column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace TShockAPI
|
namespace TShockAPI
|
||||||
|
|
@ -27,5 +28,16 @@ namespace TShockAPI
|
||||||
{
|
{
|
||||||
return String.Format(str, args);
|
return String.Format(str, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps the string representation of an object with a Terraria color code for the given color
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj"></param>
|
||||||
|
/// <param name="color"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string Color(this object obj, string color)
|
||||||
|
{
|
||||||
|
return $"[c/{color}:{obj}]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -638,25 +638,25 @@ namespace TShockAPI
|
||||||
if (TShock.Bans.InsertBan(identifier, reason, args.TokenData.Username, startDate, endDate) != null)
|
if (TShock.Bans.InsertBan(identifier, reason, args.TokenData.Username, startDate, endDate) != null)
|
||||||
{
|
{
|
||||||
TSPlayer player = null;
|
TSPlayer player = null;
|
||||||
if (identifier.StartsWith(Ban.Identifiers.IP))
|
if (identifier.StartsWith(Identifiers.IP))
|
||||||
{
|
{
|
||||||
player = TShock.Players.FirstOrDefault(p => p.IP == identifier.Substring(Ban.Identifiers.IP.Length));
|
player = TShock.Players.FirstOrDefault(p => p.IP == identifier.Substring(Identifiers.IP.Length));
|
||||||
}
|
}
|
||||||
else if (identifier.StartsWith(Ban.Identifiers.Name))
|
else if (identifier.StartsWith(Identifiers.Name))
|
||||||
{
|
{
|
||||||
//Character names may not necessarily be unique, so kick all matches
|
//Character names may not necessarily be unique, so kick all matches
|
||||||
foreach (var ply in TShock.Players.Where(p => p.Name == identifier.Substring(Ban.Identifiers.Name.Length)))
|
foreach (var ply in TShock.Players.Where(p => p.Name == identifier.Substring(Identifiers.Name.Length)))
|
||||||
{
|
{
|
||||||
ply.Kick(reason, true);
|
ply.Kick(reason, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (identifier.StartsWith(Ban.Identifiers.Account))
|
else if (identifier.StartsWith(Identifiers.Account))
|
||||||
{
|
{
|
||||||
player = TShock.Players.FirstOrDefault(p => p.Account?.Name == identifier.Substring(Ban.Identifiers.Account.Length));
|
player = TShock.Players.FirstOrDefault(p => p.Account?.Name == identifier.Substring(Identifiers.Account.Length));
|
||||||
}
|
}
|
||||||
else if (identifier.StartsWith(Ban.Identifiers.UUID))
|
else if (identifier.StartsWith(Identifiers.UUID))
|
||||||
{
|
{
|
||||||
player = TShock.Players.FirstOrDefault(p => p.UUID == identifier.Substring(Ban.Identifiers.UUID.Length));
|
player = TShock.Players.FirstOrDefault(p => p.UUID == identifier.Substring(Identifiers.UUID.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player != null)
|
if (player != null)
|
||||||
|
|
@ -673,18 +673,23 @@ namespace TShockAPI
|
||||||
[Description("Delete an existing ban entry.")]
|
[Description("Delete an existing ban entry.")]
|
||||||
[Route("/v3/bans/destroy")]
|
[Route("/v3/bans/destroy")]
|
||||||
[Permission(RestPermissions.restmanagebans)]
|
[Permission(RestPermissions.restmanagebans)]
|
||||||
[Noun("identifier", true, "The identifier of the ban to delete.", typeof(String))]
|
[Noun("uniqueId", true, "The unique ID of the ban to delete.", typeof(String))]
|
||||||
[Noun("fullDelete", false, "Whether or not to completely remove the ban from the system.", typeof(bool))]
|
[Noun("fullDelete", false, "Whether or not to completely remove the ban from the system.", typeof(bool))]
|
||||||
[Token]
|
[Token]
|
||||||
private object BanDestroyV3(RestRequestArgs args)
|
private object BanDestroyV3(RestRequestArgs args)
|
||||||
{
|
{
|
||||||
string identifier = args.Parameters["identifier"];
|
string id = args.Parameters["uniqueId"];
|
||||||
if (string.IsNullOrWhiteSpace(identifier))
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
return RestMissingParam("identifier");
|
return RestMissingParam("uniqueId");
|
||||||
|
|
||||||
|
if (!int.TryParse(id, out int uniqueId))
|
||||||
|
{
|
||||||
|
return RestInvalidParam("uniqueId");
|
||||||
|
}
|
||||||
|
|
||||||
bool.TryParse(args.Parameters["fullDelete"], out bool fullDelete);
|
bool.TryParse(args.Parameters["fullDelete"], out bool fullDelete);
|
||||||
|
|
||||||
if (TShock.Bans.RemoveBan(identifier, fullDelete))
|
if (TShock.Bans.RemoveBan(uniqueId, fullDelete))
|
||||||
{
|
{
|
||||||
return RestResponse("Ban removed.");
|
return RestResponse("Ban removed.");
|
||||||
}
|
}
|
||||||
|
|
@ -695,15 +700,20 @@ namespace TShockAPI
|
||||||
[Description("View the details of a specific ban.")]
|
[Description("View the details of a specific ban.")]
|
||||||
[Route("/v3/bans/read")]
|
[Route("/v3/bans/read")]
|
||||||
[Permission(RestPermissions.restviewbans)]
|
[Permission(RestPermissions.restviewbans)]
|
||||||
[Noun("identifier", true, "The identifier to search for.", typeof(String))]
|
[Noun("uniqueId", true, "The unique ID to search for.", typeof(String))]
|
||||||
[Token]
|
[Token]
|
||||||
private object BanInfoV3(RestRequestArgs args)
|
private object BanInfoV3(RestRequestArgs args)
|
||||||
{
|
{
|
||||||
string identifier = args.Parameters["identifier"];
|
string id = args.Parameters["uniqueId"];
|
||||||
if (string.IsNullOrWhiteSpace(identifier))
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
return RestMissingParam("identifier");
|
return RestMissingParam("uniqueId");
|
||||||
|
|
||||||
Ban ban = TShock.Bans.GetBanByIdentifier(identifier);
|
if (!int.TryParse(id, out int uniqueId))
|
||||||
|
{
|
||||||
|
return RestInvalidParam("uniqueId");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ban ban = TShock.Bans.GetBanById(uniqueId);
|
||||||
|
|
||||||
if (ban == null)
|
if (ban == null)
|
||||||
{
|
{
|
||||||
|
|
@ -726,7 +736,7 @@ namespace TShockAPI
|
||||||
[Token]
|
[Token]
|
||||||
private object BanListV3(RestRequestArgs args)
|
private object BanListV3(RestRequestArgs args)
|
||||||
{
|
{
|
||||||
IEnumerable<Ban> bans = TShock.Bans.GetAllBans();
|
IEnumerable<Ban> bans = TShock.Bans.Bans.Select(kvp => kvp.Value);
|
||||||
|
|
||||||
var banList = new ArrayList();
|
var banList = new ArrayList();
|
||||||
foreach (var ban in bans)
|
foreach (var ban in bans)
|
||||||
|
|
|
||||||
|
|
@ -1638,11 +1638,11 @@ namespace TShockAPI
|
||||||
return true;
|
return true;
|
||||||
if (force || !HasPermission(Permissions.immunetoban))
|
if (force || !HasPermission(Permissions.immunetoban))
|
||||||
{
|
{
|
||||||
TShock.Bans.InsertBan($"{DB.Ban.Identifiers.IP}{IP}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
TShock.Bans.InsertBan($"{Identifiers.IP}{IP}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
||||||
TShock.Bans.InsertBan($"{DB.Ban.Identifiers.IP}{UUID}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
TShock.Bans.InsertBan($"{Identifiers.IP}{UUID}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
||||||
if (Account != null)
|
if (Account != null)
|
||||||
{
|
{
|
||||||
TShock.Bans.InsertBan($"{DB.Ban.Identifiers.Account}{Account.Name}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
TShock.Bans.InsertBan($"{Identifiers.Account}{Account.Name}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Disconnect(string.Format("Banned: {0}", reason));
|
Disconnect(string.Format("Banned: {0}", reason));
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ using Terraria.Utilities;
|
||||||
using TShockAPI;
|
using TShockAPI;
|
||||||
using TShockAPI.DB;
|
using TShockAPI.DB;
|
||||||
using Terraria.Localization;
|
using Terraria.Localization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace TShockAPI
|
namespace TShockAPI
|
||||||
{
|
{
|
||||||
|
|
@ -41,30 +42,22 @@ namespace TShockAPI
|
||||||
|
|
||||||
public override void SendErrorMessage(string msg)
|
public override void SendErrorMessage(string msg)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
SendConsoleMessage(msg, 255, 0, 0);
|
||||||
Console.WriteLine(msg);
|
|
||||||
Console.ResetColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendInfoMessage(string msg)
|
public override void SendInfoMessage(string msg)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
SendConsoleMessage(msg, 255, 250, 170);
|
||||||
Console.WriteLine(msg);
|
|
||||||
Console.ResetColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendSuccessMessage(string msg)
|
public override void SendSuccessMessage(string msg)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
SendConsoleMessage(msg, 0, 255, 0);
|
||||||
Console.WriteLine(msg);
|
|
||||||
Console.ResetColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendWarningMessage(string msg)
|
public override void SendWarningMessage(string msg)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.DarkRed;
|
SendConsoleMessage(msg, 139, 0, 0);
|
||||||
Console.WriteLine(msg);
|
|
||||||
Console.ResetColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SendMessage(string msg, Color color)
|
public override void SendMessage(string msg, Color color)
|
||||||
|
|
@ -74,7 +67,28 @@ namespace TShockAPI
|
||||||
|
|
||||||
public override void SendMessage(string msg, byte red, byte green, byte blue)
|
public override void SendMessage(string msg, byte red, byte green, byte blue)
|
||||||
{
|
{
|
||||||
Console.WriteLine(msg);
|
SendConsoleMessage(msg, red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendConsoleMessage(string msg, byte red, byte green, byte blue)
|
||||||
|
{
|
||||||
|
var snippets = Terraria.UI.Chat.ChatManager.ParseMessage(msg, new Color(red, green, blue));
|
||||||
|
|
||||||
|
foreach (var snippet in snippets)
|
||||||
|
{
|
||||||
|
if (snippet.Color != null)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = PickNearbyConsoleColor(snippet.Color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Write(snippet.Text);
|
||||||
|
}
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ResetColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetFullMoon()
|
public void SetFullMoon()
|
||||||
|
|
@ -179,5 +193,47 @@ namespace TShockAPI
|
||||||
All.SendTileSquare((int)coords.X, (int)coords.Y, 3);
|
All.SendTileSquare((int)coords.X, (int)coords.Y, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readonly Dictionary<Color, ConsoleColor> _consoleColorMap = new Dictionary<Color, ConsoleColor>
|
||||||
|
{
|
||||||
|
{ Color.Red, ConsoleColor.Red },
|
||||||
|
{ Color.Green, ConsoleColor.Green },
|
||||||
|
{ Color.Blue, ConsoleColor.Cyan },
|
||||||
|
{ new Color(255, 250, 170), ConsoleColor.Yellow },
|
||||||
|
{ new Color(170, 170, 255), ConsoleColor.Cyan },
|
||||||
|
{ new Color(255, 170, 255), ConsoleColor.Magenta },
|
||||||
|
{ new Color(170, 255, 170), ConsoleColor.Green },
|
||||||
|
{ new Color(255, 170, 170), ConsoleColor.Red },
|
||||||
|
{ new Color(139, 0, 0), ConsoleColor.DarkRed }, // This is the console warning color
|
||||||
|
{ Color.PaleVioletRed, ConsoleColor.Magenta }, // This is the command logging color
|
||||||
|
{ Color.White, ConsoleColor.White }
|
||||||
|
};
|
||||||
|
|
||||||
|
private ConsoleColor PickNearbyConsoleColor(Color color)
|
||||||
|
{
|
||||||
|
//Grabs an integer difference between two colors in euclidean space
|
||||||
|
int ColorDiff(Color c1, Color c2)
|
||||||
|
{
|
||||||
|
return (int)Math.Sqrt((c1.R - c2.R) * (c1.R - c2.R)
|
||||||
|
+ (c1.G - c2.G) * (c1.G - c2.G)
|
||||||
|
+ (c1.B - c2.B) * (c1.B - c2.B));
|
||||||
|
}
|
||||||
|
|
||||||
|
var diffs = _consoleColorMap.Select(kvp => ColorDiff(kvp.Key, color));
|
||||||
|
int index = 0;
|
||||||
|
int min = int.MaxValue;
|
||||||
|
|
||||||
|
for (int i = 0; i < _consoleColorMap.Count; i++)
|
||||||
|
{
|
||||||
|
if (diffs.ElementAt(i) < min)
|
||||||
|
{
|
||||||
|
index = i;
|
||||||
|
min = diffs.ElementAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _consoleColorMap.Values.ElementAt(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -478,13 +478,21 @@ namespace TShockAPI
|
||||||
args.Player.Account.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
|
args.Player.Account.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
|
||||||
UserAccounts.UpdateLogin(args.Player.Account);
|
UserAccounts.UpdateLogin(args.Player.Account);
|
||||||
|
|
||||||
//Check if this user has any recorded bans
|
//Check if this user has a recorded ban on their account
|
||||||
var validBan = Bans.GetBansByIdentifiers($"acc:{args.Player.Account.Name}", $"uuid:{args.Player.UUID}").FirstOrDefault(b => Bans.IsValidBan(b));
|
var ban = Bans.Bans.FirstOrDefault(b => b.Value.Identifier == $"{Identifiers.Account}{args.Player.Account.Name}" && Bans.IsValidBan(b.Value, args.Player)).Value;
|
||||||
|
|
||||||
//If they do and any are still valid, kick them
|
//If they do and the ban is still valid, kick them
|
||||||
if (validBan != null)
|
if (ban != null && !args.Player.HasPermission(Permissions.immunetoban))
|
||||||
{
|
{
|
||||||
args.Player.Kick($"You are banned: {validBan.Reason}", true, true);
|
if (ban.ExpirationDateTime == DateTime.MaxValue)
|
||||||
|
{
|
||||||
|
args.Player.Disconnect("You are banned: " + ban.Reason);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TimeSpan ts = ban.ExpirationDateTime - DateTime.UtcNow;
|
||||||
|
args.Player.Disconnect($"You are banned: {ban.Reason} ({ban.GetPrettyExpirationString()} remaining)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1196,7 +1204,14 @@ namespace TShockAPI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ban ban = Bans.GetBansByIdentifiers($"name:{player.Name}", $"uuid:{player.UUID}", $"ip:{player.IP}").FirstOrDefault(b => Bans.IsValidBan(b));
|
List<string> identifiers = new List<string>
|
||||||
|
{
|
||||||
|
$"{Identifiers.UUID}{player.UUID}",
|
||||||
|
$"{Identifiers.Name}{player.Name}",
|
||||||
|
$"{Identifiers.IP}{player.IP}"
|
||||||
|
};
|
||||||
|
|
||||||
|
Ban ban = Bans.Bans.FirstOrDefault(b => identifiers.Contains(b.Value.Identifier) && Bans.IsValidBan(b.Value, player)).Value;
|
||||||
|
|
||||||
if (ban != null)
|
if (ban != null)
|
||||||
{
|
{
|
||||||
|
|
@ -1207,33 +1222,9 @@ namespace TShockAPI
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TimeSpan ts = ban.ExpirationDateTime - DateTime.UtcNow;
|
TimeSpan ts = ban.ExpirationDateTime - DateTime.UtcNow;
|
||||||
int months = ts.Days / 30;
|
player.Disconnect($"You are banned: {ban.Reason} ({ban.GetPrettyExpirationString()} remaining)");
|
||||||
if (months > 0)
|
|
||||||
{
|
|
||||||
player.Disconnect(String.Format("You are banned for {0} month{1} and {2} day{3}: {4}",
|
|
||||||
months, months == 1 ? "" : "s", ts.Days, ts.Days == 1 ? "" : "s", ban.Reason));
|
|
||||||
}
|
|
||||||
else if (ts.Days > 0)
|
|
||||||
{
|
|
||||||
player.Disconnect(String.Format("You are banned for {0} day{1} and {2} hour{3}: {4}",
|
|
||||||
ts.Days, ts.Days == 1 ? "" : "s", ts.Hours, ts.Hours == 1 ? "" : "s", ban.Reason));
|
|
||||||
}
|
|
||||||
else if (ts.Hours > 0)
|
|
||||||
{
|
|
||||||
player.Disconnect(String.Format("You are banned for {0} hour{1} and {2} minute{3}: {4}",
|
|
||||||
ts.Hours, ts.Hours == 1 ? "" : "s", ts.Minutes, ts.Minutes == 1 ? "" : "s", ban.Reason));
|
|
||||||
}
|
|
||||||
else if (ts.Minutes > 0)
|
|
||||||
{
|
|
||||||
player.Disconnect(String.Format("You are banned for {0} minute{1} and {2} second{3}: {4}",
|
|
||||||
ts.Minutes, ts.Minutes == 1 ? "" : "s", ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
player.Disconnect(String.Format("You are banned for {0} second{1}: {2}",
|
|
||||||
ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ProjectExtensions>
|
<ProjectExtensions>
|
||||||
<VisualStudio>
|
<VisualStudio>
|
||||||
<UserProperties BuildVersion_IncrementBeforeBuild="False" BuildVersion_StartDate="2011/6/17" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_BuildAction="Both" BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" />
|
<UserProperties BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" BuildVersion_BuildAction="Both" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_StartDate="2011/6/17" BuildVersion_IncrementBeforeBuild="False" />
|
||||||
</VisualStudio>
|
</VisualStudio>
|
||||||
</ProjectExtensions>
|
</ProjectExtensions>
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ using System.Text.RegularExpressions;
|
||||||
using Terraria;
|
using Terraria;
|
||||||
using Terraria.ID;
|
using Terraria.ID;
|
||||||
using Terraria.Utilities;
|
using Terraria.Utilities;
|
||||||
using TShockAPI.DB;
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Terraria.Localization;
|
using Terraria.Localization;
|
||||||
using TShockAPI.Localization;
|
using TShockAPI.Localization;
|
||||||
|
|
@ -39,6 +38,31 @@ namespace TShockAPI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Utils
|
public class Utils
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hex code for a blue pastel color
|
||||||
|
/// </summary>
|
||||||
|
public const string BoldHighlight = "AAAAFF";
|
||||||
|
/// <summary>
|
||||||
|
/// Hex code for a red pastel color
|
||||||
|
/// </summary>
|
||||||
|
public const string RedHighlight = "FFAAAA";
|
||||||
|
/// <summary>
|
||||||
|
/// Hex code for a green pastel color
|
||||||
|
/// </summary>
|
||||||
|
public const string GreenHighlight = "AAFFAA";
|
||||||
|
/// <summary>
|
||||||
|
/// Hex code for a pink pastel color
|
||||||
|
/// </summary>
|
||||||
|
public const string PinkHighlight = "FFAAFF";
|
||||||
|
/// <summary>
|
||||||
|
/// Hex code for a yellow pastel color
|
||||||
|
/// </summary>
|
||||||
|
public const string YellowHighlight = "FFFAAA";
|
||||||
|
/// <summary>
|
||||||
|
/// Hex code for a white highlight
|
||||||
|
/// </summary>
|
||||||
|
public const string WhiteHighlight = "FFFFFF";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The lowest id for a prefix.
|
/// The lowest id for a prefix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -465,7 +489,14 @@ namespace TShockAPI
|
||||||
if (save)
|
if (save)
|
||||||
SaveManager.Instance.SaveWorld();
|
SaveManager.Instance.SaveWorld();
|
||||||
|
|
||||||
TSPlayer.All.Kick(reason, true, true, null, true);
|
foreach (var player in TShock.Players.Where(p => p != null))
|
||||||
|
{
|
||||||
|
if (player.IsLoggedIn)
|
||||||
|
{
|
||||||
|
player.SaveServerCharacter();
|
||||||
|
}
|
||||||
|
player.Disconnect(reason);
|
||||||
|
}
|
||||||
|
|
||||||
// Broadcast so console can see we are shutting down as well
|
// Broadcast so console can see we are shutting down as well
|
||||||
TShock.Utils.Broadcast(reason, Color.Red);
|
TShock.Utils.Broadcast(reason, Color.Red);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue