Merge remote-tracking branch 'parent/general-devel' into fix-invalid-place-style

This commit is contained in:
Arthri 2021-11-24 09:21:40 +08:00
commit a1eaf285cd
14 changed files with 416 additions and 479 deletions

View file

@ -13,6 +13,18 @@ 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.
## Upcoming changes
* Removed `TShockAPI/DB/DBTools.cs`. This appears to have been dead code and not used by anything. (@hakusaro, @DeathCradle)
* Fixed the `/firework` command not sending fireworks when specified without a firework color. The firework command now correctly sends red fireworks to a target if a color is not specified. (@hakusaro, @Kojirremer)
* Fixed bad XML of TShock code documentation. (@Arthri)
## TShock 4.5.7
* Fixed the `/respawn` command to permit respawning players from the console. (@hakusaro, @Kojirremer)
* Removed the old password hashing system, which predated `bcrypt` hashes and allowed specifying the hash algorithm in the config file. This also removes the config option for setting the hash algorithm (`HashAlgorithm`). This is because it helps clear the way for .NET5/6 and OTAPI 3, and because `bcrypt` has been the default since TShock 4.3 in 2015. (@hakusaro)
* Updated to OTAPI 2.0.0.46, which adds support for Terraria Protocol 1.4.3.1. (@Patrikkk, @DeathCradle)
* **Change warning: a release of TShock for .NET 5 and OTAPI 3 may be imminent.**
## TShock 4.5.6
* Updated Linux guide. (@NezbednikSK)
* Fixed SendTileRectHandler not sending tile rect updates like Pylons/Mannequins to other clients. (@Stealownz)
* Introduced `SoftcoreOnly` config option to allow only softcore characters to connect. (@drunderscore)
* Fixed some typos that have been in the repository for over a lustrum. (@Killia0)
@ -26,6 +38,17 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Fixed Bouncer exploits allowing for invalid tiles' placement. These tiles(specifically torches) caused clients to crash. The fixed exploits are listed below. (@Arthri, @QuiCM)
- [Biome Torch Correction](https://github.com/Pryaxis/TShock/blob/3ba1e7419d63535eeb8b5634ec668448499f71df/TShockAPI/Bouncer.cs#L310). Previously, it used unrelated values to validate biome torches, and unintentionally passed on invalid tiles. It's now fixed to use the correct values and block invalid torches. As well as a new right booster track correction/check, to allow right booster tracks to be placed. Right booster track is an extraneous place style because it depends on the direction the player is facing. The new check takes that into consideration so that the place style isn't considered mismatched and rejected.
- [Max Place Styles](https://github.com/Pryaxis/TShock/blob/3ba1e7419d63535eeb8b5634ec668448499f71df/TShockAPI/Bouncer.cs#L385). Previously, it rejects only if both `MaxPlaceStyles` and `ExtraneousPlaceStyles` contains an entry for a tile, and unintentionally passed on invalid tiles. `ExtraneousPlaceStyles` only contains special max placeStyles, not all placeables unlike `MaxPlaceStyles`. It's now corrected to take from `ExtraneousPlaceStyles` first, then fallback to `MaxPlaceStyles` if there's no entry for that tile, and then finally -1 if there's no entry in either.
* Fixed a bug that caused sundials to be ignored all the time, instead of only when the player has no permission or time is frozen. (@Rozen4334)
* Added colours and usage examples (similiar to how the new ban system looks) for many more commands. (@moisterrific)
* Changed `RespawnSeconds` and `RespawnBossSeconds` from `10` to `0` to respect the game's default respawn timers. (@moisterrific)
* Updated Open Terraria API (OTAPI) and TSAPI for preliminary support of Terraria 1.4.3.0. This functionally changes OTAPI and points to 2.0.0.45 from 2.0.0.43 in previous versions. Developer note: `SendData` takes an extra arg in this version of Terraria but that's slated to be removed in a Terraria hotfix. This is vestigial and OTAPI "hacks that out" to preserve plugin compatibility. That's why it'll differ from the source code. (@Patrikkk, @DeathCradle, honorable mention: @Moneylover3246)
* Added Deerclops to `/spawnboss` command. (@hakusaro, @HiddenStriker)
* Fixed [GHSA-6w5v-hxr3-m2wx](https://github.com/Pryaxis/TShock/security/advisories/GHSA-6w5v-hxr3-m2wx). (@Yoraiz0r, @Arthri)
* Fixed an issue where player build permissions would reject gem lock changes, even if `RegionProtectGemLocks` was disabled in the config file. Now, players will be permitted to use gem locks if they don't have build permission in a region, but `RegionProtectGemLocks` is disabled. If `RegionProtectGemLocks` is enabled, players will be unable to use gem locks in a build region. (@hakusaro, @Kojirremer, @Arthri)
* Fixed an issue where `/god [player]` would tell `[player]` that they were in godmode regardless of whether or not they were or not. (@hakusaro, @Kojirremer)
* In `TSAPI`: Updated `PacketTypes` to support `SetMiscEventValues` (140), `RequestLucyPopup` (141), and `SyncProjectileTrackers` (142). (@hakusaro)
* Added `DisableDefaultIPBan` to the config file. If set to `true`, the server will not automatically IP ban players when banning them. This is useful if you run an intercepting proxy in front of TShock, and all players share the same IP. (@hakusaro, and Telegram user xmzzhh233)
* Blank passwords will be upgraded to `bcrypt` hashes automatically. Previously, blank passwords were not upgraded to bcrypt hashes. This is in preparation to remove the old password hashing system and related fallback components in the next release. Most users have been using bcrypt hashes for the past...few years. (@hakusaro)
## TShock 4.5.5
* Changed the world autosave message so that it no longer warns of a "potential lag spike." (@hakusaro)
@ -53,6 +76,9 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* `OnPlaceTileEntity`: The check was newly added.
* `OnPlaceItemFrame`: The check was newly added.
* `OnFoodPlatterTryPlacing`: The check was newly added.
* Fixed errors on startup not being reported to console. (@bartico6)
* The server now correctly disconnects players with missing groups instead of throwing an exception, stalling the connection (@bartico6)
* The server now rejects login attempts from players who would end up with a missing group. (@bartico6)
## TShock 4.5.4
* Fixed ridiculous typo in `GetDataHandlers` which caused TShock to read the wrong field in the packet for `usingBiomeTorches`. (@hakusaro, @Arthri)
@ -81,6 +107,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* TShock defaults to saving backups every 10 minutes, and defaults to keeping backups for 4 hours. (@hakusaro)
* Updated SSC bypass messaging. Now, when you connect, you're told if you're bypassing SSC. Console logging has been improved to warn when players are not being saved due to the bypass SSC permission. To turn this warning off, change `WarnPlayersAboutBypassPermission` to `false` in the `sscconfig.json` file. (@hakusaro)
* Fix oversight & exploit allowing specially crafted SendTileRectangle packets to perform large-scale world griefing. In addition, `NetTile.Slope` is now the native value (byte), and accessor methods `Slope1`, `Slope2`, and `Slope3` can be used to get the old style of values out. `HalfBrick` and `Actuator` were removed from `NetTile` because these were initialized to zero and never changed or used. (@bartico6)
* Warning: a bug introduced in a prior TShock release may cause your SSC config file to be reset after applying this update. Please backup your config file prior to installing TShock 4.5.3+ if you use SSC. (@cardinal-system)
## TShock 4.5.2
* Added preliminary support for Terraria 1.4.2.2. (@hakusaro)

View file

@ -212,7 +212,7 @@ You need to re-run the patcher any time `OTAPI` updates. You need to rebuild `Te
1. Verify that non-zero modifications ran successfully. Then, build the Terraria Server API executable.
$ cd ./../../../
$ cd ../../../../
$ xbuild ./TerrariaServerAPI/TerrariaServerAPI/TerrariaServerAPI.csproj \
/p:Configuration=$BUILD_MODE
@ -220,13 +220,13 @@ You need to re-run the patcher any time `OTAPI` updates. You need to rebuild `Te
##### TShock
1. Perform a NuGet restore in `TShockAPI` folder that contains `TShockAPI.sln`.
1. Perform a NuGet restore in the folder that contains `TShock.sln`.
$ mono ~/bin/nuget.exe restore
1. Build TShock in the `BUILD_MODE` you set earlier.
$ xbuild ./TShockAPI.sln /p:Configuration=$BUILD_MODE
$ xbuild ./TShock.sln /p:Configuration=$BUILD_MODE
You're done!

View file

@ -2078,6 +2078,8 @@ namespace TShockAPI
return;
}
if (TShock.Config.Settings.RegionProtectGemLocks)
{
if (!args.Player.HasBuildPermission(args.X, args.Y))
{
TShock.Log.ConsoleDebug("Bouncer / OnGemLockToggle rejected permissions check from {0}", args.Player.Name);
@ -2085,6 +2087,7 @@ namespace TShockAPI
return;
}
}
}
/// <summary>Handles validation of of basic anti-cheat on mass wire operations.</summary>
/// <param name="sender">The object that triggered the event.</param>

View file

@ -582,11 +582,11 @@ namespace TShockAPI
{
HelpText = "Teleports you to a warp point or manages warps."
});
add(new Command(Permissions.whisper, Whisper, "whisper", "w", "tell")
add(new Command(Permissions.whisper, Whisper, "whisper", "w", "tell", "pm", "dm")
{
HelpText = "Sends a PM to a player."
});
add(new Command(Permissions.whisper, Wallow, "wallow")
add(new Command(Permissions.whisper, Wallow, "wallow", "wa")
{
AllowServer = false,
HelpText = "Toggles to either ignore or recieve whispers from other players."
@ -832,10 +832,16 @@ namespace TShockAPI
(usingUUID && account.UUID == args.Player.UUID && !TShock.Config.Settings.DisableUUIDLogin &&
!String.IsNullOrWhiteSpace(args.Player.UUID)))
{
args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
var group = TShock.Groups.GetGroupByName(account.Group);
if (!TShock.Groups.AssertGroupValid(args.Player, group, false))
{
args.Player.SendErrorMessage("Login attempt failed - see the message above.");
return;
}
args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
args.Player.Group = group;
args.Player.tempGroup = null;
args.Player.Account = account;
@ -1508,6 +1514,11 @@ namespace TShockAPI
if (!exactTarget && !banAccount && !banUuid && !banName && !banIp)
{
banAccount = banUuid = banIp = true;
if (TShock.Config.Settings.DisableDefaultIPBan)
{
banIp = false;
}
}
reason = reason ?? "Banned";
@ -2668,6 +2679,11 @@ namespace TShockAPI
TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY);
spawnName = "a Stardust Pillar";
break;
case "deerclops":
npc.SetDefaults(668);
TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY);
spawnName = "a Deerclops";
break;
default:
args.Player.SendErrorMessage("Invalid boss type!");
return;
@ -5103,7 +5119,7 @@ namespace TShockAPI
}
IEnumerable<string> cmdNames = from cmd in ChatCommands
where cmd.CanRun(args.Player) && (cmd.Name != "auth" || TShock.SetupToken != 0)
where cmd.CanRun(args.Player) && (cmd.Name != "setup" || TShock.SetupToken != 0)
select Specifier + cmd.Name;
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(cmdNames),
@ -5148,7 +5164,7 @@ namespace TShockAPI
private static void GetVersion(CommandArgs args)
{
args.Player.SendInfoMessage("TShock: {0} ({1}).", TShock.VersionNum, TShock.VersionCodename);
args.Player.SendMessage($"TShock: {TShock.VersionNum.Color(Utils.BoldHighlight)} {TShock.VersionCodename.Color(Utils.RedHighlight)}.", Color.White);
}
private static void ListConnectedPlayers(CommandArgs args)
@ -5176,16 +5192,24 @@ namespace TShockAPI
}
if (invalidUsage)
{
args.Player.SendErrorMessage("Invalid usage, proper usage: {0}who [-i] [pagenumber]", Specifier);
args.Player.SendMessage($"List Online Players Syntax", Color.White);
args.Player.SendMessage($"{"playing".Color(Utils.BoldHighlight)} {"[-i]".Color(Utils.RedHighlight)} {"[page]".Color(Utils.GreenHighlight)}", Color.White);
args.Player.SendMessage($"Command aliases: {"playing".Color(Utils.GreenHighlight)}, {"online".Color(Utils.GreenHighlight)}, {"who".Color(Utils.GreenHighlight)}", Color.White);
args.Player.SendMessage($"Example usage: {"who".Color(Utils.BoldHighlight)} {"-i".Color(Utils.RedHighlight)}", Color.White);
return;
}
if (displayIdsRequested && !args.Player.HasPermission(Permissions.seeids))
{
args.Player.SendErrorMessage("You do not have permission to list player ids.");
args.Player.SendErrorMessage("You do not have permission to see player IDs.");
return;
}
args.Player.SendSuccessMessage("Online Players ({0}/{1})", TShock.Utils.GetActivePlayerCount(), TShock.Config.Settings.MaxSlots);
if (TShock.Utils.GetActivePlayerCount() == 0)
{
args.Player.SendMessage("There are currently no players online.", Color.White);
return;
}
args.Player.SendMessage($"Online Players ({TShock.Utils.GetActivePlayerCount().Color(Utils.GreenHighlight)}/{TShock.Config.Settings.MaxSlots})", Color.White);
var players = new List<string>();
@ -5194,22 +5218,18 @@ namespace TShockAPI
if (ply != null && ply.Active)
{
if (displayIdsRequested)
{
players.Add(String.Format("{0} (Index: {1}{2})", ply.Name, ply.Index, ply.Account != null ? ", Account ID: " + ply.Account.ID : ""));
}
players.Add($"{ply.Name} (Index: {ply.Index}{(ply.Account != null ? ", Account ID: " + ply.Account.ID : "")})");
else
{
players.Add(ply.Name);
}
}
}
PaginationTools.SendPage(
args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(players),
new PaginationTools.Settings
{
IncludeHeader = false,
FooterFormat = string.Format("Type {0}who {1}{{0}} for more.", Specifier, displayIdsRequested ? "-i " : string.Empty)
FooterFormat = $"Type {Specifier}who {(displayIdsRequested ? "-i" : string.Empty)}{Specifier} for more."
}
);
}
@ -5303,14 +5323,17 @@ namespace TShockAPI
{
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}mute <player> [reason]", Specifier);
args.Player.SendMessage("Mute Syntax", Color.White);
args.Player.SendMessage($"{"mute".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}> [{"reason".Color(Utils.GreenHighlight)}]", Color.White);
args.Player.SendMessage($"Example usage: {"mute".Color(Utils.BoldHighlight)} \"{args.Player.Name.Color(Utils.RedHighlight)}\" \"{"No swearing on my Christian server".Color(Utils.GreenHighlight)}\"", Color.White);
args.Player.SendMessage($"To mute a player without broadcasting to chat, use the command with {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)}", Color.White);
return;
}
var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
args.Player.SendErrorMessage($"Could not find any players named \"{args.Parameters[0]}\"");
}
else if (players.Count > 1)
{
@ -5318,13 +5341,16 @@ namespace TShockAPI
}
else if (players[0].HasPermission(Permissions.mute))
{
args.Player.SendErrorMessage("You cannot mute this player.");
args.Player.SendErrorMessage($"You do not have permission to mute {players[0].Name}");
}
else if (players[0].mute)
{
var plr = players[0];
plr.mute = false;
TSPlayer.All.SendInfoMessage("{0} has been unmuted by {1}.", plr.Name, args.Player.Name);
if (args.Silent)
args.Player.SendSuccessMessage($"You have unmuted {plr.Name}.");
else
TSPlayer.All.SendInfoMessage($"{args.Player.Name} has unmuted {plr.Name}.");
}
else
{
@ -5333,7 +5359,10 @@ namespace TShockAPI
reason = String.Join(" ", args.Parameters.ToArray(), 1, args.Parameters.Count - 1);
var plr = players[0];
plr.mute = true;
TSPlayer.All.SendInfoMessage("{0} has been muted by {1} for {2}.", plr.Name, args.Player.Name, reason);
if (args.Silent)
args.Player.SendSuccessMessage($"You have muted {plr.Name} for {reason}");
else
TSPlayer.All.SendInfoMessage($"{args.Player.Name} has muted {plr.Name} for {reason}.");
}
}
@ -5351,13 +5380,15 @@ namespace TShockAPI
{
if (args.Parameters.Count < 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper usage: /whisper <player> <text>");
args.Player.SendMessage("Whisper Syntax", Color.White);
args.Player.SendMessage($"{"whisper".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}> <{"message".Color(Utils.PinkHighlight)}>", Color.White);
args.Player.SendMessage($"Example usage: {"w".Color(Utils.BoldHighlight)} {args.Player.Name.Color(Utils.RedHighlight)} {"We're no strangers to love, you know the rules, and so do I.".Color(Utils.PinkHighlight)}", Color.White);
return;
}
var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
args.Player.SendErrorMessage($"Could not find any player named \"{args.Parameters[0]}\"");
}
else if (players.Count > 1)
{
@ -5370,14 +5401,19 @@ namespace TShockAPI
else
{
var plr = players[0];
if (plr == args.Player)
{
args.Player.SendErrorMessage("You cannot whisper to yourself.");
return;
}
if (!plr.AcceptingWhispers)
{
args.Player.SendErrorMessage("This player is not accepting whispers.");
args.Player.SendErrorMessage($"{plr.Name} is not accepting whispers.");
return;
}
var msg = string.Join(" ", args.Parameters.ToArray(), 1, args.Parameters.Count - 1);
plr.SendMessage(String.Format("<From {0}> {1}", args.Player.Name, msg), Color.MediumPurple);
args.Player.SendMessage(String.Format("<To {0}> {1}", plr.Name, msg), Color.MediumPurple);
plr.SendMessage($"<From {args.Player.Name}> {msg}", Color.MediumPurple);
args.Player.SendMessage($"<To {plr.Name}> {msg}", Color.MediumPurple);
plr.LastWhisper = args.Player;
args.Player.LastWhisper = plr;
}
@ -5387,7 +5423,7 @@ namespace TShockAPI
{
args.Player.AcceptingWhispers = !args.Player.AcceptingWhispers;
args.Player.SendSuccessMessage($"You {(args.Player.AcceptingWhispers ? "may now" : "will no longer")} receive whispers from other players.");
args.Player.SendSuccessMessage($"You can toggle this with the '{Specifier}wallow' command.");
args.Player.SendMessage($"You can use {Specifier.Color(Utils.GreenHighlight)}{"wa".Color(Utils.GreenHighlight)} to toggle this setting.", Color.White);
}
private static void Reply(CommandArgs args)
@ -5400,20 +5436,21 @@ namespace TShockAPI
{
if (!args.Player.LastWhisper.AcceptingWhispers)
{
args.Player.SendErrorMessage("This player is not accepting whispers.");
args.Player.SendErrorMessage($"{args.Player.LastWhisper.Name} is not accepting whispers.");
return;
}
var msg = string.Join(" ", args.Parameters);
args.Player.LastWhisper.SendMessage(String.Format("<From {0}> {1}", args.Player.Name, msg), Color.MediumPurple);
args.Player.SendMessage(String.Format("<To {0}> {1}", args.Player.LastWhisper.Name, msg), Color.MediumPurple);
args.Player.LastWhisper.SendMessage($"<From {args.Player.Name}> {msg}", Color.MediumPurple);
args.Player.SendMessage($"<To {args.Player.LastWhisper.Name}> {msg}", Color.MediumPurple);
}
else if (args.Player.LastWhisper != null)
{
args.Player.SendErrorMessage("The player you're attempting to reply to is no longer online.");
args.Player.SendErrorMessage($"{args.Player.LastWhisper.Name} is offline and cannot receive your reply.");
}
else
{
args.Player.SendErrorMessage("You haven't previously received any whispers. Please use {0}whisper to whisper to other people.", Specifier);
args.Player.SendErrorMessage("You haven't previously received any whispers.");
args.Player.SendMessage($"You can use {Specifier.Color(Utils.GreenHighlight)}{"w".Color(Utils.GreenHighlight)} to whisper to other players.", Color.White);
}
}
@ -5421,7 +5458,10 @@ namespace TShockAPI
{
if (args.Parameters.Count != 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}annoy <player> <seconds to annoy>", Specifier);
args.Player.SendMessage("Annoy Syntax", Color.White);
args.Player.SendMessage($"{"annoy".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}> <{"seconds".Color(Utils.PinkHighlight)}>", Color.White);
args.Player.SendMessage($"Example usage: {"annoy".Color(Utils.BoldHighlight)} <{args.Player.Name.Color(Utils.RedHighlight)}> <{"10".Color(Utils.PinkHighlight)}>", Color.White);
args.Player.SendMessage($"You can use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)} to annoy a player silently.", Color.White);
return;
}
int annoy = 5;
@ -5429,14 +5469,16 @@ namespace TShockAPI
var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
args.Player.SendErrorMessage($"Could not find any player named \"{args.Parameters[0]}\"");
else if (players.Count > 1)
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var ply = players[0];
args.Player.SendSuccessMessage("Annoying " + ply.Name + " for " + annoy + " seconds.");
(new Thread(ply.Whoopie)).Start(annoy);
args.Player.SendSuccessMessage($"Annoying {ply.Name} for {annoy} seconds.");
if (!args.Silent)
ply.SendMessage("You are now being annoyed.", Color.LightGoldenrodYellow);
new Thread(ply.Whoopie).Start(annoy);
}
}
@ -5444,59 +5486,114 @@ namespace TShockAPI
{
if (args.Parameters.Count != 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}rocket <player>", Specifier);
args.Player.SendMessage("Rocket Syntax", Color.White);
args.Player.SendMessage($"{"rocket".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}>", Color.White);
args.Player.SendMessage($"Example usage: {"rocket".Color(Utils.BoldHighlight)} {args.Player.Name.Color(Utils.RedHighlight)}", Color.White);
args.Player.SendMessage($"You can use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)} to rocket a player silently.", Color.White);
return;
}
var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
args.Player.SendErrorMessage($"Could not find any player named \"{args.Parameters[0]}\"");
else if (players.Count > 1)
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var ply = players[0];
var target = players[0];
if (ply.IsLoggedIn && Main.ServerSideCharacter)
if (target.IsLoggedIn && Main.ServerSideCharacter)
{
ply.TPlayer.velocity.Y = -50;
TSPlayer.All.SendData(PacketTypes.PlayerUpdate, "", ply.Index);
args.Player.SendSuccessMessage("Rocketed {0}.", ply.Name);
target.TPlayer.velocity.Y = -50;
TSPlayer.All.SendData(PacketTypes.PlayerUpdate, "", target.Index);
if (!args.Silent)
{
TSPlayer.All.SendInfoMessage($"{args.Player.Name} has launched {(target == args.Player ? (args.Player.TPlayer.Male ? "himself" : "herself") : target.Name)} into space.");
return;
}
if (target == args.Player)
args.Player.SendSuccessMessage("You have launched yourself into space.");
else
args.Player.SendSuccessMessage($"You have launched {target.Name} into space.");
}
else
{
args.Player.SendErrorMessage("Failed to rocket player: Not logged in or not SSC mode.");
if (!Main.ServerSideCharacter)
args.Player.SendErrorMessage("SSC must be enabled to use this command.");
else
args.Player.SendErrorMessage($"Unable to rocket {target.Name} because {(target.TPlayer.Male ? "he" : "she")} is not logged in.");
}
}
}
private static void FireWork(CommandArgs args)
{
var user = args.Player;
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}firework <player> [red|green|blue|yellow]", Specifier);
// firework <player> [R|G|B|Y]
user.SendMessage("Firework Syntax", Color.White);
user.SendMessage($"{"firework".Color(Utils.CyanHighlight)} <{"player".Color(Utils.PinkHighlight)}> [{"R".Color(Utils.RedHighlight)}|{"G".Color(Utils.GreenHighlight)}|{"B".Color(Utils.BoldHighlight)}|{"Y".Color(Utils.YellowHighlight)}]", Color.White);
user.SendMessage($"Example usage: {"firework".Color(Utils.CyanHighlight)} {user.Name.Color(Utils.PinkHighlight)} {"R".Color(Utils.RedHighlight)}", Color.White);
user.SendMessage($"You can use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)} to launch a firework silently.", Color.White);
return;
}
var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
user.SendErrorMessage($"Could not find any player named \"{args.Parameters[0]}\"");
else if (players.Count > 1)
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
user.SendMultipleMatchError(players.Select(p => p.Name));
else
{
int type = 167;
int type = ProjectileID.RocketFireworkRed;
if (args.Parameters.Count > 1)
{
if (args.Parameters[1].ToLower() == "green")
type = 168;
else if (args.Parameters[1].ToLower() == "blue")
type = 169;
else if (args.Parameters[1].ToLower() == "yellow")
type = 170;
switch (args.Parameters[1].ToLower())
{
case "red":
case "r":
type = ProjectileID.RocketFireworkRed;
break;
case "green":
case "g":
type = ProjectileID.RocketFireworkGreen;
break;
case "blue":
case "b":
type = ProjectileID.RocketFireworkBlue;
break;
case "yellow":
case "y":
type = ProjectileID.RocketFireworkYellow;
break;
case "r2":
case "star":
type = ProjectileID.RocketFireworksBoxRed;
break;
case "g2":
case "spiral":
type = ProjectileID.RocketFireworksBoxGreen;
break;
case "b2":
case "rings":
type = ProjectileID.RocketFireworksBoxBlue;
break;
case "y2":
case "flower":
type = ProjectileID.RocketFireworksBoxYellow;
break;
default:
type = ProjectileID.RocketFireworkRed;
break;
}
var ply = players[0];
int p = Projectile.NewProjectile(Projectile.GetNoneSource(), ply.TPlayer.position.X, ply.TPlayer.position.Y - 64f, 0f, -8f, type, 0, (float)0);
}
var target = players[0];
int p = Projectile.NewProjectile(Projectile.GetNoneSource(), target.TPlayer.position.X, target.TPlayer.position.Y - 64f, 0f, -8f, type, 0, 0);
Main.projectile[p].Kill();
args.Player.SendSuccessMessage("Launched Firework on {0}.", ply.Name);
args.Player.SendSuccessMessage($"You launched fireworks on {(target == user ? "yourself" : target.Name)}.");
if (!args.Silent && target != user)
target.SendSuccessMessage($"{user.Name} launched fireworks on you.");
}
}
@ -5558,18 +5655,25 @@ namespace TShockAPI
private static void Clear(CommandArgs args)
{
var user = args.Player;
var everyone = TSPlayer.All;
int radius = 50;
if (args.Parameters.Count != 1 && args.Parameters.Count != 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}clear <item/npc/projectile> [radius]", Specifier);
user.SendMessage("Clear Syntax", Color.White);
user.SendMessage($"{"clear".Color(Utils.BoldHighlight)} <{"item".Color(Utils.GreenHighlight)}|{"npc".Color(Utils.RedHighlight)}|{"projectile".Color(Utils.YellowHighlight)}> [{"radius".Color(Utils.PinkHighlight)}]", Color.White);
user.SendMessage($"Example usage: {"clear".Color(Utils.BoldHighlight)} {"i".Color(Utils.RedHighlight)} {"10000".Color(Utils.GreenHighlight)}", Color.White); user.SendMessage($"Example usage: {"clear".Color(Utils.BoldHighlight)} {"item".Color(Utils.RedHighlight)} {"10000".Color(Utils.GreenHighlight)}", Color.White);
user.SendMessage($"If you do not specify a radius, it will use a default radius of {radius} around your character.", Color.White);
user.SendMessage($"You can use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)} to execute this command silently.", Color.White);
return;
}
int radius = 50;
if (args.Parameters.Count == 2)
{
if (!int.TryParse(args.Parameters[1], out radius) || radius <= 0)
{
args.Player.SendErrorMessage("Invalid radius.");
user.SendErrorMessage($"\"{args.Parameters[1]}\" is not a valid radius.");
return;
}
}
@ -5578,100 +5682,121 @@ namespace TShockAPI
{
case "item":
case "items":
case "i":
{
int cleared = 0;
for (int i = 0; i < Main.maxItems; i++)
{
float dX = Main.item[i].position.X - args.Player.X;
float dY = Main.item[i].position.Y - args.Player.Y;
float dX = Main.item[i].position.X - user.X;
float dY = Main.item[i].position.Y - user.Y;
if (Main.item[i].active && dX * dX + dY * dY <= radius * radius * 256f)
{
Main.item[i].active = false;
TSPlayer.All.SendData(PacketTypes.ItemDrop, "", i);
everyone.SendData(PacketTypes.ItemDrop, "", i);
cleared++;
}
}
args.Player.SendSuccessMessage("Deleted {0} items within a radius of {1}.", cleared, radius);
if (args.Silent)
user.SendSuccessMessage($"You deleted {cleared} item{(cleared > 1 ? "s": "")} within a radius of {radius}.");
else
everyone.SendInfoMessage($"{user.Name} deleted {cleared} item{(cleared > 1 ? "s" : "")} within a radius of {radius}.");
}
break;
case "npc":
case "npcs":
case "n":
{
int cleared = 0;
for (int i = 0; i < Main.maxNPCs; i++)
{
float dX = Main.npc[i].position.X - args.Player.X;
float dY = Main.npc[i].position.Y - args.Player.Y;
float dX = Main.npc[i].position.X - user.X;
float dY = Main.npc[i].position.Y - user.Y;
if (Main.npc[i].active && dX * dX + dY * dY <= radius * radius * 256f)
{
Main.npc[i].active = false;
Main.npc[i].type = 0;
TSPlayer.All.SendData(PacketTypes.NpcUpdate, "", i);
everyone.SendData(PacketTypes.NpcUpdate, "", i);
cleared++;
}
}
args.Player.SendSuccessMessage("Deleted {0} NPCs within a radius of {1}.", cleared, radius);
if (args.Silent)
user.SendSuccessMessage($"You deleted {cleared} NPC{(cleared > 1 ? "s" : "")} within a radius of {radius}.");
else
everyone.SendInfoMessage($"{user.Name} deleted {cleared} NPC{(cleared > 1 ? "s" : "")} within a radius of {radius}.");
}
break;
case "proj":
case "projectile":
case "projectiles":
case "p":
{
int cleared = 0;
for (int i = 0; i < Main.maxProjectiles; i++)
{
float dX = Main.projectile[i].position.X - args.Player.X;
float dY = Main.projectile[i].position.Y - args.Player.Y;
float dX = Main.projectile[i].position.X - user.X;
float dY = Main.projectile[i].position.Y - user.Y;
if (Main.projectile[i].active && dX * dX + dY * dY <= radius * radius * 256f)
{
Main.projectile[i].active = false;
Main.projectile[i].type = 0;
TSPlayer.All.SendData(PacketTypes.ProjectileNew, "", i);
everyone.SendData(PacketTypes.ProjectileNew, "", i);
cleared++;
}
}
args.Player.SendSuccessMessage("Deleted {0} projectiles within a radius of {1}.", cleared, radius);
if (args.Silent)
user.SendSuccessMessage($"You deleted {cleared} projectile{(cleared > 1 ? "s" : "")} within a radius of {radius}.");
else
everyone.SendInfoMessage($"{user.Name} deleted {cleared} projectile{(cleared > 1 ? "s" : "")} within a radius of {radius}");
}
break;
default:
args.Player.SendErrorMessage("Invalid clear option!");
user.SendErrorMessage($"\"{args.Parameters[0]}\" is not a valid clear option.");
break;
}
}
private static void Kill(CommandArgs args)
{
// To-Do: separate kill self and kill other player into two permissions
var user = args.Player;
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}kill <player>", Specifier);
user.SendMessage("Kill syntax and example", Color.White);
user.SendMessage($"{"kill".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}>", Color.White);
user.SendMessage($"Example usage: {"kill".Color(Utils.BoldHighlight)} {user.Name.Color(Utils.RedHighlight)}", Color.White);
user.SendMessage($"You can use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)} to execute this command silently.", Color.White);
return;
}
string plStr = String.Join(" ", args.Parameters);
var players = TSPlayer.FindByNameOrID(plStr);
string targetName = String.Join(" ", args.Parameters);
var players = TSPlayer.FindByNameOrID(targetName);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
user.SendErrorMessage($"Could not find any player named \"{targetName}\".");
else if (players.Count > 1)
{
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
user.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var plr = players[0];
plr.KillPlayer();
args.Player.SendSuccessMessage(string.Format("You just killed {0}!", plr.Name));
plr.SendErrorMessage("{0} just killed you!", args.Player.Name);
var target = players[0];
if (target.Dead)
{
user.SendErrorMessage($"{(target == user ? "You" : target.Name)} {(target == user ? "are" : "is")} already dead!");
return;
}
target.KillPlayer();
user.SendSuccessMessage($"You just killed {(target == user ? "yourself" : target.Name)}!");
if (!args.Silent && target != user)
target.SendErrorMessage($"{user.Name} just killed you!");
}
}
private static void Respawn(CommandArgs args)
{
if (!args.Player.RealPlayer)
if (!args.Player.RealPlayer && args.Parameters.Count == 0)
{
args.Player.SendErrorMessage("You can't respawn the server console!");
return;
@ -5720,9 +5845,15 @@ namespace TShockAPI
private static void Butcher(CommandArgs args)
{
var user = args.Player;
if (args.Parameters.Count > 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}butcher [mob type]", Specifier);
user.SendMessage("Butcher Syntax and Example", Color.White);
user.SendMessage($"{"butcher".Color(Utils.BoldHighlight)} [{"NPC name".Color(Utils.RedHighlight)}|{"ID".Color(Utils.RedHighlight)}]", Color.White);
user.SendMessage($"Example usage: {"butcher".Color(Utils.BoldHighlight)} {"pigron".Color(Utils.RedHighlight)}", Color.White);
user.SendMessage("All alive NPCs (excluding town NPCs) on the server will be killed if you do not input a name or ID.", Color.White);
user.SendMessage($"To get rid of NPCs without making them drop items, use the {"clear".Color(Utils.BoldHighlight)} command instead.", Color.White);
user.SendMessage($"To execute this command silently, use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)}", Color.White);
return;
}
@ -5733,19 +5864,17 @@ namespace TShockAPI
var npcs = TShock.Utils.GetNPCByIdOrName(args.Parameters[0]);
if (npcs.Count == 0)
{
args.Player.SendErrorMessage("Invalid mob type!");
user.SendErrorMessage($"\"{args.Parameters[0]}\" is not a valid NPC.");
return;
}
else if (npcs.Count > 1)
if (npcs.Count > 1)
{
args.Player.SendMultipleMatchError(npcs.Select(n => $"{n.FullName}({n.type})"));
user.SendMultipleMatchError(npcs.Select(n => $"{n.FullName}({n.type})"));
return;
}
else
{
npcId = npcs[0].netID;
}
}
int kills = 0;
for (int i = 0; i < Main.npc.Length; i++)
@ -5756,7 +5885,11 @@ namespace TShockAPI
kills++;
}
}
TSPlayer.All.SendInfoMessage("{0} butchered {1} NPCs.", args.Player.Name, kills);
if (args.Silent)
user.SendSuccessMessage($"You butchered {kills} NPC{(kills > 1 ? "s": "")}.");
else
TSPlayer.All.SendInfoMessage($"{user.Name} butchered {kills} NPC{(kills > 1 ? "s" : "")}.");
}
private static void Item(CommandArgs args)
@ -6007,93 +6140,114 @@ namespace TShockAPI
private static void Heal(CommandArgs args)
{
TSPlayer playerToHeal;
if (args.Parameters.Count > 0)
// heal <player> [amount]
// To-Do: break up heal self and heal other into two separate permissions
var user = args.Player;
if (args.Parameters.Count < 1 || args.Parameters.Count > 2)
{
string plStr = String.Join(" ", args.Parameters);
var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
user.SendMessage("Heal Syntax and Example", Color.White);
user.SendMessage($"{"heal".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}> [{"amount".Color(Utils.GreenHighlight)}]", Color.White);
user.SendMessage($"Example usage: {"heal".Color(Utils.BoldHighlight)} {user.Name.Color(Utils.RedHighlight)} {"100".Color(Utils.GreenHighlight)}", Color.White);
user.SendMessage($"If no amount is specified, it will default to healing the target player by their max HP.", Color.White);
user.SendMessage($"To execute this command silently, use {SilentSpecifier.Color(Utils.GreenHighlight)} instead of {Specifier.Color(Utils.RedHighlight)}", Color.White);
return;
}
else if (players.Count > 1)
if (args.Parameters[0].Length == 0)
{
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
user.SendErrorMessage($"You didn't put a player name.");
return;
}
else
{
playerToHeal = players[0];
}
}
else if (!args.Player.RealPlayer)
{
args.Player.SendErrorMessage("You can't heal yourself!");
return;
}
else
{
playerToHeal = args.Player;
}
playerToHeal.Heal();
if (playerToHeal == args.Player)
{
args.Player.SendSuccessMessage("You just got healed!");
}
string targetName = args.Parameters[0];
var players = TSPlayer.FindByNameOrID(targetName);
if (players.Count == 0)
user.SendErrorMessage($"Unable to find any players named \"{targetName}\"");
else if (players.Count > 1)
user.SendMultipleMatchError(players.Select(p => p.Name));
else
{
args.Player.SendSuccessMessage(string.Format("You just healed {0}", playerToHeal.Name));
playerToHeal.SendSuccessMessage(string.Format("{0} just healed you!", args.Player.Name));
var target = players[0];
int amount = target.TPlayer.statLifeMax2;
if (target.Dead)
{
user.SendErrorMessage("You can't heal a dead player!");
return;
}
if (args.Parameters.Count == 2)
{
int.TryParse(args.Parameters[1], out amount);
}
target.Heal(amount);
if (args.Silent)
user.SendSuccessMessage($"You healed {(target == user ? "yourself" : target.Name)} for {amount} HP.");
else
TSPlayer.All.SendInfoMessage($"{user.Name} healed {(target == user ? (target.TPlayer.Male ? "himself" : "herself") : target.Name)} for {amount} HP.");
}
}
private static void Buff(CommandArgs args)
{
// buff <"buff name|ID"> [duration]
var user = args.Player;
if (args.Parameters.Count < 1 || args.Parameters.Count > 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}buff <buff name or ID> [time in seconds]", Specifier);
user.SendMessage("Buff Syntax and Example", Color.White);
user.SendMessage($"{"buff".Color(Utils.BoldHighlight)} <\"{"buff name".Color(Utils.RedHighlight)}|{"ID".Color(Utils.RedHighlight)}\"> [{"duration".Color(Utils.GreenHighlight)}]", Color.White);
user.SendMessage($"Example usage: {"buff".Color(Utils.BoldHighlight)} \"{"obsidian skin".Color(Utils.RedHighlight)}\" {"-1".Color(Utils.GreenHighlight)}", Color.White);
user.SendMessage($"If you don't specify the duration, it will default to {"60".Color(Utils.GreenHighlight)} seconds.", Color.White);
user.SendMessage($"If you put {"-1".Color(Utils.GreenHighlight)} as the duration, it will use the max possible time of 415 days.", Color.White);
return;
}
int id = 0;
int time = 60;
var timeLimit = (int.MaxValue / 60) - 1;
if (!int.TryParse(args.Parameters[0], out id))
{
var found = TShock.Utils.GetBuffByName(args.Parameters[0]);
if (found.Count == 0)
{
args.Player.SendErrorMessage("Invalid buff name!");
user.SendErrorMessage($"Unable to find any buffs named \"{args.Parameters[0]}\"");
return;
}
else if (found.Count > 1)
if (found.Count > 1)
{
args.Player.SendMultipleMatchError(found.Select(f => Lang.GetBuffName(f)));
user.SendMultipleMatchError(found.Select(f => Lang.GetBuffName(f)));
return;
}
id = found[0];
}
if (args.Parameters.Count == 2)
int.TryParse(args.Parameters[1], out time);
if (id > 0 && id < Main.maxBuffTypes)
{
// Max possible buff duration as of 1.4.2.2 is 35791393 seconds (415 days).
// Max possible buff duration as of Terraria 1.4.2.3 is 35791393 seconds (415 days).
if (time < 0 || time > timeLimit)
time = timeLimit;
args.Player.SetBuff(id, time * 60);
args.Player.SendSuccessMessage(string.Format("You have buffed yourself with {0} ({1}) for {2} seconds!",
TShock.Utils.GetBuffName(id), TShock.Utils.GetBuffDescription(id), (time)));
user.SetBuff(id, time * 60);
user.SendSuccessMessage($"You buffed yourself with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds.");
}
else
args.Player.SendErrorMessage("Invalid buff ID!");
user.SendErrorMessage($"\"{id}\" is not a valid buff ID!");
}
private static void GBuff(CommandArgs args)
{
var user = args.Player;
if (args.Parameters.Count < 2 || args.Parameters.Count > 3)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}gbuff <player> <buff name or ID> [time in seconds]", Specifier);
user.SendMessage("Give Buff Syntax and Example", Color.White);
user.SendMessage($"{"gbuff".Color(Utils.BoldHighlight)} <{"player".Color(Utils.RedHighlight)}> <{"buff name".Color(Utils.PinkHighlight)}|{"ID".Color(Utils.PinkHighlight)}> [{"seconds".Color(Utils.GreenHighlight)}]", Color.White);
user.SendMessage($"Example usage: {"gbuff".Color(Utils.BoldHighlight)} {user.Name.Color(Utils.RedHighlight)} {"regen".Color(Utils.PinkHighlight)} {"-1".Color(Utils.GreenHighlight)}", Color.White);
user.SendMessage($"To buff a player without them knowing, use {SilentSpecifier.Color(Utils.RedHighlight)} instead of {Specifier.Color(Utils.GreenHighlight)}", Color.White);
return;
}
int id = 0;
@ -6102,12 +6256,12 @@ namespace TShockAPI
var foundplr = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (foundplr.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
user.SendErrorMessage($"Unable to find any player named \"{args.Parameters[0]}\"");
return;
}
else if (foundplr.Count > 1)
{
args.Player.SendMultipleMatchError(foundplr.Select(p => p.Name));
user.SendMultipleMatchError(foundplr.Select(p => p.Name));
return;
}
else
@ -6117,12 +6271,12 @@ namespace TShockAPI
var found = TShock.Utils.GetBuffByName(args.Parameters[1]);
if (found.Count == 0)
{
args.Player.SendErrorMessage("Invalid buff name!");
user.SendErrorMessage($"Unable to find any buff named \"{args.Parameters[1]}\"");
return;
}
else if (found.Count > 1)
{
args.Player.SendMultipleMatchError(found.Select(b => Lang.GetBuffName(b)));
user.SendMultipleMatchError(found.Select(b => Lang.GetBuffName(b)));
return;
}
id = found[0];
@ -6131,18 +6285,16 @@ namespace TShockAPI
int.TryParse(args.Parameters[2], out time);
if (id > 0 && id < Main.maxBuffTypes)
{
var target = foundplr[0];
if (time < 0 || time > timeLimit)
time = timeLimit;
foundplr[0].SetBuff(id, time * 60);
args.Player.SendSuccessMessage(string.Format("You have buffed {0} with {1} ({2}) for {3} seconds!",
foundplr[0].Name, TShock.Utils.GetBuffName(id),
TShock.Utils.GetBuffDescription(id), (time)));
foundplr[0].SendSuccessMessage(string.Format("{0} has buffed you with {1} ({2}) for {3} seconds!",
args.Player.Name, TShock.Utils.GetBuffName(id),
TShock.Utils.GetBuffDescription(id), (time)));
target.SetBuff(id, time * 60);
user.SendSuccessMessage($"You have buffed {(target == user ? "yourself" : target.Name)} with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!");
if (!args.Silent && target != user)
target.SendSuccessMessage($"{user.Name} has buffed you with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!");
}
else
args.Player.SendErrorMessage("Invalid buff ID!");
user.SendErrorMessage("Invalid buff ID!");
}
}
@ -6553,7 +6705,7 @@ namespace TShockAPI
if (!args.Silent || (playerToGod == args.Player))
{
playerToGod.SendSuccessMessage(string.Format("You are {0} in god mode.", args.Player.GodMode ? "now" : "no longer"));
playerToGod.SendSuccessMessage(string.Format("You are {0} in god mode.", playerToGod.GodMode ? "now" : "no longer"));
}
}

View file

@ -251,13 +251,13 @@ namespace TShockAPI.Configuration
[Description("Allows groups on the banned item allowed list to spawn banned items even if PreventBannedItemSpawn is set to true.")]
public bool AllowAllowedGroupsToSpawnBannedItems = false;
/// <summary>The number of seconds a player must wait before being respawned. Cannot be longer than normal value now. Use at your own risk.</summary>
[Description("The number of seconds a player must wait before being respawned. Cannot be longer than normal value now. Use at your own risk.")]
public int RespawnSeconds = 10;
/// <summary>The number of seconds a player must wait before being respawned. Valid range: 0 (default) to 15 seconds. Use at your own risk.</summary>
[Description("The number of seconds a player must wait before being respawned. Valid range: 0 (default) to 15 seconds. Use at your own risk.")]
public int RespawnSeconds = 0;
/// <summary>The number of seconds a player must wait before being respawned if there is a boss nearby. Cannot be longer than normal value now. Use at your own risk.</summary>
[Description("The number of seconds a player must wait before being respawned if there is a boss nearby. Cannot be longer than normal value now. Use at your own risk.")]
public int RespawnBossSeconds = 10;
/// <summary>The number of seconds a player must wait before being respawned if there is a boss nearby. Valid range: 0 (default) to 30 seconds. Use at your own risk.</summary>
[Description("The number of seconds a player must wait before being respawned if there is a boss nearby. Valid range: 0 (default) to 30 seconds. Use at your own risk.")]
public int RespawnBossSeconds = 0;
/// <summary>Whether or not to announce boss spawning or invasion starts.</summary>
[Description("Whether or not to announce boss spawning or invasion starts.")]
@ -312,6 +312,10 @@ namespace TShockAPI.Configuration
[Description("The reason given if banning a mediumcore player on death.")]
public string MediumcoreBanReason = "Death results in a ban";
/// <summary>Disbales IP bans by default, if no arguments are passed to the ban command.</summary>
[Description("Disbales IP bans by default, if no arguments are passed to the ban command.")]
public bool DisableDefaultIPBan;
/// <summary>Enable or disable the whitelist based on IP addresses in the whitelist.txt file.</summary>
[Description("Enable or disable the whitelist based on IP addresses in the whitelist.txt file.")]
public bool EnableWhitelist;
@ -364,11 +368,6 @@ namespace TShockAPI.Configuration
[Description("The minimum password length for new user accounts. Can never be lower than 4.")]
public int MinimumPasswordLength = 4;
/// <summary>The hash algorithm used to encrypt user passwords.
/// Valid types: "sha512", "sha256" and "md5". Append with "-xp" for the xp supported algorithms.</summary>
[Description("The hash algorithm used to encrypt user passwords. Valid types: \"sha512\", \"sha256\" and \"md5\". Append with \"-xp\" for the xp supported algorithms.")]
public string HashAlgorithm = "sha512";
/// <summary>Determines the BCrypt work factor to use. If increased, all passwords will be upgraded to new work-factor on verify.
/// The number of computational rounds is 2^n. Increase with caution. Range: 5-31.</summary>
[Description("Determines the BCrypt work factor to use. If increased, all passwords will be upgraded to new work-factor on verify. The number of computational rounds is 2^n. Increase with caution. Range: 5-31.")]

View file

@ -1,224 +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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using TShockAPI.DB;
namespace TShockAPI.DB
{
public class DBTools
{
public static IDbConnection database;
public static void CreateTable(string name, bool IfNotExists, List<Column> columns)
{
//Build up Creation string :)
StringBuilder sb = new StringBuilder();
sb.Append("CREATE TABLE ");
if (IfNotExists)
sb.Append("IF NOT EXISTS ");
if (TShock.Config.StorageType.ToLower() == "sqlite")
sb.Append("'" + name + "' (");
else if (TShock.Config.StorageType.ToLower() == "mysql")
sb.Append(name + " (");
int count = 0;
foreach (Column column in columns)
{
count++;
if (column.Type.ToLower() == "int")
{
if (TShock.Config.StorageType.ToLower() == "sqlite")
sb.Append(column.Name + " NUMERIC ");
else if (TShock.Config.StorageType.ToLower() == "mysql")
sb.Append(column.Name + " INT(255) ");
}
else if (column.Type.ToLower() == "string")
{
if (TShock.Config.StorageType.ToLower() == "sqlite")
sb.Append(column.Name + " TEXT ");
else if (TShock.Config.StorageType.ToLower() == "mysql")
sb.Append(column.Name + " VARCHAR(255) ");
}
if (column.Unique)
{
if (columns.Count != count)
{
sb.Append("UNIQUE, ");
}
else
sb.Append("UNIQUE) ");
}
if (columns.Count == count)
sb.Append(")");
}
using (var com = database.CreateCommand())
{
com.CommandText = sb.ToString();
com.ExecuteNonQuery();
}
}
public static void InsertTable(string tablename, bool Ignore, List<ColumnData> Values, List<ColumnData> WhereAndStatements)
{
StringBuilder sb = new StringBuilder();
sb.Append("INSERT ");
if (Ignore)
{
if (TShock.Config.StorageType.ToLower() == "sqlite")
sb.Append("OR IGNORE ");
else if (TShock.Config.StorageType.ToLower() == "mysql")
sb.Append("IGNORE ");
}
if (TShock.Config.StorageType.ToLower() == "sqlite")
sb.Append("INTO '" + tablename + "' (");
else if (TShock.Config.StorageType.ToLower() == "mysql")
sb.Append("INTO " + tablename + " ");
using (var com = database.CreateCommand())
{
//Values
if (TShock.Config.StorageType.ToLower() == "sqlite")
{
int count = 0;
foreach (ColumnData columnname in Values)
{
count++;
if (Values.Count != count)
sb.Append(columnname.Name + ", ");
else
sb.Append(columnname.Name + ") ");
}
sb.Append("VALUES (");
count = 0;
foreach (ColumnData columnname in Values)
{
count++;
if (Values.Count != count)
{
sb.Append("@" + columnname.Name + ", ");
com.AddParameter("@" + columnname.Name.ToLower(), columnname.Value);
}
else
{
sb.Append("@" + columnname.Name + ") ");
com.AddParameter("@" + columnname.Name.ToLower(), columnname.Value);
}
}
}
else if (TShock.Config.StorageType.ToLower() == "mysql")
{
sb.Append("SET ");
int count = 0;
foreach (ColumnData columnname in Values)
{
count++;
if (Values.Count != count)
{
sb.Append("@" + columnname.Name + "=" + columnname.Value + ", ");
com.AddParameter("@" + columnname.Name.ToLower(), columnname.Value);
}
else
{
sb.Append("@" + columnname.Name + "=" + columnname.Value + ") ");
com.AddParameter("@" + columnname.Name.ToLower(), columnname.Value);
}
}
}
//Where Statement (if any)
if (WhereAndStatements.Count > 0)
{
sb.Append("WHERE ");
int count = 0;
foreach (ColumnData columnname in WhereAndStatements)
{
count++;
if (Values.Count != count)
{
sb.Append("@" + columnname.Name + "=" + columnname.Value + "-where" + " AND ");
com.AddParameter("@" + columnname.Name.ToLower() + "-where", columnname.Value);
}
else
{
sb.Append("@" + columnname.Name + "=" + columnname.Value + "-where" + ";");
com.AddParameter("@" + columnname.Name.ToLower() + "-where", columnname.Value);
}
}
}
}
}
}
public class Column
{
public string Name { get; set; }
public string Type { get; set; }
public bool Unique { get; set; }
public string Parameters { get; set; }
public Column(string name, bool unique, string type, string parameters)
{
Name = name;
Type = type;
Unique = unique;
Parameters = parameters;
}
public Column()
{
Name = string.Empty;
Type = string.Empty;
Unique = false;
Parameters = string.Empty;
}
}
public class ColumnData
{
public string Name { get; set; }
public object Value { get; set; }
public ColumnData(string name, object value)
{
Name = name;
Value = value;
}
public ColumnData()
{
Name = string.Empty;
Value = string.Empty;
}
}
}

View file

@ -200,6 +200,45 @@ namespace TShockAPI.DB
LoadPermisions();
Group.DefaultGroup = GetGroupByName(TShock.Config.Settings.DefaultGuestGroupName);
AssertCoreGroupsPresent();
}
internal void AssertCoreGroupsPresent()
{
if (!GroupExists(TShock.Config.Settings.DefaultGuestGroupName))
{
TShock.Log.ConsoleError("The guest group could not be found. This may indicate a typo in the configuration file, or that the group was renamed or deleted.");
throw new Exception("The guest group could not be found.");
}
if (!GroupExists(TShock.Config.Settings.DefaultRegistrationGroupName))
{
TShock.Log.ConsoleError("The default usergroup could not be found. This may indicate a typo in the configuration file, or that the group was renamed or deleted.");
throw new Exception("The default usergroup could not be found.");
}
}
/// <summary>
/// Asserts that the group reference can be safely assigned to the player object.
/// <para>If this assertion fails, and <paramref name="kick"/> is true, the player is disconnected. If <paramref name="kick"/> is false, the player will receive an error message.</para>
/// </summary>
/// <param name="player">The player in question</param>
/// <param name="group">The group we want to assign them</param>
/// <param name="kick">Whether or not failing this check disconnects the player.</param>
/// <returns></returns>
public bool AssertGroupValid(TSPlayer player, Group group, bool kick)
{
if (group == null)
{
if (kick)
player.Disconnect("Your account's group could not be loaded. Please contact server administrators about this.");
else
player.SendErrorMessage("Your account's group could not be loaded. Please contact server administrators about this.");
return false;
}
return true;
}
private void AddDefaultGroup(string name, string parent, string permissions)

View file

@ -447,39 +447,12 @@ namespace TShockAPI.DB
}
catch (SaltParseException)
{
if (String.Equals(HashPassword(password), Password, StringComparison.InvariantCultureIgnoreCase))
{
// Return true to keep blank passwords working but don't convert them to bcrypt.
if (Password == "non-existant password") {
return true;
}
// The password is not stored using BCrypt; upgrade it to BCrypt immediately
UpgradePasswordToBCrypt(password);
return true;
}
TShock.Log.ConsoleError("Error: Unable to verify the password hash for user {0} ({1})", Name, ID);
return false;
}
return false;
}
/// <summary>Upgrades a password to BCrypt, from an insecure hashing algorithm.</summary>
/// <param name="password">The raw user account password (unhashed) to upgrade</param>
protected void UpgradePasswordToBCrypt(string password)
{
// Save the old password, in the event that we have to revert changes.
string oldpassword = Password;
try
{
TShock.UserAccounts.SetUserAccountPassword(this, password);
}
catch (UserAccountManagerException e)
{
TShock.Log.ConsoleError(e.ToString());
Password = oldpassword; // Revert changes
}
}
/// <summary>Upgrades a password to the highest work factor available in the config.</summary>
/// <param name="password">The raw user account password (unhashed) to upgrade</param>
protected void UpgradePasswordWorkFactor(string password)
@ -540,51 +513,6 @@ namespace TShockAPI.DB
Password = BCrypt.Net.BCrypt.HashPassword(password.Trim(), workFactor);
}
/// <summary>
/// A dictionary of hashing algorithms and an implementation object.
/// </summary>
protected readonly Dictionary<string, Func<HashAlgorithm>> HashTypes = new Dictionary<string, Func<HashAlgorithm>>
{
{"sha512", () => new SHA512Managed()},
{"sha256", () => new SHA256Managed()},
{"md5", () => new MD5Cng()},
{"sha512-xp", () => SHA512.Create()},
{"sha256-xp", () => SHA256.Create()},
{"md5-xp", () => MD5.Create()},
};
/// <summary>
/// Returns a hashed string for a given string based on the config file's hash algo
/// </summary>
/// <param name="bytes">bytes to hash</param>
/// <returns>string hash</returns>
protected string HashPassword(byte[] bytes)
{
if (bytes == null)
throw new NullReferenceException("bytes");
Func<HashAlgorithm> func;
if (!HashTypes.TryGetValue(TShock.Config.Settings.HashAlgorithm.ToLower(), out func))
throw new NotSupportedException("Hashing algorithm {0} is not supported".SFormat(TShock.Config.Settings.HashAlgorithm.ToLower()));
using (var hash = func())
{
var ret = hash.ComputeHash(bytes);
return ret.Aggregate("", (s, b) => s + b.ToString("X2"));
}
}
/// <summary>
/// Returns a hashed string for a given string based on the config file's hash algo
/// </summary>
/// <param name="password">string to hash</param>
/// <returns>string hash</returns>
protected string HashPassword(string password)
{
if (string.IsNullOrEmpty(password) && Password == "non-existant password")
return "non-existant password";
return HashPassword(Encoding.UTF8.GetBytes(password));
}
#region IEquatable
/// <summary>Indicates whether the current <see cref="UserAccount"/> is equal to another <see cref="UserAccount"/>.</summary>

View file

@ -1935,6 +1935,7 @@ namespace TShockAPI
return args.Handled;
}
/// <summary>
/// For use in an Emoji event.
/// </summary>
public class EmojiEventArgs : GetDataHandledEventArgs
@ -1968,6 +1969,7 @@ namespace TShockAPI
return args.Handled;
}
/// <summary>
/// For use in a TileEntityDisplayDollItemSync event.
/// </summary>
public class DisplayDollItemSyncEventArgs : GetDataHandledEventArgs
@ -2026,6 +2028,7 @@ namespace TShockAPI
return args.Handled;
}
/// <summary>
/// For use in an OnRequestTileEntityInteraction event.
/// </summary>
public class RequestTileEntityInteractionEventArgs : GetDataHandledEventArgs
@ -2456,10 +2459,13 @@ namespace TShockAPI
args.Player.State = 2;
NetMessage.SendData((int)PacketTypes.WorldInfo, args.Player.Index);
args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
var group = TShock.Groups.GetGroupByName(account.Group);
if (!TShock.Groups.AssertGroupValid(args.Player, group, true))
return true;
args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
args.Player.Group = group;
args.Player.tempGroup = null;
args.Player.Account = account;
@ -3037,6 +3043,9 @@ namespace TShockAPI
var group = TShock.Groups.GetGroupByName(account.Group);
if (!TShock.Groups.AssertGroupValid(args.Player, group, true))
return true;
args.Player.Group = group;
args.Player.tempGroup = null;
args.Player.Account = account;
@ -3240,6 +3249,7 @@ namespace TShockAPI
{
TShock.Log.ConsoleDebug($"GetDataHandlers / HandleSpecial rejected enchanted sundial permission {args.Player.Name}");
args.Player.SendErrorMessage("You do not have permission to use the Enchanted Sundial.");
return true;
}
else if (TShock.Config.Settings.ForceTime != "normal")
{
@ -3250,9 +3260,9 @@ namespace TShockAPI
}
else
args.Player.SendErrorMessage("You must set ForceTime to normal via config to use the Enchanted Sundial.");
}
return true;
}
}
return false;
}

View file

@ -53,5 +53,5 @@ using System.Runtime.InteropServices;
// Also, be sure to release on github with the exact assembly version tag as below
// so that the update manager works correctly (via the Github releases api and mimic)
[assembly: AssemblyVersion("4.5.5")]
[assembly: AssemblyFileVersion("4.5.5")]
[assembly: AssemblyVersion("4.5.7")]
[assembly: AssemblyFileVersion("4.5.7")]

View file

@ -1715,7 +1715,6 @@ namespace TShockAPI
var time2 = (int)time;
var launch = DateTime.UtcNow;
var startname = Name;
SendInfoMessage("You are now being annoyed.");
while ((DateTime.UtcNow - launch).TotalSeconds < time2 && startname == Name)
{
SendData(PacketTypes.NpcSpecial, number: Index, number2: 2f);

View file

@ -58,7 +58,7 @@ namespace TShockAPI
/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
public static readonly string VersionCodename = "Olympics maybe?";
public static readonly string VersionCodename = "Herrscher of Logic";
/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
public static string SavePath = "tshock";
@ -79,7 +79,7 @@ namespace TShockAPI
/// <summary>Players - Contains all TSPlayer objects for accessing TSPlayers currently on the server</summary>
public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers];
/// <summary>Bans - Static reference to the ban manager for accessing bans & related functions.</summary>
/// <summary>Bans - Static reference to the ban manager for accessing bans &amp; related functions.</summary>
public static BanManager Bans;
/// <summary>Warps - Static reference to the warp manager for accessing the warp system.</summary>
public static WarpManager Warps;
@ -148,7 +148,7 @@ namespace TShockAPI
/// </summary>
public static event Action Initialized;
/// <summary>Version - The version required by the TerrariaAPI to be passed back for checking & loading the plugin.</summary>
/// <summary>Version - The version required by the TerrariaAPI to be passed back for checking &amp; loading the plugin.</summary>
/// <value>value - The version number specified in the Assembly, based on the VersionNum variable set in this class.</value>
public override Version Version
{
@ -386,8 +386,8 @@ namespace TShockAPI
}
catch (Exception ex)
{
Log.Error("Fatal Startup Exception");
Log.Error(ex.ToString());
Log.ConsoleError("Fatal Startup Exception");
Log.ConsoleError(ex.ToString());
Environment.Exit(1);
}
}

View file

@ -62,6 +62,10 @@ namespace TShockAPI
/// Hex code for a white highlight
/// </summary>
public const string WhiteHighlight = "FFFFFF";
/// <summary>
/// Hex code for a cyan pastel color
/// </summary>
public const string CyanHighlight = "AAFFFF";
/// <summary>
/// The lowest id for a prefix.
@ -890,7 +894,7 @@ namespace TShockAPI
Main.recipe[i] = new Recipe();
}
/// <summary>Dumps a matrix of all permissions & all groups in Markdown table format.</summary>
/// <summary>Dumps a matrix of all permissions &amp; all groups in Markdown table format.</summary>
/// <param name="path">The save destination.</param>
internal void DumpPermissionMatrix(string path)
{

@ -1 +1 @@
Subproject commit 8c2c087327bbd1f20ff6c46f4d11e5714e57064b
Subproject commit b9a0fdf6d464d17c47f70ed9327779a78a0aaee4