diff --git a/.dockerignore b/.dockerignore
index b20ac5b6..234117c0 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,15 +1,20 @@
-# ignore every file
-*
+# Ignore all Git metadata and build output (in all directories).
+**/.git*
+**/bin/
+**/obj/
-# except for the ones required for building
-!i18n/
-!prebuilts/
-!TerrariaServerAPI/
-!TShockAPI/
-!TShockLauncher/
-!TShockLauncher.Tests/
-!TShock.sln
-
-# but exclude build artifacts
-*/bin/
-*/obj/
+# Ignore other specific files that aren't needed for the build itself.
+/.all-contributorsrc
+/.dockerignore
+/.editorconfig
+/.vscode
+/appveyor.yml
+/COPYING
+/crowdin.yml
+/Dockerfile
+/docs
+/README.md
+/README_cn.md
+/renovate.json
+/scripts
+/SECURITY.md
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index 990b9ed1..a6b7aa40 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -1,3 +1,5 @@
> By participating in the TShock for Terraria community, all members will adhere to maintaining decorum with respect to all humans, in and out of the community. Members will not engage in discussion that inappropriately disparages or marginalizes any group of people or any individual. Members will not attempt to further or advance an agenda to the point of being overbearing or close minded (such as through spreading FUD). Members will not abuse services provided to them and will follow the guidance of community leaders on a situational basis about what abuse consists of. Members will adhere to United States and international law. If members notice a violation of this code of conduct, they will not engage but will instead contact the leadership team on either the forums or Discord.
-> Do not attempt to circumvent or bypass the code of conduct by using clever logic or reasoning (e.g., insulting Facepunch members, because they weren't directly mentioned here).
\ No newline at end of file
+> Do not attempt to circumvent or bypass the code of conduct by using clever logic or reasoning (e.g., insulting Facepunch members, because they weren't directly mentioned here).
+
+> Do not abuse GitHub services by generating useless emails to all people who watch the repo. This includes unnecessary PR approvals and off-topic discussion, issue, and PR comments that serve no purpose. Due to the high visibility nature of actions that create emails, you may be blocked temporarily as your first warning.
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 8275735a..75b69d48 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,2 @@
# These are supported funding model platforms
-github: [SignatureBeef, hakusaro, Stealownz, QuiCM]
+github: [SignatureBeef, QuiCM]
diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml
new file mode 100644
index 00000000..3d47d3c6
--- /dev/null
+++ b/.github/workflows/ci-docker.yml
@@ -0,0 +1,60 @@
+name: CI (Docker image)
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ attestations: write
+ id-token: write
+ packages: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Login to ghcr.io
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Generate version information
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ghcr.io/${{ github.repository }}
+ tags: |
+ type=schedule
+ type=ref,event=branch
+ type=ref,event=tag,enable=${{ !startsWith(github.ref, 'refs/tags/v') }}
+ type=ref,event=pr
+ type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
+ type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
+ type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
+ flavor: |
+ latest=${{ startsWith(github.ref, 'refs/tags/v') }}
+ - name: Build image
+ id: build
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64,linux/arm/v7,windows/amd64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ pull: true
+ cache-from: type=gha, scope=${{ github.workflow }}
+ cache-to: type=gha, scope=${{ github.workflow }}
+ - name: Generate build provenance attestation
+ if: ${{ github.event_name != 'pull_request' }}
+ uses: actions/attest-build-provenance@v2
+ with:
+ subject-name: ghcr.io/${{ github.repository }}
+ subject-digest: ${{ steps.build.outputs.digest }}
+ push-to-registry: true
diff --git a/.github/workflows/ci-otapi3.yml b/.github/workflows/ci-otapi3.yml
index f5599ffc..e72d8c22 100644
--- a/.github/workflows/ci-otapi3.yml
+++ b/.github/workflows/ci-otapi3.yml
@@ -63,14 +63,14 @@ jobs:
tar -cvf ../../../../../../TShock-Beta-${{ matrix.arch }}-Release.tar *
- name: Upload artifact (non-Windows)
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: ${{ matrix.arch != 'win-x64' }}
with:
name: TShock-Beta-${{ matrix.arch }}-Release
path: TShock-Beta-${{ matrix.arch }}-Release.tar
- name: Upload artifact (Windows)
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: ${{ matrix.arch == 'win-x64' }}
with:
name: TShock-Beta-${{ matrix.arch }}-Release
diff --git a/Dockerfile b/Dockerfile
index 186bee70..87024fb9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,8 @@
-ARG TARGETPLATFORM=linux/amd64
-ARG BUILDPLATFORM=${TARGETPLATFORM}
+# TARGETPLATFORM and BUILDPLATFORM are automatically filled in by Docker buildx.
+# They should not be set in the global scope manually.
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:6.0 AS builder
-ARG TARGETPLATFORM
-
# Copy build context
WORKDIR /TShock
COPY . ./
@@ -12,6 +10,10 @@ COPY . ./
# Build and package release based on target architecture
RUN dotnet build -v m
WORKDIR /TShock/TShockLauncher
+
+# Make TARGETPLATFORM available to the container.
+ARG TARGETPLATFORM
+
RUN \
case "${TARGETPLATFORM}" in \
"linux/amd64") export ARCH="linux-x64" \
diff --git a/README.md b/README.md
index b5559b37..0369df53 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
- 
+ 
diff --git a/README_cn.md b/README_cn.md
index e038817e..65c5eba6 100644
--- a/README_cn.md
+++ b/README_cn.md
@@ -1,5 +1,5 @@
- 
+ 
diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index affa1609..0f5f6e3c 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -424,7 +424,7 @@ namespace TShockAPI
};
PlayerAddBuffWhitelist[BuffID.BrainOfConfusionBuff] = new BuffLimit
{
- MaxTicks = 240,
+ MaxTicks = 60 * 4,
CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true
};
@@ -434,6 +434,12 @@ namespace TShockAPI
CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true
};
+ PlayerAddBuffWhitelist[BuffID.ParryDamageBuff] = new BuffLimit
+ {
+ MaxTicks = 60 * 5,
+ CanBeAddedWithoutHostile = true,
+ CanOnlyBeAppliedToSender = true
+ };
#endregion Whitelist
}
@@ -1878,7 +1884,7 @@ namespace TShockAPI
return;
}
- if (TShock.Players[id] == null)
+ if (TShock.Players[id] == null || !TShock.Players[id].Active)
{
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: target is null", args.Player.Name,
@@ -2081,7 +2087,7 @@ namespace TShockAPI
short amount = args.Amount;
byte plr = args.TargetPlayerIndex;
- if (amount <= 0 || Main.player[plr] == null || !Main.player[plr].active)
+ if (amount <= 0 || TShock.Players[plr] == null || !TShock.Players[plr].Active)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnHealOtherPlayer rejected null checks"));
args.Handled = true;
@@ -2589,7 +2595,7 @@ namespace TShockAPI
byte direction = args.Direction;
PlayerDeathReason reason = args.PlayerDeathReason;
- if (id >= Main.maxPlayers || TShock.Players[id] == null)
+ if (id >= Main.maxPlayers || TShock.Players[id] == null || !TShock.Players[id].Active)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerDamage rejected null check"));
args.Handled = true;
@@ -2659,9 +2665,12 @@ namespace TShockAPI
* If the player was not specified, that is, the player index is -1, then it is definitely a custom cause, as you can only deal damage with a projectile or another player.
* This is how everything else works. If an NPC is specified, its value is not -1, which is a custom cause.
*
+ * An exception to this is damage dealt by the Inferno potion to other players -- it is only identified by the other index value of 16,
+ * even lacking a source player index.
+ *
* Checking whether this damage came from the player is necessary, because the damage from the player can come even when it is hit by a NPC
*/
- if (TShock.Config.Settings.DisableCustomDeathMessages && id != args.Player.Index &&
+ if (TShock.Config.Settings.DisableCustomDeathMessages && id != args.Player.Index && reason._sourceOtherIndex != 16 &&
(reason._sourcePlayerIndex == -1 || reason._sourceNPCIndex != -1 || reason._sourceOtherIndex != -1 || reason._sourceCustomReason != null))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerDamage rejected custom death message from {0}", args.Player.Name));
@@ -2851,7 +2860,7 @@ namespace TShockAPI
{ BuffID.Poisoned, 3600 }, // BuffID: 20
{ BuffID.OnFire, 1200 }, // BuffID: 24
{ BuffID.Confused, short.MaxValue }, // BuffID: 31 Brain of Confusion Internal Item ID: 3223
- { BuffID.CursedInferno, 420 }, // BuffID: 39
+ { BuffID.CursedInferno, 600 }, // BuffID: 39
{ BuffID.Frostburn, 900 }, // BuffID: 44
{ BuffID.Ichor, 1200 }, // BuffID: 69
{ BuffID.Venom, 1800 }, // BuffID: 70
diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs
index 2214f252..f7d3247a 100644
--- a/TShockAPI/Commands.cs
+++ b/TShockAPI/Commands.cs
@@ -148,24 +148,29 @@ namespace TShockAPI
Permissions = new List();
}
- public bool Run(string msg, bool silent, TSPlayer ply, List parms)
+ public bool Run(CommandArgs args)
{
- if (!CanRun(ply))
+ if (!CanRun(args.Player))
return false;
try
{
- CommandDelegate(new CommandArgs(msg, silent, ply, parms));
+ CommandDelegate(args);
}
catch (Exception e)
{
- ply.SendErrorMessage(GetString("Command failed, check logs for more details."));
+ args.Player.SendErrorMessage(GetString("Command failed, check logs for more details."));
TShock.Log.Error(e.ToString());
}
return true;
}
+ public bool Run(string msg, bool silent, TSPlayer ply, List parms)
+ {
+ return Run(new CommandArgs(msg, silent, ply, parms));
+ }
+
public bool Run(string msg, TSPlayer ply, List parms)
{
return Run(msg, false, ply, parms);
@@ -704,7 +709,12 @@ namespace TShockAPI
TShock.Utils.SendLogs(GetString("{0} executed: {1}{2}.", player.Name, silent ? SilentSpecifier : Specifier, cmdText), Color.PaleVioletRed, player);
else
TShock.Utils.SendLogs(GetString("{0} executed (args omitted): {1}{2}.", player.Name, silent ? SilentSpecifier : Specifier, cmdName), Color.PaleVioletRed, player);
- cmd.Run(cmdText, silent, player, args);
+
+ CommandArgs arguments = new CommandArgs(cmdText, silent, player, args);
+ bool handled = PlayerHooks.OnPrePlayerCommand(cmd, ref arguments);
+ if (!handled)
+ cmd.Run(arguments);
+ PlayerHooks.OnPostPlayerCommand(cmd, arguments, handled);
}
}
return true;
@@ -1176,7 +1186,7 @@ namespace TShockAPI
try
{
- TShock.UserAccounts.SetUserGroup(account, args.Parameters[2]);
+ TShock.UserAccounts.SetUserGroup(args.Player, account, args.Parameters[2]);
TShock.Log.ConsoleInfo(GetString("{0} changed account {1} to group {2}.", args.Player.Name, account.Name, args.Parameters[2]));
args.Player.SendSuccessMessage(GetString("Account {0} has been changed to group {1}.", account.Name, args.Parameters[2]));
@@ -1193,6 +1203,10 @@ namespace TShockAPI
{
args.Player.SendErrorMessage(GetString($"User {account.Name} does not exist."));
}
+ catch (UserGroupUpdateLockedException)
+ {
+ args.Player.SendErrorMessage(GetString("Hook blocked the attempt to change the user group."));
+ }
catch (UserAccountManagerException e)
{
args.Player.SendErrorMessage(GetString($"User {account.Name} could not be added. Check console for details."));
@@ -2044,6 +2058,7 @@ namespace TShockAPI
private static void OffNoSave(CommandArgs args)
{
string reason = ((args.Parameters.Count > 0) ? GetString("Server shutting down: ") + String.Join(" ", args.Parameters) : GetString("Server shutting down."));
+ Netplay.SaveOnServerExit = false;
TShock.Utils.StopServer(false, reason);
}
@@ -3070,12 +3085,12 @@ namespace TShockAPI
args.Player.SendErrorMessage(GetString("You do not have permission to teleport all other players."));
return;
}
- for (int i = 0; i < Main.maxPlayers; i++)
+ foreach (var player in TShock.Players)
{
- if (Main.player[i].active && (Main.player[i] != args.TPlayer))
+ if (player != null && player.Active && player.Index != args.Player.Index)
{
- if (TShock.Players[i].Teleport(args.TPlayer.position.X, args.TPlayer.position.Y))
- TShock.Players[i].SendSuccessMessage(GetString("You were teleported to {0}.", args.Player.Name));
+ if (player.Teleport(args.TPlayer.position.X, args.TPlayer.position.Y))
+ player.SendSuccessMessage(GetString("You were teleported to {0}.", args.Player.Name));
}
}
args.Player.SendSuccessMessage(GetString("Teleported everyone to yourself."));
@@ -4622,21 +4637,22 @@ namespace TShockAPI
{
if (args.Parameters.Count != 1)
{
- args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}wind .", Specifier));
+ args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}wind .", Specifier));
return;
}
- int speed;
- if (!int.TryParse(args.Parameters[0], out speed) || speed * 100 < 0)
+ float mph;
+ if (!float.TryParse(args.Parameters[0], out mph) || mph is < -40f or > 40f)
{
- args.Player.SendErrorMessage(GetString("Invalid wind speed."));
+ args.Player.SendErrorMessage(GetString("Invalid wind speed (must be between -40 and 40)."));
return;
}
+ float speed = mph / 50f; // -40 to 40 mph -> -0.8 to 0.8
Main.windSpeedCurrent = speed;
Main.windSpeedTarget = speed;
TSPlayer.All.SendData(PacketTypes.WorldInfo);
- TSPlayer.All.SendInfoMessage(GetString("{0} changed the wind speed to {1}.", args.Player.Name, speed));
+ TSPlayer.All.SendInfoMessage(GetString("{0} changed the wind speed to {1}mph.", args.Player.Name, mph));
}
#endregion Time/PvpFun Commands
@@ -6462,7 +6478,7 @@ namespace TShockAPI
if (target == user)
user.SendSuccessMessage(GetString($"You buffed yourself with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds."));
else
- target.SendSuccessMessage(GetString($"You have buffed {user.Name} with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!"));
+ user.SendSuccessMessage(GetString($"You have buffed {target.Name} with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!"));
if (!args.Silent && target != user)
target.SendSuccessMessage(GetString($"{user.Name} has buffed you with {TShock.Utils.GetBuffName(id)} ({TShock.Utils.GetBuffDescription(id)}) for {time} seconds!"));
}
@@ -6740,10 +6756,6 @@ namespace TShockAPI
playerToGod.GodMode = !playerToGod.GodMode;
- var godPower = CreativePowerManager.Instance.GetPower();
-
- godPower.SetEnabledState(playerToGod.Index, playerToGod.GodMode);
-
if (playerToGod != args.Player)
{
args.Player.SendSuccessMessage(playerToGod.GodMode
diff --git a/TShockAPI/Configuration/TShockConfig.cs b/TShockAPI/Configuration/TShockConfig.cs
index 79c374b2..c91466fe 100644
--- a/TShockAPI/Configuration/TShockConfig.cs
+++ b/TShockAPI/Configuration/TShockConfig.cs
@@ -320,8 +320,8 @@ namespace TShockAPI.Configuration
[Description("The reason given if banning a mediumcore player on death.")]
public string MediumcoreBanReason = GetString("Death results in a ban");
- /// Disbales IP bans by default, if no arguments are passed to the ban command.
- [Description("Disbales IP bans by default, if no arguments are passed to the ban command.")]
+ /// Disables IP bans by default, if no arguments are passed to the ban command.
+ [Description("Disables IP bans by default, if no arguments are passed to the ban command.")]
public bool DisableDefaultIPBan;
/// Enable or disable the whitelist based on IP addresses in the whitelist.txt file.
diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs
index 575ac3dc..5a5e13a6 100644
--- a/TShockAPI/DB/CharacterManager.cs
+++ b/TShockAPI/DB/CharacterManager.cs
@@ -189,6 +189,9 @@ namespace TShockAPI.DB
if (!player.IsLoggedIn)
return false;
+ if (player.State < 10)
+ return false;
+
if (player.HasPermission(Permissions.bypassssc) && !fromCommand)
{
TShock.Log.ConsoleInfo(GetParticularString("{0} is a player name", $"Skipping SSC save (due to tshock.ignore.ssc) for {player.Account.Name}"));
diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs
index 0354989e..6fc50d98 100644
--- a/TShockAPI/DB/UserManager.cs
+++ b/TShockAPI/DB/UserManager.cs
@@ -25,6 +25,7 @@ using MySql.Data.MySqlClient;
using System.Text.RegularExpressions;
using BCrypt.Net;
using System.Security.Cryptography;
+using TShockAPI.Hooks;
namespace TShockAPI.DB
{
@@ -166,7 +167,41 @@ namespace TShockAPI.DB
if (null == grp)
throw new GroupNotExistsException(group);
- if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, account.Name) == 0)
+ if (AccountHooks.OnAccountGroupUpdate(account, ref grp))
+ throw new UserGroupUpdateLockedException(account.Name);
+
+ if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", grp.Name, account.Name) == 0)
+ throw new UserAccountNotExistException(account.Name);
+
+ try
+ {
+ // Update player group reference for any logged in player
+ foreach (var player in TShock.Players.Where(p => p != null && p.Account != null && p.Account.Name == account.Name))
+ {
+ player.Group = grp;
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new UserAccountManagerException(GetString("SetUserGroup SQL returned an error"), ex);
+ }
+ }
+ ///
+ /// Sets the group for a given username
+ ///
+ /// Who changes the group
+ /// The user account
+ /// The user account group to be set
+ public void SetUserGroup(TSPlayer author, UserAccount account, string group)
+ {
+ Group grp = TShock.Groups.GetGroupByName(group);
+ if (null == grp)
+ throw new GroupNotExistsException(group);
+
+ if (AccountHooks.OnAccountGroupUpdate(account, author, ref grp))
+ throw new UserGroupUpdateLockedException(account.Name);
+
+ if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", grp.Name, account.Name) == 0)
throw new UserAccountNotExistException(account.Name);
try
@@ -619,7 +654,7 @@ namespace TShockAPI.DB
public class UserAccountNotExistException : UserAccountManagerException
{
/// Creates a new UserAccountNotExistException object, with the user account name in the message.
- /// The user account name to be pasesd in the message.
+ /// The user account name to be passed in the message.
/// A new UserAccountNotExistException object with a message containing the user account name that does not exist.
public UserAccountNotExistException(string name)
: base(GetString($"User account {name} does not exist"))
@@ -627,6 +662,20 @@ namespace TShockAPI.DB
}
}
+ /// The UserGroupUpdateLockedException used when the user group update failed and the request failed as a result..
+ [Serializable]
+ public class UserGroupUpdateLockedException : UserAccountManagerException
+ {
+ /// Creates a new UserGroupUpdateLockedException object.
+ /// The name of the user who failed to change the group.
+ /// New UserGroupUpdateLockedException object with a message containing the name of the user account that failed to change the group.
+ public UserGroupUpdateLockedException(string name) :
+ base(GetString($"Unable to update group of user {name}."))
+ {
+ }
+ }
+
+
/// A GroupNotExistsException, used when a group does not exist.
[Serializable]
public class GroupNotExistsException : UserAccountManagerException
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 9ce1a5a2..b736a512 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -2248,6 +2248,7 @@ namespace TShockAPI
var args = new SyncTilePickingEventArgs
{
+ Player = player,
PlayerIndex = playerIndex,
TileX = tileX,
TileY = tileY,
@@ -4444,6 +4445,11 @@ namespace TShockAPI
return true;
}
+ // Don't modify the player data if it isn't there.
+ // This is the case whilst the player is connecting, as we receive the SyncLoadout packet before the ContinueConnecting2 packet.
+ if (args.Player.PlayerData == null)
+ return false;
+
// The client does not sync slot changes when changing loadouts, it only tells the server the loadout index changed,
// and the server will replicate the changes the client did. This means that PlayerData.StoreSlot is never called, so we need to
// swap around the PlayerData items ourself.
diff --git a/TShockAPI/Group.cs b/TShockAPI/Group.cs
index ff2ba2e9..45086eef 100644
--- a/TShockAPI/Group.cs
+++ b/TShockAPI/Group.cs
@@ -20,6 +20,8 @@ using System;
using System.Linq;
using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+
namespace TShockAPI
{
///
@@ -52,17 +54,17 @@ namespace TShockAPI
///
/// The group that this group inherits permissions from.
///
- public Group Parent { get; set; }
+ public virtual Group Parent { get; set; }
///
/// The chat prefix for this group.
///
- public string Prefix { get; set; }
+ public virtual string Prefix { get; set; }
///
/// The chat suffix for this group.
///
- public string Suffix { get; set; }
+ public virtual string Suffix { get; set; }
///
/// The name of the parent, not particularly sure why this is here.
@@ -164,6 +166,20 @@ namespace TShockAPI
///
public byte B = 255;
+ ///
+ /// Simplifies work with the , , properties.
+ ///
+ public virtual Color Color
+ {
+ get => new Color(R, G, B);
+ set
+ {
+ R = value.R;
+ G = value.G;
+ B = value.B;
+ }
+ }
+
///
/// The default group attributed to unregistered users.
///
@@ -242,7 +258,7 @@ namespace TShockAPI
/// Adds a permission to the list of negated permissions.
///
/// The permission to negate.
- public void NegatePermission(string permission)
+ public virtual void NegatePermission(string permission)
{
// Avoid duplicates
if (!negatedpermissions.Contains(permission))
@@ -256,7 +272,7 @@ namespace TShockAPI
/// Adds a permission to the list of permissions.
///
/// The permission to add.
- public void AddPermission(string permission)
+ public virtual void AddPermission(string permission)
{
if (permission.StartsWith("!"))
{
@@ -276,7 +292,7 @@ namespace TShockAPI
/// will parse "!permission" and add it to the negated permissions.
///
/// The new list of permissions to associate with the group.
- public void SetPermission(List permission)
+ public virtual void SetPermission(List permission)
{
permissions.Clear();
negatedpermissions.Clear();
@@ -288,7 +304,7 @@ namespace TShockAPI
/// where "!permission" will remove a negated permission.
///
///
- public void RemovePermission(string permission)
+ public virtual void RemovePermission(string permission)
{
if (permission.StartsWith("!"))
{
@@ -302,7 +318,7 @@ namespace TShockAPI
/// Assigns all fields of this instance to another.
///
/// The other instance.
- public void AssignTo(Group otherGroup)
+ public virtual void AssignTo(Group otherGroup)
{
otherGroup.Name = Name;
otherGroup.Parent = Parent;
diff --git a/TShockAPI/Handlers/SendTileRectHandler.cs b/TShockAPI/Handlers/SendTileRectHandler.cs
index 3ff85fea..d85185b8 100644
--- a/TShockAPI/Handlers/SendTileRectHandler.cs
+++ b/TShockAPI/Handlers/SendTileRectHandler.cs
@@ -1,26 +1,640 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Collections.Generic;
+using System.IO;
using Terraria;
-using Terraria.DataStructures;
-using Terraria.GameContent.Tile_Entities;
using Terraria.ID;
-using Terraria.ObjectData;
using TShockAPI.Net;
namespace TShockAPI.Handlers
{
///
- /// Provides processors for handling Tile Rect packets
+ /// Provides processors for handling tile rect packets.
+ /// This required many hours of reverse engineering work, and is kindly provided to TShock for free by @punchready.
///
- public class SendTileRectHandler : IPacketHandler
+ public sealed class SendTileRectHandler : IPacketHandler
{
///
- /// Maps plant tile types to their valid grass ground tiles when using flower boots
+ /// Represents a tile rectangle sent through the packet.
///
- private static readonly Dictionary> FlowerBootPlantToGrassMap = new Dictionary>
+ private sealed class TileRect
+ {
+ private readonly NetTile[,] _tiles;
+ public readonly int X;
+ public readonly int Y;
+ public readonly int Width;
+ public readonly int Height;
+
+ ///
+ /// Accesses the tiles contained in this rect.
+ ///
+ /// The X coordinate within the rect.
+ /// The Y coordinate within the rect.
+ /// The tile at the given position within the rect.
+ public NetTile this[int x, int y] => _tiles[x, y];
+
+ ///
+ /// Constructs a new tile rect based on the given information.
+ ///
+ public TileRect(NetTile[,] tiles, int x, int y, int width, int height)
+ {
+ _tiles = tiles;
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+
+ ///
+ /// Reads a tile rect from the given stream.
+ ///
+ /// The resulting tile rect.
+ public static TileRect Read(MemoryStream stream, int tileX, int tileY, int width, int height)
+ {
+ NetTile[,] tiles = new NetTile[width, height];
+ for (int x = 0; x < width; x++)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ tiles[x, y] = new NetTile();
+ tiles[x, y].Unpack(stream); // explicit > implicit
+ }
+ }
+ return new TileRect(tiles, tileX, tileY, width, height);
+ }
+ }
+
+ ///
+ /// Represents a common tile rect operation (Placement, State Change, Removal).
+ ///
+ private readonly struct TileRectMatch
+ {
+ private const short IGNORE_FRAME = -1;
+
+ private enum MatchType
+ {
+ Placement,
+ StateChange,
+ Removal,
+ }
+
+ private readonly int Width;
+ private readonly int Height;
+
+ private readonly ushort TileType;
+ private readonly short MaxFrameX;
+ private readonly short MaxFrameY;
+ private readonly short FrameXStep;
+ private readonly short FrameYStep;
+
+ private readonly MatchType Type;
+
+ private TileRectMatch(MatchType type, int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep)
+ {
+ Type = type;
+ Width = width;
+ Height = height;
+ TileType = tileType;
+ MaxFrameX = maxFrameX;
+ MaxFrameY = maxFrameY;
+ FrameXStep = frameXStep;
+ FrameYStep = frameYStep;
+ }
+
+ ///
+ /// Creates a new placement operation.
+ ///
+ /// The width of the placement.
+ /// The height of the placement.
+ /// The tile type of the placement.
+ /// The maximum allowed frameX of the placement.
+ /// The maximum allowed frameY of the placement.
+ /// The step size in which frameX changes for this placement, or 1 if any value is allowed.
+ /// The step size in which frameX changes for this placement, or 1 if any value is allowed.
+ /// The resulting operation match.
+ public static TileRectMatch Placement(int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep)
+ {
+ return new TileRectMatch(MatchType.Placement, width, height, tileType, maxFrameX, maxFrameY, frameXStep, frameYStep);
+ }
+
+ ///
+ /// Creates a new state change operation.
+ ///
+ /// The width of the state change.
+ /// The height of the state change.
+ /// The target tile type of the state change.
+ /// The maximum allowed frameX of the state change.
+ /// The maximum allowed frameY of the state change.
+ /// The step size in which frameX changes for this placement, or 1 if any value is allowed.
+ /// The step size in which frameY changes for this placement, or 1 if any value is allowed.
+ /// The resulting operation match.
+ public static TileRectMatch StateChange(int width, int height, ushort tileType, short maxFrameX, short maxFrameY, short frameXStep, short frameYStep)
+ {
+ return new TileRectMatch(MatchType.StateChange, width, height, tileType, maxFrameX, maxFrameY, frameXStep, frameYStep);
+ }
+
+ ///
+ /// Creates a new state change operation which only changes frameX.
+ ///
+ /// The width of the state change.
+ /// The height of the state change.
+ /// The target tile type of the state change.
+ /// The maximum allowed frameX of the state change.
+ /// The step size in which frameX changes for this placement, or 1 if any value is allowed.
+ /// The resulting operation match.
+ public static TileRectMatch StateChangeX(int width, int height, ushort tileType, short maxFrame, short frameStep)
+ {
+ return new TileRectMatch(MatchType.StateChange, width, height, tileType, maxFrame, IGNORE_FRAME, frameStep, 0);
+ }
+
+ ///
+ /// Creates a new state change operation which only changes frameY.
+ ///
+ /// The width of the state change.
+ /// The height of the state change.
+ /// The target tile type of the state change.
+ /// The maximum allowed frameY of the state change.
+ /// The step size in which frameY changes for this placement, or 1 if any value is allowed.
+ /// The resulting operation match.
+ public static TileRectMatch StateChangeY(int width, int height, ushort tileType, short maxFrame, short frameStep)
+ {
+ return new TileRectMatch(MatchType.StateChange, width, height, tileType, IGNORE_FRAME, maxFrame, 0, frameStep);
+ }
+
+ ///
+ /// Creates a new removal operation.
+ ///
+ /// The width of the removal.
+ /// The height of the removal.
+ /// The target tile type of the removal.
+ /// The resulting operation match.
+ public static TileRectMatch Removal(int width, int height, ushort tileType)
+ {
+ return new TileRectMatch(MatchType.Removal, width, height, tileType, 0, 0, 0, 0);
+ }
+
+ ///
+ /// Determines whether the given tile rectangle matches this operation, and if so, applies it to the world.
+ ///
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect matches this operation and the changes have been applied, otherwise .
+ public bool Matches(TSPlayer player, TileRect rect)
+ {
+ if (rect.Width != Width || rect.Height != Height)
+ {
+ return false;
+ }
+
+ for (int x = 0; x < rect.Width; x++)
+ {
+ for (int y = 0; y < rect.Height; y++)
+ {
+ NetTile tile = rect[x, y];
+ if (Type is MatchType.Placement or MatchType.StateChange)
+ {
+ if (tile.Type != TileType)
+ {
+ return false;
+ }
+ }
+ if (Type is MatchType.Placement or MatchType.StateChange)
+ {
+ if (MaxFrameX != IGNORE_FRAME)
+ {
+ if (tile.FrameX < 0 || tile.FrameX > MaxFrameX || tile.FrameX % FrameXStep != 0)
+ {
+ return false;
+ }
+ }
+ if (MaxFrameY != IGNORE_FRAME)
+ {
+ if (tile.FrameY < 0 || tile.FrameY > MaxFrameY || tile.FrameY % FrameYStep != 0)
+ {
+ // this is the only tile type sent in a tile rect where the frame have a different pattern (56, 74, 92 instead of 54, 72, 90)
+ if (!(TileType == TileID.LunarMonolith && tile.FrameY % FrameYStep == 2))
+ {
+ return false;
+ }
+ }
+ }
+ }
+ if (Type == MatchType.Removal)
+ {
+ if (tile.Active)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ for (int x = rect.X; x < rect.X + rect.Width; x++)
+ {
+ for (int y = rect.Y; y < rect.Y + rect.Height; y++)
+ {
+ if (!player.HasBuildPermission(x, y))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+ }
+ }
+
+ switch (Type)
+ {
+ case MatchType.Placement:
+ {
+ return MatchPlacement(player, rect);
+ }
+ case MatchType.StateChange:
+ {
+ return MatchStateChange(player, rect);
+ }
+ case MatchType.Removal:
+ {
+ return MatchRemoval(player, rect);
+ }
+ }
+
+ return false;
+ }
+
+ private bool MatchPlacement(TSPlayer player, TileRect rect)
+ {
+ for (int x = rect.X; x < rect.Y + rect.Width; x++)
+ {
+ for (int y = rect.Y; y < rect.Y + rect.Height; y++)
+ {
+ if (Main.tile[x, y].active()) // the client will kill tiles that auto break before placing the object
+ {
+ return false;
+ }
+ }
+ }
+
+ // let's hope tile types never go out of short range (they use ushort in terraria's code)
+ if (TShock.TileBans.TileIsBanned((short)TileType, player))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+
+ for (int x = 0; x < rect.Width; x++)
+ {
+ for (int y = 0; y < rect.Height; y++)
+ {
+ Main.tile[x + rect.X, y + rect.Y].active(active: true);
+ Main.tile[x + rect.X, y + rect.Y].type = rect[x, y].Type;
+ Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX;
+ Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY;
+ }
+ }
+
+ return true;
+ }
+
+ private bool MatchStateChange(TSPlayer player, TileRect rect)
+ {
+ for (int x = rect.X; x < rect.Y + rect.Width; x++)
+ {
+ for (int y = rect.Y; y < rect.Y + rect.Height; y++)
+ {
+ if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType)
+ {
+ return false;
+ }
+ }
+ }
+
+ for (int x = 0; x < rect.Width; x++)
+ {
+ for (int y = 0; y < rect.Height; y++)
+ {
+ if (MaxFrameX != IGNORE_FRAME)
+ {
+ Main.tile[x + rect.X, y + rect.Y].frameX = rect[x, y].FrameX;
+ }
+ if (MaxFrameY != IGNORE_FRAME)
+ {
+ Main.tile[x + rect.X, y + rect.Y].frameY = rect[x, y].FrameY;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private bool MatchRemoval(TSPlayer player, TileRect rect)
+ {
+ for (int x = rect.X; x < rect.Y + rect.Width; x++)
+ {
+ for (int y = rect.Y; y < rect.Y + rect.Height; y++)
+ {
+ if (!Main.tile[x, y].active() || Main.tile[x, y].type != TileType)
+ {
+ return false;
+ }
+ }
+ }
+
+ for (int x = 0; x < rect.Width; x++)
+ {
+ for (int y = 0; y < rect.Height; y++)
+ {
+ Main.tile[x + rect.X, y + rect.Y].active(active: false);
+ Main.tile[x + rect.X, y + rect.Y].frameX = -1;
+ Main.tile[x + rect.X, y + rect.Y].frameY = -1;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ /// Contains the complete list of valid tile rect operations the game currently performs.
+ ///
+ // The matches restrict the tile rects to only place one kind of tile, and only with the given maximum values and step sizes for frameX and frameY. This performs pretty much perfect checks on the data, allowing only valid placements.
+ // For TileID.MinecartTrack, the data is taken from `Minecart._trackSwitchOptions`, allowing any framing value in this array (currently 0-36).
+ // For TileID.Plants, it is taken from `ItemID.Sets.flowerPacketInfo[n].stylesOnPurity`, allowing every style multiplied by 18.
+ // The other operations are based on code analysis and manual observation.
+ private static readonly TileRectMatch[] Matches = new TileRectMatch[]
+ {
+ TileRectMatch.Placement(2, 3, TileID.TargetDummy, 54, 36, 18, 18),
+ TileRectMatch.Placement(3, 4, TileID.TeleportationPylon, 468, 54, 18, 18),
+ TileRectMatch.Placement(2, 3, TileID.DisplayDoll, 126, 36, 18, 18),
+ TileRectMatch.Placement(2, 3, TileID.HatRack, 90, 54, 18, 18),
+ TileRectMatch.Placement(2, 2, TileID.ItemFrame, 162, 18, 18, 18),
+ TileRectMatch.Placement(3, 3, TileID.WeaponsRack2, 90, 36, 18, 18),
+ TileRectMatch.Placement(1, 1, TileID.FoodPlatter, 18, 0, 18, 18),
+ TileRectMatch.Placement(1, 1, TileID.LogicSensor, 108, 0, 18, 18),
+
+ TileRectMatch.StateChangeY(3, 2, TileID.Campfire, 54, 18),
+ TileRectMatch.StateChangeY(4, 3, TileID.Cannon, 468, 18),
+ TileRectMatch.StateChangeY(2, 2, TileID.ArrowSign, 270, 18),
+ TileRectMatch.StateChangeY(2, 2, TileID.PaintedArrowSign, 270, 18),
+
+ TileRectMatch.StateChangeX(2, 2, TileID.MusicBoxes, 54, 18),
+
+ TileRectMatch.StateChangeY(2, 3, TileID.LunarMonolith, 92, 18),
+ TileRectMatch.StateChangeY(2, 3, TileID.BloodMoonMonolith, 90, 18),
+ TileRectMatch.StateChangeY(2, 3, TileID.VoidMonolith, 90, 18),
+ TileRectMatch.StateChangeY(2, 3, TileID.EchoMonolith, 90, 18),
+ TileRectMatch.StateChangeY(2, 3, TileID.ShimmerMonolith, 144, 18),
+ TileRectMatch.StateChangeY(2, 4, TileID.WaterFountain, 126, 18),
+
+ TileRectMatch.StateChangeX(1, 1, TileID.Candles, 18, 18),
+ TileRectMatch.StateChangeX(1, 1, TileID.PeaceCandle, 18, 18),
+ TileRectMatch.StateChangeX(1, 1, TileID.WaterCandle, 18, 18),
+ TileRectMatch.StateChangeX(1, 1, TileID.PlatinumCandle, 18, 18),
+ TileRectMatch.StateChangeX(1, 1, TileID.ShadowCandle, 18, 18),
+
+ TileRectMatch.StateChange(1, 1, TileID.Traps, 90, 90, 18, 18),
+
+ TileRectMatch.StateChangeX(1, 1, TileID.WirePipe, 36, 18),
+ TileRectMatch.StateChangeX(1, 1, TileID.ProjectilePressurePad, 66, 22),
+ TileRectMatch.StateChangeX(1, 1, TileID.Plants, 792, 18),
+ TileRectMatch.StateChangeX(1, 1, TileID.MinecartTrack, 36, 1),
+
+ TileRectMatch.Removal(1, 2, TileID.Firework),
+ TileRectMatch.Removal(1, 1, TileID.LandMine),
+ };
+
+
+ ///
+ /// Handles a packet receive event.
+ ///
+ public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args)
+ {
+ // this permission bypasses all checks for direct access to the world
+ if (args.Player.HasPermission(Permissions.allowclientsideworldedit))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}"));
+
+ // use vanilla handling
+ args.Handled = false;
+ return;
+ }
+
+ // this handler handles the entire logic of this packet
+ args.Handled = true;
+
+ // as of 1.4 this is the biggest size the client will send in any case, determined by full code analysis
+ // see default matches above and special cases below
+ if (args.Width > 4 || args.Length > 4)
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from size from {args.Player.Name}"));
+
+ // definitely invalid; do not send any correcting data
+ return;
+ }
+
+ // player throttled?
+ if (args.Player.IsBouncerThrottled())
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from throttle from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ // player disabled?
+ if (args.Player.IsBeingDisabled())
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from being disabled from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ // read the tile rectangle
+ TileRect rect = TileRect.Read(args.Data, args.TileX, args.TileY, args.Width, args.Length);
+
+ // check if the positioning is valid
+ if (!IsRectPositionValid(args.Player, rect))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from out of bounds / build permission from {args.Player.Name}"));
+
+ // send nothing due to out of bounds
+ return;
+ }
+
+ // a very special case, due to the clentaminator having a larger range than TSPlayer.IsInRange() allows
+ if (MatchesConversionSpread(args.Player, rect))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ // check if the distance is valid
+ if (!IsRectDistanceValid(args.Player, rect))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from out of range from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ // a very special case, due to the flower seed check otherwise hijacking this
+ if (MatchesFlowerBoots(args.Player, rect))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ // check if the rect matches any valid operation
+ foreach (TileRectMatch match in Matches)
+ {
+ if (match.Matches(args.Player, rect))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+ }
+
+ // a few special cases
+ if (MatchesGrassMow(args.Player, rect) || MatchesChristmasTree(args.Player, rect))
+ {
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from matches from {args.Player.Name}"));
+
+ // send correcting data
+ args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
+ return;
+ }
+
+ ///
+ /// Checks whether the tile rect is at a valid position for the given player.
+ ///
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect at a valid position, otherwise .
+ private static bool IsRectPositionValid(TSPlayer player, TileRect rect)
+ {
+ for (int x = 0; x < rect.Width; x++)
+ {
+ for (int y = 0; y < rect.Height; y++)
+ {
+ int realX = rect.X + x;
+ int realY = rect.Y + y;
+
+ if (realX < 0 || realX >= Main.maxTilesX || realY < 0 || realY >= Main.maxTilesY)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Checks whether the tile rect is at a valid distance to the given player.
+ ///
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect at a valid distance, otherwise .
+ private static bool IsRectDistanceValid(TSPlayer player, TileRect rect)
+ {
+ for (int x = 0; x < rect.Width; x++)
+ {
+ for (int y = 0; y < rect.Height; y++)
+ {
+ int realX = rect.X + x;
+ int realY = rect.Y + y;
+
+ if (!player.IsInRange(realX, realY))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ ///
+ /// Checks whether the tile rect is a valid conversion spread (Clentaminator, Powders, etc.).
+ ///
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect matches a conversion spread operation, otherwise .
+ private static bool MatchesConversionSpread(TSPlayer player, TileRect rect)
+ {
+ if (rect.Width != 1 || rect.Height != 1)
+ {
+ return false;
+ }
+
+ ITile oldTile = Main.tile[rect.X, rect.Y];
+ NetTile newTile = rect[0, 0];
+
+ WorldGenMock.SimulateConversionChange(rect.X, rect.Y, out HashSet validTiles, out HashSet validWalls);
+
+ if (newTile.Type != oldTile.type && validTiles.Contains(newTile.Type))
+ {
+ if (TShock.TileBans.TileIsBanned((short)newTile.Type, player))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+ else if (!player.HasBuildPermission(rect.X, rect.Y))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+ else
+ {
+ Main.tile[rect.X, rect.Y].type = newTile.Type;
+ Main.tile[rect.X, rect.Y].frameX = newTile.FrameX;
+ Main.tile[rect.X, rect.Y].frameY = newTile.FrameY;
+
+ return true;
+ }
+ }
+
+ if (newTile.Wall != oldTile.wall && validWalls.Contains(newTile.Wall))
+ {
+ // wallbans when?
+
+ if (!player.HasBuildPermission(rect.X, rect.Y))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+ else
+ {
+ Main.tile[rect.X, rect.Y].wall = newTile.Wall;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ private static readonly Dictionary> PlantToGrassMap = new Dictionary>
{
{ TileID.Plants, new HashSet()
{
@@ -38,661 +652,674 @@ namespace TShockAPI.Handlers
{
TileID.JungleGrass
} },
+ { TileID.AshPlants, new HashSet()
+ {
+ TileID.AshGrass
+ } },
};
- ///
- /// Maps plant tile types to a list of valid styles, which are used to determine the FrameX value of the plant tile
- /// See `Player.DoBootsEffect_PlaceFlowersOnTile`
- ///
- private static readonly Dictionary> FlowerBootPlantToStyleMap = new Dictionary>()
+ private static readonly Dictionary> GrassToStyleMap = new Dictionary>()
{
{ TileID.Plants, new HashSet()
{
- // The upper line is from a `NextFromList` call
- // The lower line is from an additional switch which will add the listed options by adding a random value to a select set of styles
6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 30, 33, 36, 39, 42,
22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44,
} },
{ TileID.HallowedPlants, new HashSet()
{
- // 5 is intentionally missing here because it is being skipped by vanilla
4, 6,
} },
{ TileID.HallowedPlants2, new HashSet()
{
- // 5 is intentionally missing here because it is being skipped by vanilla
2, 3, 4, 6, 7,
} },
{ TileID.JunglePlants2, new HashSet()
{
9, 10, 11, 12, 13, 14, 15, 16,
} },
+ { TileID.AshPlants, new HashSet()
+ {
+ 6, 7, 8, 9, 10,
+ } },
};
///
- /// Item IDs that can spawn flowers while you walk
+ /// Checks whether the tile rect is a valid Flower Boots placement.
///
- public static List FlowerBootItems = new List
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect matches a Flower Boots placement, otherwise .
+ private static bool MatchesFlowerBoots(TSPlayer player, TileRect rect)
{
- ItemID.FlowerBoots,
- ItemID.FairyBoots
- };
-
- ///
- /// Maps TileIDs to Tile Entity IDs.
- /// Note: is empty at the time of writing, but entities are dynamically assigned their ID at initialize time
- /// which is why we can use the _myEntityId field on each entity type
- ///
- public static Dictionary TileEntityIdToTileIdMap = new Dictionary
- {
- { TileID.TargetDummy, TETrainingDummy._myEntityID },
- { TileID.ItemFrame, TEItemFrame._myEntityID },
- { TileID.LogicSensor, TELogicSensor._myEntityID },
- { TileID.DisplayDoll, TEDisplayDoll._myEntityID },
- { TileID.WeaponsRack2, TEWeaponsRack._myEntityID },
- { TileID.HatRack, TEHatRack._myEntityID },
- { TileID.FoodPlatter, TEFoodPlatter._myEntityID },
- { TileID.TeleportationPylon, TETeleportationPylon._myEntityID }
- };
-
- ///
- /// Invoked when a SendTileRect packet is received
- ///
- ///
- ///
- public void OnReceive(object sender, GetDataHandlers.SendTileRectEventArgs args)
- {
- // By default, we'll handle everything
- args.Handled = true;
-
- if (ShouldSkipProcessing(args))
+ if (rect.Width != 1 || rect.Height != 1)
{
- return;
+ return false;
}
- bool[,] processed = new bool[args.Width, args.Length];
- NetTile[,] tiles = ReadNetTilesFromStream(args.Data, args.Width, args.Length);
-
- Debug.VisualiseTileSetDiff(args.TileX, args.TileY, args.Width, args.Length, tiles);
-
- IterateTileRect(tiles, processed, args);
-
- // Uncommenting this function will send the same tile rect 10 blocks above you for visualisation. This will modify your world and overwrite existing blocks.
- // Use in test worlds only.
- //Debug.DisplayTileSetInGame(args.TileX, (short)(args.TileY - 10), args.Width, args.Length, tiles, args.Player);
-
- // If we are handling this event then we have updated the server's Main.tile state the way we want it.
- // At this point we should send our state back to the client so they remain in sync with the server
- if (args.Handled == true)
+ if (!player.TPlayer.flowerBoots)
{
- TSPlayer.All.SendTileRect(args.TileX, args.TileY, args.Width, args.Length);
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect reimplemented from carbonara from {args.Player.Name}"));
- }
- }
-
- ///
- /// Iterates over each tile in the tile rectangle and performs processing on individual tiles or multi-tile Tile Objects
- ///
- ///
- ///
- ///
- internal void IterateTileRect(NetTile[,] tiles, bool[,] processed, GetDataHandlers.SendTileRectEventArgs args)
- {
- int tileX = args.TileX;
- int tileY = args.TileY;
- byte width = args.Width;
- byte length = args.Length;
-
- for (int x = 0; x < width; x++)
- {
- for (int y = 0; y < length; y++)
- {
- // Do not process already processed tiles
- if (processed[x, y])
- {
- continue;
- }
-
- int realX = tileX + x;
- int realY = tileY + y;
-
- // Do not process tiles outside of the world boundaries
- if ((realX < 0 || realX >= Main.maxTilesX)
- || (realY < 0 || realY > Main.maxTilesY))
- {
- processed[x, y] = true;
- continue;
- }
-
- // Do not process tiles that the player cannot update
- if (!args.Player.HasBuildPermission(realX, realY) ||
- !args.Player.IsInRange(realX, realY))
- {
- processed[x, y] = true;
- continue;
- }
-
- NetTile newTile = tiles[x, y];
- TileObjectData data;
-
- // If the new tile has an associated TileObjectData object, we take the tile and the surrounding tiles that make up the tile object
- // and process them as a tile object
- if (newTile.Type < TileObjectData._data.Count && TileObjectData._data[newTile.Type] != null)
- {
- // Verify that the changes are actually valid conceptually
- // Many tiles that are never placed or modified using this packet are valid TileObjectData entries, which is the main attack vector for most exploits using this packet
- if (Main.tile[realX, realY].type == newTile.Type)
- {
- switch (newTile.Type)
- {
- // Some individual cases might still allow crashing exploits, as the actual framing is not being checked here
- // Doing so requires hard-coding the individual valid framing values and is a lot of effort
- case TileID.ProjectilePressurePad:
- case TileID.WirePipe:
- case TileID.Traps:
- case TileID.Candles:
- case TileID.PeaceCandle:
- case TileID.WaterCandle:
- case TileID.PlatinumCandle:
- case TileID.Firework:
- case TileID.WaterFountain:
- case TileID.BloodMoonMonolith:
- case TileID.VoidMonolith:
- case TileID.LunarMonolith:
- case TileID.MusicBoxes:
- case TileID.ArrowSign:
- case TileID.PaintedArrowSign:
- case TileID.Cannon:
- case TileID.Campfire:
- case TileID.Plants:
- case TileID.MinecartTrack:
- case TileID.ChristmasTree:
- case TileID.ShimmerMonolith:
- {
- // Allowed changes
- }
- break;
- default:
- {
- continue;
- }
- }
- }
- else
- {
- // Together with Flower Boots and Land Mine destruction, these are the only cases where a tile type is allowed to be modified
- switch (newTile.Type)
- {
- case TileID.LogicSensor:
- case TileID.FoodPlatter:
- case TileID.WeaponsRack2:
- case TileID.ItemFrame:
- case TileID.HatRack:
- case TileID.DisplayDoll:
- case TileID.TeleportationPylon:
- case TileID.TargetDummy:
- {
- // Allowed placements
- }
- break;
- default:
- {
- continue;
- }
- }
- }
-
- data = TileObjectData._data[newTile.Type];
- NetTile[,] newTiles;
- int objWidth = data.Width;
- int objHeight = data.Height;
-
- // Ensure the tile object fits inside the rect before processing it
- if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, processed))
- {
- continue;
- }
-
- newTiles = new NetTile[objWidth, objHeight];
-
- for (int i = 0; i < objWidth; i++)
- {
- for (int j = 0; j < objHeight; j++)
- {
- newTiles[i, j] = tiles[x + i, y + j];
- processed[x + i, y + j] = true;
- }
- }
- ProcessTileObject(newTile.Type, realX, realY, objWidth, objHeight, newTiles, args);
- continue;
- }
-
- // If the new tile does not have an associated tile object, process it as an individual tile
- ProcessSingleTile(realX, realY, newTile, width, length, args);
- processed[x, y] = true;
- }
- }
- }
-
- ///
- /// Processes a tile object consisting of multiple tiles from the tile rect packet
- ///
- /// The tile type the object is comprised of
- /// 2D array of NetTile containing the new tiles properties
- /// X position at the top left of the object
- /// Y position at the top left of the object
- /// Width of the tile object
- /// Height of the tile object
- /// SendTileRectEventArgs containing event information
- internal void ProcessTileObject(int tileType, int realX, int realY, int width, int height, NetTile[,] newTiles, GetDataHandlers.SendTileRectEventArgs args)
- {
- // As long as the player has permission to build, we should allow a tile object to be placed
- // More in depth checks should take place in handlers for the Place Object (79), Update Tile Entity (86), and Place Tile Entity (87) packets
- if (!args.Player.HasBuildPermissionForTileObject(realX, realY, width, height))
- {
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from no permission for tile object from {args.Player.Name}"));
- return;
+ return false;
}
- if (TShock.TileBans.TileIsBanned((short)tileType))
- {
- TShock.Log.ConsoleDebug(GetString("Bouncer / SendTileRect rejected for banned tile"));
- return;
- }
+ ITile oldTile = Main.tile[rect.X, rect.Y];
+ NetTile newTile = rect[0, 0];
- // Update all tiles in the tile object. These will be sent back to the player later
- UpdateMultipleServerTileStates(realX, realY, width, height, newTiles);
-
- // Tile entities have special placements that we should let the game deal with
- if (TileEntityIdToTileIdMap.ContainsKey(tileType))
- {
- TileEntity.PlaceEntityNet(realX, realY, TileEntityIdToTileIdMap[tileType]);
- }
- }
-
- ///
- /// Processes a single tile from the tile rect packet
- ///
- /// X position at the top left of the object
- /// Y position at the top left of the object
- /// The NetTile containing new tile properties
- /// The width of the rectangle being processed
- /// The length of the rectangle being processed
- /// SendTileRectEventArgs containing event information
- internal void ProcessSingleTile(int realX, int realY, NetTile newTile, byte rectWidth, byte rectLength, GetDataHandlers.SendTileRectEventArgs args)
- {
- // Some boots allow growing flowers on grass. This process sends a 1x1 tile rect to grow the flowers
- // The rect size must be 1 and the player must have an accessory that allows growing flowers in order for this rect to be valid
- if (rectWidth == 1 && rectLength == 1 && WorldGen.InWorld(realX, realY + 1) && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type)))
- {
- ProcessFlowerBoots(realX, realY, newTile);
- return;
- }
-
- ITile tile = Main.tile[realX, realY];
-
- // Triggering a single land mine tile
- if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.LandMine && !newTile.Active)
- {
- UpdateServerTileState(tile, newTile, TileDataType.Tile);
- }
-
- // Hammering a single junction box
- if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.WirePipe)
- {
- UpdateServerTileState(tile, newTile, TileDataType.Tile);
- }
-
- // Mowing a single grass tile: Grass -> GolfGrass OR HallowedGrass -> GolfGrassHallowed
- if (rectWidth == 1 && rectLength == 1 &&
- (
- tile.type == TileID.Grass && newTile.Type == TileID.GolfGrass ||
- tile.type == TileID.HallowedGrass && newTile.Type == TileID.GolfGrassHallowed
- ))
- {
- UpdateServerTileState(tile, newTile, TileDataType.Tile);
- if (WorldGen.InWorld(realX, realY + 1) && TileID.Sets.IsVine[Main.tile[realX, realY + 1].type]) // vanilla does in theory break the vines on its own, but we can't trust that
- {
- WorldGen.KillTile(realX, realY + 1);
- }
- }
-
- // Conversion: only sends a 1x1 rect; has to happen AFTER grass mowing as it would otherwise also let mowing through, but without fixing vines
- if (rectWidth == 1 && rectLength == 1)
- {
- ProcessConversionSpreads(tile, newTile);
- }
-
- // All other single tile updates should not be processed.
- }
-
- ///
- /// Applies changes to a tile if a tile rect for flower-growing boots is valid
- ///
- /// The tile x position of the tile rect packet - this is where the flowers are intending to grow
- /// The tile y position of the tile rect packet - this is where the flowers are intending to grow
- /// The NetTile containing information about the flowers that are being grown
- internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile)
- {
- ITile tile = Main.tile[realX, realY];
- // Ensure that:
- // - the placed plant is valid for the grass below
- // - the target tile is empty
- // - and the placed plant has valid framing (style * 18 = FrameX)
if (
- FlowerBootPlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) &&
- !tile.active() &&
- grassTiles.Contains(Main.tile[realX, realY + 1].type) &&
- FlowerBootPlantToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18))
+ PlantToGrassMap.TryGetValue(newTile.Type, out HashSet grassTiles) &&
+ !oldTile.active() && grassTiles.Contains(Main.tile[rect.X, rect.Y + 1].type) &&
+ GrassToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18))
)
{
- UpdateServerTileState(tile, newTile, TileDataType.Tile);
- }
- }
-
- // Moss and MossBrick are not used in conversion
- private static List _convertibleTiles = typeof(TileID.Sets.Conversion)
- .GetFields()
- .ExceptBy(new[] { nameof(TileID.Sets.Conversion.Moss), nameof(TileID.Sets.Conversion.MossBrick) }, f => f.Name)
- .Select(f => (bool[])f.GetValue(null))
- .ToList();
- // PureSand is only used in WorldGen.SpreadDesertWalls, which is server side
- private static List _convertibleWalls = typeof(WallID.Sets.Conversion)
- .GetFields()
- .ExceptBy(new[] { nameof(WallID.Sets.Conversion.PureSand) }, f => f.Name)
- .Select(f => (bool[])f.GetValue(null))
- .ToList();
-
- ///
- /// Updates a single tile on the server if it is a valid conversion from one tile or wall type to another (eg stone -> corrupt stone)
- ///
- /// The tile to update
- /// The NetTile containing new tile properties
- internal void ProcessConversionSpreads(ITile tile, NetTile newTile)
- {
- var allowTile = false;
- if (Main.tileMoss[tile.type] && TileID.Sets.Conversion.Stone[newTile.Type])
- {
- allowTile = true;
- }
- else if ((Main.tileMoss[tile.type] || TileID.Sets.Conversion.Stone[tile.type] || TileID.Sets.Conversion.Ice[tile.type] || TileID.Sets.Conversion.Sandstone[tile.type]) &&
- (newTile.Type == TileID.Sandstone || newTile.Type == TileID.IceBlock))
- {
- // ProjectileID.SandSpray and ProjectileID.SnowSpray
- allowTile = true;
- }
- else
- {
- foreach (var tileType in _convertibleTiles)
+ if (TShock.TileBans.TileIsBanned((short)newTile.Type, player))
{
- if (tileType[tile.type] && tileType[newTile.Type])
- {
- allowTile = true;
- break;
- }
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
}
- }
- if (allowTile)
- {
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect processing a tile conversion update - [{tile.type}] -> [{newTile.Type}]"));
- UpdateServerTileState(tile, newTile, TileDataType.Tile);
- }
-
- foreach (var wallType in _convertibleWalls)
- {
- if (wallType[tile.wall] && wallType[newTile.Wall])
+ if (!player.HasBuildPermission(rect.X, rect.Y))
{
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect processing a wall conversion update - [{tile.wall}] -> [{newTile.Wall}]"));
- UpdateServerTileState(tile, newTile, TileDataType.Wall);
- break;
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
}
- }
- }
- ///
- /// Updates a single tile's world state with a set of changes from the networked tile state
- ///
- /// The tile to update
- /// The NetTile containing the change
- /// The type of data to merge into world state
- public static void UpdateServerTileState(ITile tile, NetTile newTile, TileDataType updateType)
- {
- //This logic (updateType & TDT.Tile) != 0 is the way Terraria does it (see: Tile.cs/Clear(TileDataType))
- //& is not a typo - we're performing a binary AND test to see if a given flag is set.
+ Main.tile[rect.X, rect.Y].active(active: true);
+ Main.tile[rect.X, rect.Y].type = newTile.Type;
+ Main.tile[rect.X, rect.Y].frameX = newTile.FrameX;
+ Main.tile[rect.X, rect.Y].frameY = 0;
- if ((updateType & TileDataType.Tile) != 0)
- {
- tile.active(newTile.Active);
- tile.type = newTile.Type;
-
- if (newTile.FrameImportant)
- {
- tile.frameX = newTile.FrameX;
- tile.frameY = newTile.FrameY;
- }
- else if (tile.type != newTile.Type || !tile.active())
- {
- //This is vanilla logic - if the tile changed types (or wasn't active) the frame values might not be valid - so we reset them to -1.
- tile.frameX = -1;
- tile.frameY = -1;
- }
- }
-
- if ((updateType & TileDataType.Wall) != 0)
- {
- tile.wall = newTile.Wall;
- }
-
- if ((updateType & TileDataType.TilePaint) != 0)
- {
- tile.color(newTile.TileColor);
- tile.fullbrightBlock(newTile.FullbrightBlock);
- tile.invisibleBlock(newTile.InvisibleBlock);
- }
-
- if ((updateType & TileDataType.WallPaint) != 0)
- {
- tile.wallColor(newTile.WallColor);
- tile.fullbrightWall(newTile.FullbrightWall);
- tile.invisibleWall(newTile.InvisibleWall);
- }
-
- if ((updateType & TileDataType.Liquid) != 0)
- {
- tile.liquid = newTile.Liquid;
- tile.liquidType(newTile.LiquidType);
- }
-
- if ((updateType & TileDataType.Slope) != 0)
- {
- tile.halfBrick(newTile.IsHalf);
- tile.slope(newTile.Slope);
- }
-
- if ((updateType & TileDataType.Wiring) != 0)
- {
- tile.wire(newTile.Wire);
- tile.wire2(newTile.Wire2);
- tile.wire3(newTile.Wire3);
- tile.wire4(newTile.Wire4);
- }
-
- if ((updateType & TileDataType.Actuator) != 0)
- {
- tile.actuator(newTile.IsActuator);
- tile.inActive(newTile.Inactive);
- }
- }
-
- ///
- /// Performs on multiple tiles
- ///
- ///
- ///
- ///
- ///
- ///
- public static void UpdateMultipleServerTileStates(int x, int y, int width, int height, NetTile[,] newTiles)
- {
- for (int i = 0; i < width; i++)
- {
- for (int j = 0; j < height; j++)
- {
- UpdateServerTileState(Main.tile[x + i, y + j], newTiles[i, j], TileDataType.Tile);
- }
- }
- }
-
- ///
- /// Reads a set of NetTiles from a memory stream
- ///
- ///
- ///
- ///
- ///
- static NetTile[,] ReadNetTilesFromStream(System.IO.MemoryStream stream, byte width, byte length)
- {
- NetTile[,] tiles = new NetTile[width, length];
- for (int x = 0; x < width; x++)
- {
- for (int y = 0; y < length; y++)
- {
- tiles[x, y] = new NetTile(stream);
- }
- }
-
- return tiles;
- }
-
- ///
- /// Determines whether or not the tile rect should be immediately accepted or rejected
- ///
- ///
- ///
- static bool ShouldSkipProcessing(GetDataHandlers.SendTileRectEventArgs args)
- {
- if (args.Player.HasPermission(Permissions.allowclientsideworldedit))
- {
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect accepted clientside world edit from {args.Player.Name}"));
- args.Handled = false;
- return true;
- }
-
- if (args.Width > 4 || args.Length > 4) // as of 1.4.3.6 this is the biggest size the client will send in any case
- {
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from non-vanilla tilemod from {args.Player.Name}"));
- return true;
- }
-
- if (args.Player.IsBouncerThrottled())
- {
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from throttle from {args.Player.Name}"));
- args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
- return true;
- }
-
- if (args.Player.IsBeingDisabled())
- {
- TShock.Log.ConsoleDebug(GetString($"Bouncer / SendTileRect rejected from being disabled from {args.Player.Name}"));
- args.Player.SendTileRect(args.TileX, args.TileY, args.Length, args.Width);
return true;
}
return false;
}
- ///
- /// Checks if a tile object fits inside the dimensions of a tile rectangle
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, bool[,] processed)
- {
- if (x + width > rectWidth || y + height > rectLength)
- {
- // This is ugly, but we want to mark all these tiles as processed so that we're not hitting this check multiple times for one dodgy tile object
- for (int i = x; i < rectWidth; i++)
- {
- for (int j = y; j < rectLength; j++)
- {
- processed[i, j] = true;
- }
- }
- TShock.Log.ConsoleDebug(GetString("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (excessive size)"));
+ private static readonly Dictionary GrassToMowedMap = new Dictionary
+ {
+ { TileID.Grass, TileID.GolfGrass },
+ { TileID.HallowedGrass, TileID.GolfGrassHallowed },
+ };
+
+ ///
+ /// Checks whether the tile rect is a valid grass mow.
+ ///
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect matches a grass mowing operation, otherwise .
+ private static bool MatchesGrassMow(TSPlayer player, TileRect rect)
+ {
+ if (rect.Width != 1 || rect.Height != 1)
+ {
return false;
}
- return true;
+ ITile oldTile = Main.tile[rect.X, rect.Y];
+ NetTile newTile = rect[0, 0];
+
+ if (GrassToMowedMap.TryGetValue(oldTile.type, out ushort mowed) && newTile.Type == mowed)
+ {
+ if (TShock.TileBans.TileIsBanned((short)newTile.Type, player))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+
+ if (!player.HasBuildPermission(rect.X, rect.Y))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+
+ Main.tile[rect.X, rect.Y].type = newTile.Type;
+ if (!newTile.FrameImportant)
+ {
+ Main.tile[rect.X, rect.Y].frameX = -1;
+ Main.tile[rect.X, rect.Y].frameY = -1;
+ }
+
+ // prevent a common crash when the game checks all vines in an unlimited horizontal length
+ if (TileID.Sets.IsVine[Main.tile[rect.X, rect.Y + 1].type])
+ {
+ WorldGen.KillTile(rect.X, rect.Y + 1);
+ }
+
+ return true;
+ }
+
+ return false;
}
- class Debug
+
+ ///
+ /// Checks whether the tile rect is a valid christmas tree modification.
+ /// This also required significant reverse engineering effort.
+ ///
+ /// The player the operation originates from.
+ /// The tile rectangle of the operation.
+ /// , if the rect matches a christmas tree operation, otherwise .
+ private static bool MatchesChristmasTree(TSPlayer player, TileRect rect)
{
- ///
- /// Displays the difference in IDs between existing tiles and a set of NetTiles to the console
- ///
- /// X position at the top left of the rect
- /// Y position at the top left of the rect
- /// Width of the NetTile set
- /// Height of the NetTile set
- /// New tiles to be visualised
- public static void VisualiseTileSetDiff(int tileX, int tileY, int width, int height, NetTile[,] newTiles)
+ if (rect.Width != 1 || rect.Height != 1)
{
- if (TShock.Config.Settings.DebugLogs)
+ return false;
+ }
+
+ ITile oldTile = Main.tile[rect.X, rect.Y];
+ NetTile newTile = rect[0, 0];
+
+ if (oldTile.type == TileID.ChristmasTree && newTile.Type == TileID.ChristmasTree)
+ {
+ if (newTile.FrameX != 10)
{
- char pad = '0';
- for (int y = 0; y < height; y++)
- {
- int realY = y + tileY;
- for (int x = 0; x < width; x++)
- {
- int realX = x + tileX;
- ushort type = Main.tile[realX, realY].type;
- string type2 = type.ToString();
- Console.Write((type2.ToString()).PadLeft(3, pad) + (Main.tile[realX, realY].active() ? "a" : "-") + " ");
- }
- Console.Write(" -> ");
- for (int x = 0; x < width; x++)
- {
- int realX = x + tileX;
- ushort type = newTiles[x, y].Type;
- string type2 = type.ToString();
- Console.Write((type2.ToString()).PadLeft(3, pad) + (newTiles[x, y].Active ? "a" : "-") + " ");
- }
- Console.Write("\n");
- }
+ return false;
+ }
+
+ int obj_0 = (newTile.FrameY & 0b0000000000000111);
+ int obj_1 = (newTile.FrameY & 0b0000000000111000) >> 3;
+ int obj_2 = (newTile.FrameY & 0b0000001111000000) >> 6;
+ int obj_3 = (newTile.FrameY & 0b0011110000000000) >> 10;
+ int obj_x = (newTile.FrameY & 0b1100000000000000) >> 14;
+
+ if (obj_x != 0)
+ {
+ return false;
+ }
+
+ if (obj_0 is < 0 or > 4 || obj_1 is < 0 or > 6 || obj_2 is < 0 or > 11 || obj_3 is < 0 or > 11)
+ {
+ return false;
+ }
+
+ if (!player.HasBuildPermission(rect.X, rect.Y))
+ {
+ // for simplicity, let's pretend that the edit was valid, but do not execute it
+ return true;
+ }
+
+ Main.tile[rect.X, rect.Y].frameY = newTile.FrameY;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// This helper class allows simulating a `WorldGen.Convert` call and retrieving all valid changes for a given tile.
+ ///
+ internal static class WorldGenMock
+ {
+ ///
+ /// This is a mock tile which collects all possible changes the `WorldGen.Convert` code could make in its property setters.
+ ///
+ private sealed class MockTile
+ {
+ private readonly HashSet _setTypes;
+ private readonly HashSet _setWalls;
+
+ private ushort _type;
+ private ushort _wall;
+
+ public MockTile(ushort type, ushort wall, HashSet setTypes, HashSet setWalls)
+ {
+ _setTypes = setTypes;
+ _setWalls = setWalls;
+ _type = type;
+ _wall = wall;
+ }
+
+#pragma warning disable IDE1006
+
+ public ushort type
+ {
+ get => _type;
+ set
+ {
+ _setTypes.Add(value);
+ _type = value;
}
}
- ///
- /// Sends a tile rect at the given (tileX, tileY) coordinate, using the given set of NetTiles information to update the tile rect
- ///
- /// X position at the top left of the rect
- /// Y position at the top left of the rect
- /// Width of the NetTile set
- /// Height of the NetTile set
- /// New tiles to place in the rect
- /// Player to send the debug display to
- public static void DisplayTileSetInGame(short tileX, short tileY, byte width, byte height, NetTile[,] newTiles, TSPlayer player)
+ public ushort wall
{
- for (int x = 0; x < width; x++)
+ get => _wall;
+ set
{
- for (int y = 0; y < height; y++)
- {
- UpdateServerTileState(Main.tile[tileX + x, tileY + y], newTiles[x, y], TileDataType.All);
- }
- //Add a line of dirt blocks at the bottom for safety
- UpdateServerTileState(Main.tile[tileX + x, tileY + height], new NetTile { Active = true, Type = 0 }, TileDataType.All);
+ _setWalls.Add(value);
+ _wall = value;
}
+ }
- player.SendTileRect(tileX, tileY, width, height);
+#pragma warning restore IDE1006
+ }
+
+ ///
+ /// Simulates what would happen if `WorldGen.Convert` was called on the given coordinates and returns two sets with the possible tile type and wall types that the conversion could change the tile to.
+ ///
+ public static void SimulateConversionChange(int x, int y, out HashSet validTiles, out HashSet validWalls)
+ {
+ validTiles = new HashSet();
+ validWalls = new HashSet();
+
+ // all the conversion types used in the code, most apparent in Projectile ai 31
+ foreach (int conversionType in new int[] { 0, 1, 2, 3, 4, 5, 6, 7 })
+ {
+ MockTile mock = new(Main.tile[x, y].type, Main.tile[x, y].wall, validTiles, validWalls);
+ Convert(mock, x, y, conversionType);
+ }
+ }
+
+ /*
+ * This is a copy of the `WorldGen.Convert` method with the following precise changes:
+ * - Added a `MockTile tile` parameter
+ * - Changed the `i` and `j` parameters to `k` and `l`
+ * - Removed the size parameter
+ * - Removed the area loop and `Tile tile = Main.tile[k, l]` access in favor of using the tile parameter
+ * - Removed all calls to `WorldGen.SquareWallFrame`, `NetMessage.SendTileSquare`, `WorldGen.TryKillingTreesAboveIfTheyWouldBecomeInvalid`
+ * - Changed all `continue` statements to `break` statements
+ * - Removed the ifs checking the bounds of the tile and wall types
+ * - Removed branches that would call `WorldGen.KillTile`
+ * - Changed branches depending on randomness to instead set the property to both values after one another
+ *
+ * This overall leads to a method that can be called on a MockTile and real-world coordinates and will spit out the proper conversion changes into the MockTile.
+ */
+
+ private static void Convert(MockTile tile, int k, int l, int conversionType)
+ {
+ int type = tile.type;
+ int wall = tile.wall;
+ switch (conversionType)
+ {
+ case 4:
+ if (WallID.Sets.Conversion.Grass[wall] && wall != 81)
+ {
+ tile.wall = 81;
+ }
+ else if (WallID.Sets.Conversion.Stone[wall] && wall != 83)
+ {
+ tile.wall = 83;
+ }
+ else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 218)
+ {
+ tile.wall = 218;
+ }
+ else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 221)
+ {
+ tile.wall = 221;
+ }
+ else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 192)
+ {
+ tile.wall = 192;
+ }
+ else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 193)
+ {
+ tile.wall = 193;
+ }
+ else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 194)
+ {
+ tile.wall = 194;
+ }
+ else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 195)
+ {
+ tile.wall = 195;
+ }
+ if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type]) && type != 203)
+ {
+ tile.type = 203;
+ }
+ else if (TileID.Sets.Conversion.JungleGrass[type] && type != 662)
+ {
+ tile.type = 662;
+ }
+ else if (TileID.Sets.Conversion.Grass[type] && type != 199)
+ {
+ tile.type = 199;
+ }
+ else if (TileID.Sets.Conversion.Ice[type] && type != 200)
+ {
+ tile.type = 200;
+ }
+ else if (TileID.Sets.Conversion.Sand[type] && type != 234)
+ {
+ tile.type = 234;
+ }
+ else if (TileID.Sets.Conversion.HardenedSand[type] && type != 399)
+ {
+ tile.type = 399;
+ }
+ else if (TileID.Sets.Conversion.Sandstone[type] && type != 401)
+ {
+ tile.type = 401;
+ }
+ else if (TileID.Sets.Conversion.Thorn[type] && type != 352)
+ {
+ tile.type = 352;
+ }
+ break;
+ case 2:
+ if (WallID.Sets.Conversion.Grass[wall] && wall != 70)
+ {
+ tile.wall = 70;
+ }
+ else if (WallID.Sets.Conversion.Stone[wall] && wall != 28)
+ {
+ tile.wall = 28;
+ }
+ else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 219)
+ {
+ tile.wall = 219;
+ }
+ else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 222)
+ {
+ tile.wall = 222;
+ }
+ else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 200)
+ {
+ tile.wall = 200;
+ }
+ else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 201)
+ {
+ tile.wall = 201;
+ }
+ else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 202)
+ {
+ tile.wall = 202;
+ }
+ else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 203)
+ {
+ tile.wall = 203;
+ }
+ if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type]) && type != 117)
+ {
+ tile.type = 117;
+ }
+ else if (TileID.Sets.Conversion.GolfGrass[type] && type != 492)
+ {
+ tile.type = 492;
+ }
+ else if (TileID.Sets.Conversion.Grass[type] && type != 109 && type != 492)
+ {
+ tile.type = 109;
+ }
+ else if (TileID.Sets.Conversion.Ice[type] && type != 164)
+ {
+ tile.type = 164;
+ }
+ else if (TileID.Sets.Conversion.Sand[type] && type != 116)
+ {
+ tile.type = 116;
+ }
+ else if (TileID.Sets.Conversion.HardenedSand[type] && type != 402)
+ {
+ tile.type = 402;
+ }
+ else if (TileID.Sets.Conversion.Sandstone[type] && type != 403)
+ {
+ tile.type = 403;
+ }
+ if (type == 59 && (Main.tile[k - 1, l].type == 109 || Main.tile[k + 1, l].type == 109 || Main.tile[k, l - 1].type == 109 || Main.tile[k, l + 1].type == 109))
+ {
+ tile.type = 0;
+ }
+ break;
+ case 1:
+ if (WallID.Sets.Conversion.Grass[wall] && wall != 69)
+ {
+ tile.wall = 69;
+ }
+ else if (TileID.Sets.Conversion.JungleGrass[type] && type != 661)
+ {
+ tile.type = 661;
+ }
+ else if (WallID.Sets.Conversion.Stone[wall] && wall != 3)
+ {
+ tile.wall = 3;
+ }
+ else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 217)
+ {
+ tile.wall = 217;
+ }
+ else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 220)
+ {
+ tile.wall = 220;
+ }
+ else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 188)
+ {
+ tile.wall = 188;
+ }
+ else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 189)
+ {
+ tile.wall = 189;
+ }
+ else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 190)
+ {
+ tile.wall = 190;
+ }
+ else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 191)
+ {
+ tile.wall = 191;
+ }
+ if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type]) && type != 25)
+ {
+ tile.type = 25;
+ }
+ else if (TileID.Sets.Conversion.Grass[type] && type != 23)
+ {
+ tile.type = 23;
+ }
+ else if (TileID.Sets.Conversion.Ice[type] && type != 163)
+ {
+ tile.type = 163;
+ }
+ else if (TileID.Sets.Conversion.Sand[type] && type != 112)
+ {
+ tile.type = 112;
+ }
+ else if (TileID.Sets.Conversion.HardenedSand[type] && type != 398)
+ {
+ tile.type = 398;
+ }
+ else if (TileID.Sets.Conversion.Sandstone[type] && type != 400)
+ {
+ tile.type = 400;
+ }
+ else if (TileID.Sets.Conversion.Thorn[type] && type != 32)
+ {
+ tile.type = 32;
+ }
+ break;
+ case 3:
+ if (WallID.Sets.CanBeConvertedToGlowingMushroom[wall])
+ {
+ tile.wall = 80;
+ }
+ if (tile.type == 60)
+ {
+ tile.type = 70;
+ }
+ break;
+ case 5:
+ if ((WallID.Sets.Conversion.Stone[wall] || WallID.Sets.Conversion.NewWall1[wall] || WallID.Sets.Conversion.NewWall2[wall] || WallID.Sets.Conversion.NewWall3[wall] || WallID.Sets.Conversion.NewWall4[wall] || WallID.Sets.Conversion.Ice[wall] || WallID.Sets.Conversion.Sandstone[wall]) && wall != 187)
+ {
+ tile.wall = 187;
+ }
+ else if ((WallID.Sets.Conversion.HardenedSand[wall] || WallID.Sets.Conversion.Dirt[wall] || WallID.Sets.Conversion.Snow[wall]) && wall != 216)
+ {
+ tile.wall = 216;
+ }
+ if ((TileID.Sets.Conversion.Grass[type] || TileID.Sets.Conversion.Sand[type] || TileID.Sets.Conversion.Snow[type] || TileID.Sets.Conversion.Dirt[type]) && type != 53)
+ {
+ int num = 53;
+ if (WorldGen.BlockBelowMakesSandConvertIntoHardenedSand(k, l))
+ {
+ num = 397;
+ }
+ tile.type = (ushort)num;
+ }
+ else if (TileID.Sets.Conversion.HardenedSand[type] && type != 397)
+ {
+ tile.type = 397;
+ }
+ else if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type] || TileID.Sets.Conversion.Ice[type] || TileID.Sets.Conversion.Sandstone[type]) && type != 396)
+ {
+ tile.type = 396;
+ }
+ break;
+ case 6:
+ if ((WallID.Sets.Conversion.Stone[wall] || WallID.Sets.Conversion.NewWall1[wall] || WallID.Sets.Conversion.NewWall2[wall] || WallID.Sets.Conversion.NewWall3[wall] || WallID.Sets.Conversion.NewWall4[wall] || WallID.Sets.Conversion.Ice[wall] || WallID.Sets.Conversion.Sandstone[wall]) && wall != 71)
+ {
+ tile.wall = 71;
+ }
+ else if ((WallID.Sets.Conversion.HardenedSand[wall] || WallID.Sets.Conversion.Dirt[wall] || WallID.Sets.Conversion.Snow[wall]) && wall != 40)
+ {
+ tile.wall = 40;
+ }
+ if ((TileID.Sets.Conversion.Grass[type] || TileID.Sets.Conversion.Sand[type] || TileID.Sets.Conversion.HardenedSand[type] || TileID.Sets.Conversion.Snow[type] || TileID.Sets.Conversion.Dirt[type]) && type != 147)
+ {
+ tile.type = 147;
+ }
+ else if ((Main.tileMoss[type] || TileID.Sets.Conversion.Stone[type] || TileID.Sets.Conversion.Ice[type] || TileID.Sets.Conversion.Sandstone[type]) && type != 161)
+ {
+ tile.type = 161;
+ }
+ break;
+ case 7:
+ if ((WallID.Sets.Conversion.Stone[wall] || WallID.Sets.Conversion.Ice[wall] || WallID.Sets.Conversion.Sandstone[wall]) && wall != 1)
+ {
+ tile.wall = 1;
+ }
+ else if ((WallID.Sets.Conversion.HardenedSand[wall] || WallID.Sets.Conversion.Snow[wall] || WallID.Sets.Conversion.Dirt[wall]) && wall != 2)
+ {
+ tile.wall = 2;
+ }
+ else if (WallID.Sets.Conversion.NewWall1[wall] && wall != 196)
+ {
+ tile.wall = 196;
+ }
+ else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 197)
+ {
+ tile.wall = 197;
+ }
+ else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 198)
+ {
+ tile.wall = 198;
+ }
+ else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 199)
+ {
+ tile.wall = 199;
+ }
+ if ((TileID.Sets.Conversion.Stone[type] || TileID.Sets.Conversion.Ice[type] || TileID.Sets.Conversion.Sandstone[type]) && type != 1)
+ {
+ tile.type = 1;
+ }
+ else if (TileID.Sets.Conversion.GolfGrass[type] && type != 477)
+ {
+ tile.type = 477;
+ }
+ else if (TileID.Sets.Conversion.Grass[type] && type != 2 && type != 477)
+ {
+ tile.type = 2;
+ }
+ else if ((TileID.Sets.Conversion.Sand[type] || TileID.Sets.Conversion.HardenedSand[type] || TileID.Sets.Conversion.Snow[type] || TileID.Sets.Conversion.Dirt[type]) && type != 0)
+ {
+ int num2 = 0;
+ if (WorldGen.TileIsExposedToAir(k, l))
+ {
+ num2 = 2;
+ }
+ tile.type = (ushort)num2;
+ }
+ break;
+ }
+ if (tile.wall == 69 || tile.wall == 70 || tile.wall == 81)
+ {
+ if (l < Main.worldSurface)
+ {
+ tile.wall = 65;
+ tile.wall = 63;
+ }
+ else
+ {
+ tile.wall = 64;
+ }
+ }
+ else if (WallID.Sets.Conversion.Stone[wall] && wall != 1 && wall != 262 && wall != 274 && wall != 61 && wall != 185)
+ {
+ tile.wall = 1;
+ }
+ else if (WallID.Sets.Conversion.Stone[wall] && wall == 262)
+ {
+ tile.wall = 61;
+ }
+ else if (WallID.Sets.Conversion.Stone[wall] && wall == 274)
+ {
+ tile.wall = 185;
+ }
+ if (WallID.Sets.Conversion.NewWall1[wall] && wall != 212)
+ {
+ tile.wall = 212;
+ }
+ else if (WallID.Sets.Conversion.NewWall2[wall] && wall != 213)
+ {
+ tile.wall = 213;
+ }
+ else if (WallID.Sets.Conversion.NewWall3[wall] && wall != 214)
+ {
+ tile.wall = 214;
+ }
+ else if (WallID.Sets.Conversion.NewWall4[wall] && wall != 215)
+ {
+ tile.wall = 215;
+ }
+ else if (tile.wall == 80)
+ {
+ tile.wall = 15;
+ tile.wall = 64;
+ }
+ else if (WallID.Sets.Conversion.HardenedSand[wall] && wall != 216)
+ {
+ tile.wall = 216;
+ }
+ else if (WallID.Sets.Conversion.Sandstone[wall] && wall != 187)
+ {
+ tile.wall = 187;
+ }
+ if (tile.type == 492)
+ {
+ tile.type = 477;
+ }
+ else if (TileID.Sets.Conversion.JungleGrass[type] && type != 60)
+ {
+ tile.type = 60;
+ }
+ else if (TileID.Sets.Conversion.Grass[type] && type != 2 && type != 477)
+ {
+ tile.type = 2;
+ }
+ else if (TileID.Sets.Conversion.Stone[type] && type != 1)
+ {
+ tile.type = 1;
+ }
+ else if (TileID.Sets.Conversion.Sand[type] && type != 53)
+ {
+ tile.type = 53;
+ }
+ else if (TileID.Sets.Conversion.HardenedSand[type] && type != 397)
+ {
+ tile.type = 397;
+ }
+ else if (TileID.Sets.Conversion.Sandstone[type] && type != 396)
+ {
+ tile.type = 396;
+ }
+ else if (TileID.Sets.Conversion.Ice[type] && type != 161)
+ {
+ tile.type = 161;
+ }
+ else if (TileID.Sets.Conversion.MushroomGrass[type])
+ {
+ tile.type = 60;
}
}
}
diff --git a/TShockAPI/Hooks/AccountHooks.cs b/TShockAPI/Hooks/AccountHooks.cs
index 9c08b26d..ae9fff24 100644
--- a/TShockAPI/Hooks/AccountHooks.cs
+++ b/TShockAPI/Hooks/AccountHooks.cs
@@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+using System.ComponentModel;
using TShockAPI.DB;
namespace TShockAPI.Hooks
{
@@ -39,6 +40,31 @@ namespace TShockAPI.Hooks
}
}
+ public class AccountGroupUpdateEventArgs : HandledEventArgs
+ {
+ public string AccountName { get; private set; }
+ public Group Group { get; set; }
+
+ public AccountGroupUpdateEventArgs(string accountName, Group group)
+ {
+ this.AccountName = accountName;
+ this.Group = group;
+ }
+ }
+
+ public class AccountGroupUpdateByPlayerEventArgs : AccountGroupUpdateEventArgs
+ {
+ ///
+ /// The player who updated the user's group
+ ///
+ public TSPlayer Player { get; private set; }
+
+ public AccountGroupUpdateByPlayerEventArgs(TSPlayer player, string accountName, Group group) : base(accountName, group)
+ {
+ this.Player = player;
+ }
+ }
+
public class AccountHooks
{
public delegate void AccountCreateD(AccountCreateEventArgs e);
@@ -62,5 +88,25 @@ namespace TShockAPI.Hooks
AccountDelete(new AccountDeleteEventArgs(u));
}
+
+ public delegate void AccountGroupUpdateD(AccountGroupUpdateEventArgs e);
+ public static event AccountGroupUpdateD AccountGroupUpdate;
+
+ public static bool OnAccountGroupUpdate(UserAccount account, TSPlayer author, ref Group group)
+ {
+ AccountGroupUpdateEventArgs args = new AccountGroupUpdateByPlayerEventArgs(author, account.Name, group);
+ AccountGroupUpdate?.Invoke(args);
+ group = args.Group;
+
+ return args.Handled;
+ }
+ public static bool OnAccountGroupUpdate(UserAccount account, ref Group group)
+ {
+ AccountGroupUpdateEventArgs args = new AccountGroupUpdateEventArgs(account.Name, group);
+ AccountGroupUpdate?.Invoke(args);
+ group = args.Group;
+
+ return args.Handled;
+ }
}
}
diff --git a/TShockAPI/Hooks/PlayerHooks.cs b/TShockAPI/Hooks/PlayerHooks.cs
index 7a3e2067..43756464 100644
--- a/TShockAPI/Hooks/PlayerHooks.cs
+++ b/TShockAPI/Hooks/PlayerHooks.cs
@@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using TShockAPI.DB;
@@ -119,6 +120,49 @@ namespace TShockAPI.Hooks
public string CommandPrefix { get; set; }
}
+ ///
+ /// EventArgs used for the event.
+ ///
+ public class PrePlayerCommandEventArgs : HandledEventArgs
+ {
+ ///
+ /// The command entered by the player.
+ ///
+ public Command Command { get; }
+ ///
+ /// Command arguments.
+ ///
+ public CommandArgs Arguments { get; set; }
+
+ public PrePlayerCommandEventArgs(Command command, CommandArgs args)
+ {
+ Command = command;
+ Arguments = args;
+ }
+ }
+
+ ///
+ /// EventArgs used for the event.
+ ///
+ public class PostPlayerCommandEventArgs : HandledEventArgs
+ {
+ ///
+ /// The command entered by the player.
+ ///
+ public Command Command { get; }
+ ///
+ /// Command arguments.
+ ///
+ public CommandArgs Arguments { get; }
+
+ public PostPlayerCommandEventArgs(Command command, CommandArgs arguments, bool handled)
+ {
+ Command = command;
+ Arguments = arguments;
+ Handled = handled;
+ }
+ }
+
///
/// EventArgs used for the event.
///
@@ -343,6 +387,26 @@ namespace TShockAPI.Hooks
///
public static event PlayerCommandD PlayerCommand;
+ ///
+ /// The delegate of the event.
+ ///
+ /// The EventArgs for this event.
+ public delegate void PrePlayerCommandD(PrePlayerCommandEventArgs e);
+ ///
+ /// Fired before a command is run.
+ ///
+ public static event PrePlayerCommandD PrePlayerCommand;
+
+ ///
+ /// The delegate of the event.
+ ///
+ /// The EventArgs for this event.
+ public delegate void PostPlayerCommandD(PostPlayerCommandEventArgs e);
+ ///
+ /// Fired after a command is run.
+ ///
+ public static event PostPlayerCommandD PostPlayerCommand;
+
///
/// The delegate of the event.
///
@@ -449,6 +513,40 @@ namespace TShockAPI.Hooks
return playerCommandEventArgs.Handled;
}
+ ///
+ /// Fires the event.
+ ///
+ /// Command to be executed
+ /// Command arguments
+ /// True if the event has been handled.
+ public static bool OnPrePlayerCommand(Command cmd, ref CommandArgs arguments)
+ {
+ if (PrePlayerCommand == null)
+ return false;
+
+ PrePlayerCommandEventArgs args = new PrePlayerCommandEventArgs(cmd, arguments);
+
+ PrePlayerCommand(args);
+
+ arguments = args.Arguments;
+ return args.Handled;
+ }
+
+ ///
+ /// Fires the event.
+ ///
+ /// Executed command.
+ /// Command arguments.
+ /// Is the command executed.
+ public static void OnPostPlayerCommand(Command cmd, CommandArgs arguments, bool handled)
+ {
+ if (PostPlayerCommand == null)
+ return;
+
+ PostPlayerCommandEventArgs args = new PostPlayerCommandEventArgs(cmd, arguments, handled);
+ PostPlayerCommand(args);
+ }
+
///
/// Fires the event.
///
diff --git a/TShockAPI/I18n.cs b/TShockAPI/I18n.cs
index e8f5eedb..ac2985d0 100644
--- a/TShockAPI/I18n.cs
+++ b/TShockAPI/I18n.cs
@@ -60,6 +60,12 @@ namespace TShockAPI
}
}
+ if (LanguageManager.Instance.ActiveCulture == GameCulture.DefaultCulture)
+ {
+ var bf = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static;
+ // LanguageManager.SetLanguage will change this so we need to reset it back to null
+ typeof(CultureInfo).GetField("s_currentThreadUICulture", bf)?.SetValue(null, null);
+ }
return CultureInfo.CurrentUICulture;
}
}
diff --git a/TShockAPI/Localization/EnglishLanguage.cs b/TShockAPI/Localization/EnglishLanguage.cs
index 1ec4ecea..334c4780 100644
--- a/TShockAPI/Localization/EnglishLanguage.cs
+++ b/TShockAPI/Localization/EnglishLanguage.cs
@@ -20,7 +20,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Terraria;
+using Terraria.Initializers;
using Terraria.Localization;
+using Terraria.UI.Chat;
namespace TShockAPI.Localization
{
@@ -37,6 +39,8 @@ namespace TShockAPI.Localization
private static readonly Dictionary Buffs = new Dictionary();
+ private static readonly Dictionary VanillaCommandsPrefixs = new Dictionary();
+
internal static void Initialize()
{
var culture = Language.ActiveCulture;
@@ -71,6 +75,15 @@ namespace TShockAPI.Localization
var i = (int)field.GetValue(null);
Prefixs.Add(i, Lang.prefix[i].Value);
}
+
+ ChatInitializer.Load();
+ foreach (var command in ChatManager.Commands._localizedCommands)
+ {
+ if (VanillaCommandsPrefixs.ContainsKey(command.Value._name))
+ continue;
+ VanillaCommandsPrefixs.Add(command.Value._name,command.Key.Value);
+ }
+ ChatManager.Commands._localizedCommands.Clear();
}
finally
{
@@ -136,5 +149,18 @@ namespace TShockAPI.Localization
return null;
}
+
+ ///
+ /// Get vanilla command prefix in English
+ ///
+ /// vanilla command name
+ /// vanilla command prefix in English
+ public static string GetCommandPrefixByName(string name)
+ {
+ string commandText;
+ if (VanillaCommandsPrefixs.TryGetValue(name, out commandText))
+ return commandText;
+ return null;
+ }
}
}
diff --git a/TShockAPI/NetItem.cs b/TShockAPI/NetItem.cs
index 278fda1c..0f0dc6e3 100644
--- a/TShockAPI/NetItem.cs
+++ b/TShockAPI/NetItem.cs
@@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
- using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -155,13 +155,39 @@ namespace TShockAPI
/// The net ID.
/// The stack.
/// The prefix ID.
- public NetItem(int netId, int stack, byte prefixId)
+ public NetItem(int netId, int stack = 1, byte prefixId = 0)
{
_netId = netId;
_stack = stack;
_prefixId = prefixId;
}
+ ///
+ /// Creates a new .
+ ///
+ /// Item in the game.
+ public NetItem(Item item)
+ {
+ _netId = item.netID;
+ _stack = item.stack;
+ _prefixId = item.prefix;
+ }
+
+ ///
+ /// Creates based on data from this structure.
+ ///
+ /// A copy of the item.
+ public Item ToItem()
+ {
+ Item item = new Item();
+
+ item.netDefaults(_netId);
+ item.stack = _stack;
+ item.prefix = _prefixId;
+
+ return item;
+ }
+
///
/// Converts the to a string.
///
diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs
index d7ec7166..99ac5d7d 100644
--- a/TShockAPI/Permissions.cs
+++ b/TShockAPI/Permissions.cs
@@ -529,18 +529,9 @@ namespace TShockAPI
field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute;
var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : GetString("No description available.");
- var commands = GetCommands(name);
- foreach (var c in commands)
- {
- for (var i = 0; i < c.Names.Count; i++)
- {
- c.Names[i] = "/" + c.Names[i];
- }
- }
- var strs =
- commands.Select(
- c =>
- c.Name + (c.Names.Count > 1 ? " ({0})".SFormat(string.Join(" ", c.Names.ToArray(), 1, c.Names.Count - 1)) : ""));
+ var strs = GetCommands(name).Select(c => c.Names.Count > 1
+ ? $"/{c.Name} (/{string.Join(" /", c.Names.Skip(1))})"
+ : $"/{c.Name}");
sb.AppendLine($"## {name}");
sb.AppendLine($"{desc}");
diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs
index 58cc23d8..a6e681e2 100644
--- a/TShockAPI/Rest/Rest.cs
+++ b/TShockAPI/Rest/Rest.cs
@@ -351,7 +351,6 @@ namespace Rests
{
str = string.Format("{0}({1});", jsonp, str);
}
- e.Response.Connection.Type = ConnectionType.Close;
e.Response.ContentType = new ContentTypeHeader("application/json; charset=utf-8");
e.Response.Add(serverHeader);
var bytes = Encoding.UTF8.GetBytes(str);
diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs
index c41e7767..5dd2ff20 100644
--- a/TShockAPI/Rest/RestManager.cs
+++ b/TShockAPI/Rest/RestManager.cs
@@ -402,7 +402,7 @@ namespace TShockAPI
{"serverversion", Main.versionNumber},
{"tshockversion", TShock.VersionNum},
{"port", TShock.Config.Settings.ServerPort},
- {"playercount", Main.player.Where(p => null != p && p.active).Count()},
+ {"playercount", TShock.Utils.GetActivePlayerCount()},
{"maxplayers", TShock.Config.Settings.MaxSlots},
{"world", (TShock.Config.Settings.UseServerName ? TShock.Config.Settings.ServerName : Main.worldName)},
{"uptime", (DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime).ToString(@"d'.'hh':'mm':'ss")},
@@ -555,7 +555,8 @@ namespace TShockAPI
{
try
{
- TShock.UserAccounts.SetUserGroup(account, group);
+ TShock.UserAccounts.SetUserGroup(new TSRestPlayer(args.TokenData.Username, TShock.Groups.GetGroupByName(args.TokenData.UserGroupName)),
+ account, group);
response.Add("group-response", "Group updated successfully");
}
catch (Exception e)
@@ -944,8 +945,8 @@ namespace TShockAPI
[Token]
private object PlayerList(RestRequestArgs args)
{
- var activeplayers = Main.player.Where(p => null != p && p.active).ToList();
- return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } };
+ var activeplayers = TShock.Players.Where(p => null != p && p.Active).Select(p => p.Name);
+ return new RestObject() { { "players", string.Join(", ", activeplayers) } };
}
[Description("Fetches detailed user information on all connected users, and can be filtered by specifying a key value pair filter users where the key is a field and the value is a users field value.")]
diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs
index ef5c3c22..c9194c36 100644
--- a/TShockAPI/TSPlayer.cs
+++ b/TShockAPI/TSPlayer.cs
@@ -34,6 +34,7 @@ using TShockAPI.Hooks;
using TShockAPI.Net;
using Timer = System.Timers.Timer;
using System.Linq;
+using Terraria.GameContent.Creative;
namespace TShockAPI
{
@@ -935,9 +936,15 @@ namespace TShockAPI
public bool LoginHarassed = false;
///
- /// Player cant die, unless onehit
+ /// Controls the journey godmode
///
- public bool GodMode = false;
+ public bool GodMode
+ {
+ get =>
+ CreativePowerManager.Instance.GetPower().IsEnabledForPlayer(Index);
+ set =>
+ CreativePowerManager.Instance.GetPower().SetEnabledState(Index, value);
+ }
///
/// Players controls are inverted if using SSC
@@ -981,7 +988,7 @@ namespace TShockAPI
get
{
return RealPlayer
- && (Netplay.Clients[Index] != null && Netplay.Clients[Index].IsActive && !Netplay.Clients[Index].PendingTermination);
+ && (Client != null && Client.IsActive && !Client.PendingTermination);
}
}
@@ -998,8 +1005,8 @@ namespace TShockAPI
///
public int State
{
- get { return Netplay.Clients[Index].State; }
- set { Netplay.Clients[Index].State = value; }
+ get { return Client.State; }
+ set { Client.State = value; }
}
///
@@ -1007,7 +1014,7 @@ namespace TShockAPI
///
public string UUID
{
- get { return RealPlayer ? Netplay.Clients[Index].ClientUUID : ""; }
+ get { return RealPlayer ? Client.ClientUUID : ""; }
}
///
@@ -1019,8 +1026,8 @@ namespace TShockAPI
{
if (string.IsNullOrEmpty(CacheIP))
return
- CacheIP = RealPlayer ? (Netplay.Clients[Index].Socket.IsConnected()
- ? TShock.Utils.GetRealIP(Netplay.Clients[Index].Socket.GetRemoteAddress().ToString())
+ CacheIP = RealPlayer ? (Client.Socket.IsConnected()
+ ? TShock.Utils.GetRealIP(Client.Socket.GetRemoteAddress().ToString())
: "")
: "127.0.0.1";
else
@@ -1103,6 +1110,11 @@ namespace TShockAPI
}
+ ///
+ /// Player RemoteClient.
+ ///
+ public RemoteClient Client => Netplay.Clients[Index];
+
///
/// Gets the Terraria Player object associated with the player.
///
@@ -1135,6 +1147,11 @@ namespace TShockAPI
get { return TPlayer.team; }
}
+ ///
+ /// Gets PvP player mode.
+ ///
+ public bool Hostile => TPlayer.hostile;
+
///
/// Gets the player's X coordinate.
///
@@ -1487,6 +1504,41 @@ namespace TShockAPI
return false;
}
+ ///
+ /// Changes the values of the array.
+ ///
+ /// The area of the sections you want to set a value to.
+ /// The minimum size should be set to 200x150. If null, then the entire map is specified.
+ /// Is the section loaded.
+ // The server does not send the player the whole world, it sends it in sections. To do this, it sets up visible and invisible sections.
+ // If the player was not in any section(Client.TileSections[x, y] == false) then the server will send the missing section of the world.
+ // This method allows you to simulate what the player has or has not seen these sections.
+ // For example, we can put some number of earths blocks in some vast area, for example, for the whole world, but the player will not see the changes, because some section is already loaded for him. At this point this method can come into effect! With it we will be able to select some zone and make it both visible and invisible to the player.
+ // The server will assume that the zone is not loaded on the player, and will resend the data, but with earth blocks.
+ public void UpdateSection(Rectangle? rectangle = null, bool isLoaded = false)
+ {
+ if (rectangle.HasValue)
+ {
+ for (int i = Netplay.GetSectionX(rectangle.Value.X); i < Netplay.GetSectionX(rectangle.Value.X + rectangle.Value.Width) && i < Main.maxSectionsX; i++)
+ {
+ for (int j = Netplay.GetSectionY(rectangle.Value.Y); j < Netplay.GetSectionY(rectangle.Value.Y + rectangle.Value.Height) && j < Main.maxSectionsY; j++)
+ {
+ Client.TileSections[i, j] = isLoaded;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Main.maxSectionsX; i++)
+ {
+ for (int j = 0; j < Main.maxSectionsY; j++)
+ {
+ Client.TileSections[i, j] = isLoaded;
+ }
+ }
+ }
+ }
+
///
/// Gives an item to the player. Includes banned item spawn prevention to check if the player can spawn the item.
///
@@ -1519,6 +1571,15 @@ namespace TShockAPI
GiveItemByDrop(type, stack, prefix);
}
+ ///
+ /// Gives an item to the player.
+ ///
+ /// Item with data to be given to the player.
+ public virtual void GiveItem(NetItem item)
+ {
+ GiveItem(item.NetId, item.Stack, item.PrefixId);
+ }
+
private Item EmptySentinelItem = new Item();
private bool Depleted(Item item)
@@ -1815,7 +1876,17 @@ namespace TShockAPI
/// The amount of damage the player will take.
public virtual void DamagePlayer(int damage)
{
- NetMessage.SendPlayerHurt(Index, PlayerDeathReason.LegacyDefault(), damage, (new Random()).Next(-1, 1), false, false, 0, -1, -1);
+ DamagePlayer(damage, PlayerDeathReason.LegacyDefault());
+ }
+
+ ///
+ /// Wounds the player with the given damage.
+ ///
+ /// The amount of damage the player will take.
+ /// The reason for causing damage to player.
+ public virtual void DamagePlayer(int damage, PlayerDeathReason reason)
+ {
+ NetMessage.SendPlayerHurt(Index, reason, damage, (new Random()).Next(-1, 1), false, false, 0, -1, -1);
}
///
@@ -1823,7 +1894,16 @@ namespace TShockAPI
///
public virtual void KillPlayer()
{
- NetMessage.SendPlayerDeath(Index, PlayerDeathReason.LegacyDefault(), 99999, (new Random()).Next(-1, 1), false, -1, -1);
+ KillPlayer(PlayerDeathReason.LegacyDefault());
+ }
+
+ ///
+ /// Kills the player.
+ ///
+ /// Reason for killing a player.
+ public virtual void KillPlayer(PlayerDeathReason reason)
+ {
+ NetMessage.SendPlayerDeath(Index, reason, 99999, (new Random()).Next(-1, 1), false, -1, -1);
}
///
@@ -1832,6 +1912,8 @@ namespace TShockAPI
/// The team color index.
public virtual void SetTeam(int team)
{
+ if (team < 0 || team >= Main.teamColor.Length)
+ throw new ArgumentException("The player's team is not in the range of available.");
Main.player[Index].team = team;
NetMessage.SendData((int)PacketTypes.PlayerTeam, -1, -1, NetworkText.Empty, Index);
}
@@ -1840,6 +1922,7 @@ namespace TShockAPI
/// Sets the player's pvp.
///
/// The state of the pvp mode.
+ /// Whether a chat message about the change should be sent.
public virtual void SetPvP(bool mode, bool withMsg = false)
{
Main.player[Index].hostile = mode;
@@ -2064,7 +2147,7 @@ namespace TShockAPI
if (!RealPlayer || !ConnectionAlive)
return;
- Netplay.Clients[Index].Socket.AsyncSend(data, 0, data.Length, Netplay.Clients[Index].ServerWriteCallBack);
+ Client.Socket.AsyncSend(data, 0, data.Length, Client.ServerWriteCallBack);
}
///
diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs
index 7a5a38af..365c531d 100644
--- a/TShockAPI/TShock.cs
+++ b/TShockAPI/TShock.cs
@@ -63,7 +63,7 @@ namespace TShockAPI
/// VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
/// VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.
- public static readonly string VersionCodename = "Thank you, everyone, for your support of TShock all these years! <3";
+ public static readonly string VersionCodename = "Intensity";
/// SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).
public static string SavePath = "tshock";
@@ -428,6 +428,8 @@ namespace TShockAPI
Hooks.AccountHooks.AccountDelete += OnAccountDelete;
Hooks.AccountHooks.AccountCreate += OnAccountCreate;
+ On.Terraria.RemoteClient.Reset += RemoteClient_Reset;
+
GetDataHandlers.InitGetDataHandler();
Commands.InitCommands();
@@ -496,6 +498,12 @@ namespace TShockAPI
}
}
+ private static void RemoteClient_Reset(On.Terraria.RemoteClient.orig_Reset orig, RemoteClient client)
+ {
+ client.ClientUUID = null;
+ orig(client);
+ }
+
private static void OnAchievementInitializerLoad(ILContext il)
{
// Modify AchievementInitializer.Load to remove the Main.netMode == 2 check (occupies the first 4 IL instructions)
@@ -676,17 +684,13 @@ namespace TShockAPI
if (args.Chest != null)
{
+ // After checking for protected regions, no further range checking is necessarily because the client packet only specifies the
+ // inventory slot to quick stack. The vanilla Terraria server itself determines what chests are close enough to the player.
if (Config.Settings.RegionProtectChests && !Regions.CanBuild((int)args.WorldPosition.X, (int)args.WorldPosition.Y, tsplr))
{
args.Handled = true;
return;
}
-
- if (!tsplr.IsInRange(args.Chest.x, args.Chest.y))
- {
- args.Handled = true;
- return;
- }
}
}
@@ -1501,11 +1505,11 @@ namespace TShockAPI
{
if (!String.IsNullOrEmpty(text))
{
- text = item.Key.Value + ' ' + text;
+ text = EnglishLanguage.GetCommandPrefixByName(item.Value._name) + ' ' + text;
}
else
{
- text = item.Key.Value;
+ text = EnglishLanguage.GetCommandPrefixByName(item.Value._name);
}
break;
}
diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index 7bc42215..34df3c74 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -18,11 +18,11 @@
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)
-->
- 5.1.3
+ 5.2.1
TShock for Terraria
Pryaxis & TShock Contributors
TShockAPI
- Copyright © Pryaxis & TShock Contributors 2011-2022
+ Copyright © Pryaxis & TShock Contributors 2011-2023
True
GPL-3.0-or-later
@@ -34,7 +34,7 @@
-
+
diff --git a/TShockAPI/TextLog.cs b/TShockAPI/TextLog.cs
index 070c6c88..ae3bb29e 100644
--- a/TShockAPI/TextLog.cs
+++ b/TShockAPI/TextLog.cs
@@ -29,7 +29,8 @@ namespace TShockAPI
///
public class TextLog : ILog, IDisposable
{
- private readonly StreamWriter _logWriter;
+ private readonly bool ClearFile;
+ private StreamWriter _logWriter;
///
/// File name of the Text log
@@ -44,7 +45,7 @@ namespace TShockAPI
public TextLog(string filename, bool clear)
{
FileName = filename;
- _logWriter = new StreamWriter(filename, !clear);
+ ClearFile = clear;
}
public bool MayWriteType(TraceLevel type)
@@ -247,6 +248,10 @@ namespace TShockAPI
{
if (!MayWriteType(level))
return;
+ if (_logWriter is null)
+ {
+ _logWriter = new StreamWriter(FileName, !ClearFile);
+ }
var caller = "TShock";
@@ -278,7 +283,10 @@ namespace TShockAPI
public void Dispose()
{
- _logWriter.Dispose();
+ if (_logWriter != null)
+ {
+ _logWriter.Dispose();
+ }
}
}
}
diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs
index efe17130..3b9c0286 100644
--- a/TShockAPI/Utils.cs
+++ b/TShockAPI/Utils.cs
@@ -172,7 +172,7 @@ namespace TShockAPI
foreach (TSPlayer player in TShock.Players)
{
if (player != null && player != excludedPlayer && player.Active && player.HasPermission(Permissions.logs) &&
- player.DisplayLogs && TShock.Config.Settings.DisableSpewLogs == false)
+ player.DisplayLogs && !TShock.Config.Settings.DisableSpewLogs)
player.SendMessage(log, color);
}
}
@@ -183,7 +183,7 @@ namespace TShockAPI
/// The number of active players on the server.
public int GetActivePlayerCount()
{
- return Main.player.Where(p => null != p && p.active).Count();
+ return TShock.Players.Count(p => null != p && p.Active);
}
//Random should not be generated in a method
@@ -330,7 +330,7 @@ namespace TShockAPI
/// The item represented by the tag.
public Item GetItemFromTag(string tag)
{
- Regex regex = new Regex(@"\[i(tem)?(?:\/s(?\d{1,3}))?(?:\/p(?\d{1,3}))?:(?-?\d{1,4})\]");
+ Regex regex = new Regex(@"\[i(tem)?(?:\/s(?\d{1,4}))?(?:\/p(?\d{1,3}))?:(?-?\d{1,4})\]");
Match match = regex.Match(tag);
if (!match.Success)
return null;
@@ -1149,11 +1149,15 @@ namespace TShockAPI
/// If the server is empty; determines if we should use Utils.GetActivePlayerCount() for player count or 0.
internal void SetConsoleTitle(bool empty)
{
+ if (ShouldSkipTitle)
+ return;
Console.Title = GetString("{0}{1}/{2} on {3} @ {4}:{5} (TShock for Terraria v{6})",
!string.IsNullOrWhiteSpace(TShock.Config.Settings.ServerName) ? TShock.Config.Settings.ServerName + " - " : "",
empty ? 0 : GetActivePlayerCount(),
TShock.Config.Settings.MaxSlots, Main.worldName, Netplay.ServerIP.ToString(), Netplay.ListenPort, TShock.VersionNum);
}
+ // Some terminals doesn't supports XTerm escape sequences for setting the title
+ private static bool ShouldSkipTitle = !System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) && !(Environment.GetEnvironmentVariable("TERM")?.Contains("xterm") ?? false);
/// Determines the distance between two vectors.
/// The first vector location.
diff --git a/TShockInstaller/TShockInstaller.csproj b/TShockInstaller/TShockInstaller.csproj
index 9b73d66a..ac3bfff4 100644
--- a/TShockInstaller/TShockInstaller.csproj
+++ b/TShockInstaller/TShockInstaller.csproj
@@ -12,6 +12,6 @@
-
+
diff --git a/TShockLauncher/TShockLauncher.csproj b/TShockLauncher/TShockLauncher.csproj
index e3c4ac32..fbe428bb 100644
--- a/TShockLauncher/TShockLauncher.csproj
+++ b/TShockLauncher/TShockLauncher.csproj
@@ -30,7 +30,7 @@
-
+
diff --git a/TShockPluginManager/Nuget.cs b/TShockPluginManager/Nuget.cs
index 9fdefb6b..1d00fe1c 100644
--- a/TShockPluginManager/Nuget.cs
+++ b/TShockPluginManager/Nuget.cs
@@ -30,7 +30,6 @@ using NuGet.Versioning;
namespace TShockPluginManager
{
-
public class Nugetter
{
// this object can figure out the right framework folders to use from a set of packages
@@ -82,15 +81,13 @@ namespace TShockPluginManager
// make sure the source repository can actually tell us about dependencies
var dependencyInfoResource = await sourceRepository.GetResourceAsync();
// get the try and dependencies
- // (the above function returns a nullable value, but doesn't properly indicate it as such)
- #pragma warning disable CS8602
- var dependencyInfo = await dependencyInfoResource?.ResolvePackage(
+ if (dependencyInfoResource is null) continue;
+ var dependencyInfo = await dependencyInfoResource.ResolvePackage(
package, framework, cacheContext, logger, CancellationToken.None);
- #pragma warning restore CS8602
// oop, we don't have the ability to get dependency info from this repository, or
// it wasn't found. let's try the next source repository!
- if (dependencyInfo == null) continue;
+ if (dependencyInfo is null) continue;
availablePackages.Add(dependencyInfo);
foreach (var dependency in dependencyInfo.Dependencies)
@@ -302,8 +299,11 @@ namespace TShockPluginManager
var relativeFolder = Path.GetDirectoryName(packageRelativeFilePath);
var targetFolder = Path.Join(isPlugin ? "./ServerPlugins" : "./bin", relativeFolder);
- Directory.CreateDirectory(targetFolder);
- File.Copy(filePath, Path.Join(targetFolder, Path.GetFileName(filePath)), true);
+ if (File.Exists(filePath))
+ {
+ Directory.CreateDirectory(targetFolder);
+ File.Copy(filePath, Path.Join(targetFolder, Path.GetFileName(filePath)), true);
+ }
}
}
}
diff --git a/TShockPluginManager/TShockPluginManager.csproj b/TShockPluginManager/TShockPluginManager.csproj
index 33c503fd..1d53f320 100644
--- a/TShockPluginManager/TShockPluginManager.csproj
+++ b/TShockPluginManager/TShockPluginManager.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/TerrariaServerAPI b/TerrariaServerAPI
index e0e2df24..d4bb7e3a 160000
--- a/TerrariaServerAPI
+++ b/TerrariaServerAPI
@@ -1 +1 @@
-Subproject commit e0e2df24dbc618683cc75595c59bf7f88acada08
+Subproject commit d4bb7e3a21e875cfeb23bcf5cf847c85d9470ccf
diff --git a/docs/changelog.md b/docs/changelog.md
index 9295ece7..490a6b0a 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -78,7 +78,40 @@ Use past tense when adding new entries; sign your name off when you add or chang
* 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
-* An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team.(@CelestialAnarchy, #2617, @ATFGK)
+* Fixed `/dump-reference-data` mutate the command names. (#2943, @sgkoishi)
+* Added `ParryDamageBuff` (Striking Moment with Brand of the Inferno and shield) for player, updated `CursedInferno` buff for NPC (@sgkoishi, #3005)
+* Changed the use of `Player.active` to `TSPlayer.Active` for consistency. (@sgkoishi, #2939)
+* Fix typo in config for IP bans. (@redchess64)
+* Updated `TShockAPI.NetItem` (@AgaSpace):
+ * Added constructor overload with parameter `Terraria.Item`.
+ * Added the `ToItem` method to get a copy of `Terraria.Item`.
+ * In the constructor `stack` and `prefix` are now optional parameters.
+* Fixed unable to transfer long response body for REST API. (@sgkoishi, #2925)
+* Fixed the `/wind` command not being very helpful. (@punchready)
+* Fixed /help, /me, and /p commands can't work in non-English languages. (@ACaiCat)
+* Added a hook `AccountHooks.AccountGroupUpdate`, which is called when you change the user group. (@AgaSpace)
+* * Ensured `TSPlayer.PlayerData` is non-null whilst syncing loadouts. (@drunderscore)
+* * Detected invalid installations, by checking for a file named `TerrariaServer.exe`. (@drunderscore)
+ * This made the two most common installation mistakes (extracting into the Terraria client directory, and extracting TShock 5 or newer into a TShock 4 or older install) prompt the user with a more useful diagnostic, rather than (likely) crashing moments later.
+
+## TShock 5.2.1
+* Updated `TSPlayer.GodMode`. (@AgaSpace)
+ * Previously the field was used as some kind of dataset changed by /godmode command, but now it is a property that receives/changes data in journey mode.
+* Added the `TSPlayer.Client` property. It allows the developer to get the `RemoteClient` player, without an additional call to `Terraria.Netplay.Clients`. (@AgaSpace)
+* Updated the documentation for the `TSPlayer.SetPvP` method. The `sendMsg` parameter, which is responsible for sending a pvp mode change message, was not documented earlier. (@AgaSpace)
+* Added methods `TSPlayer.KillPlayer` and `TSPlayer.DamagePlayer` for which you can specify the cause (`PlayerDeathReason`) in parameters. (@AgaSpace)
+* Added an error when trying to change a `TSPlayer` team to, say, 9, when there are only 6. (@AgaSpace)
+* Added an error when trying to call the `TSPlayer.SetTeam` method with an argument (team) greater than 5 or less than 0. (@AgaSpace)
+* Added a method `TSPlayer.UpdateSection` with arguments `rectangle` and `isLoaded`, which will load some area from the server to the player. (@AgaSpace)
+* Added a method `TSPlayer.GiveItem`, which has `TShockAPI.NetItem` structure in its arguments. (@AgaSpace)
+* Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@AgaSpace)
+* Fixed bug where when the `UseSqlLogs` config property is true, an empty log file would still get created. (@ZakFahey)
+* Fixed typo in `/gbuff`. (@sgkoishi, #2955)
+* Added `PlayerHooks.PrePlayerCommand` hook, which fired before command execution. (@AgaSpace)
+* Added `PlayerHooks.PostPlayerCommand` hook, which fired after command execution. (@AgaSpace)
+
+## TShock 5.2
+* An additional option `pvpwithnoteam` is added at `PvPMode` to enable PVP with no team. (@CelestialAnarchy, #2617, @ATFGK)
* Corrected and updated deserialization of the following packets (@ATFGK):
* `ProjectileNew`: Read the third `AI` value.
* Before this change, it was previously possible for the projectile damage limit to falsely trigger, such as when using the Terra Balde and Fire Gauntlet together.
@@ -105,8 +138,10 @@ Use past tense when adding new entries; sign your name off when you add or chang
* Initialized achievements and the `AchievementManager` on the server. This ensures that players cannot cause exceptions to be thrown, chat messages are always logged, and allows achievement names to be localized in the console. Also added a test case for this. (@drunderscore)
* Allowed multiple test cases to be in TShock's test suite. (@drunderscore)
* Fixed unable to use Purification/Evil Powder in jungle. (@sgkoishi)
-* Detected invalid installations, by checking for a file named `TerrariaServer.exe`. (@drunderscore)
- * This made the two most common installation mistakes (extracting into the Terraria client directory, and extracting TShock 5 or newer into a TShock 4 or older install) prompt the user with a more useful diagnostic, rather than (likely) crashing moments later.
+* Set the `GetDataHandledEventArgs.Player` property for the `SyncTilePicking` data handler. (@drunderscore)
+* Relaxed custom death message restrictions to allow Inferno potions in PvP. (@drunderscore)
+* Allowed Flower Boots to place Ash Flowers on Ash Grass blocks. (@punchready)
+* Removed unnecessary range check that artifically shortened quick stack reach. (@boddyn, #2885, @bcat)
## TShock 5.1.3
* Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef)
diff --git a/docs/docker.md b/docs/docker.md
index 0b89681b..afc4bdfc 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -14,32 +14,27 @@ Open ports can also be passed through using `-p :`.
For Example:
```bash
-# Building the image
-docker build -t tshock:linux-amd64 --build-arg TARGETPLATFORM=linux/amd64 .
-
-# Running the image
docker run -p 7777:7777 -p 7878:7878 \
-v /home/cider/tshock/:/tshock \
-v /home/cider/.local/share/Terraria/Worlds:/worlds \
-v /home/cider/tshock/plugins:/plugins \
- --rm -it tshock:linux-amd64 \
+ --rm -it ghcr.io/pryaxis/tshock:latest \
-world /worlds/backflip.wld -motd "OMFG DOCKER"
```
-## Building for Other Platforms
+## Building custom images
-Using `docker buildx`, you could build [multi-platform images](https://docs.docker.com/build/building/multi-platform/) for TShock.
+Occasionally, it may be necessary to adjust TShock with customizations that are not included in the upstream project.
+Therefore, these changes are also not available in the officially provided Docker images.
+
+To build and load a Docker image from your local checkout, use the following `buildx` command:
-For Example:
```bash
-# Building the image using buildx and loading it into docker
-sudo docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --load .
-
-# Running the image
-docker run -p 7777:7777 -p 7878:7878 \
- -v /home/cider/tshock/:/tshock \
- -v /home/cider/.local/share/Terraria/Worlds:/worlds \
- -v /home/cider/tshock/plugins:/plugins \
- --rm -it tshock:linux-arm64 \
- -world /worlds/backflip.wld -motd "ARM64 ftw"
+docker buildx build -t tshock:latest --load .
+```
+
+It is also possible to build [multi-platform images](https://docs.docker.com/build/building/multi-platform/) for TShock (e.g. an image targeting `arm64`, on a host that is not `arm64`):
+
+```bash
+docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --load .
```
diff --git a/docs/index.html b/docs/index.html
index 2ae1df82..0cfccf11 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -11,12 +11,7 @@
diff --git a/i18n/es_ES/TShockAPI.po b/i18n/es_ES/TShockAPI.po
index 5898f6f1..3fc0368f 100644
--- a/i18n/es_ES/TShockAPI.po
+++ b/i18n/es_ES/TShockAPI.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: tshock\n"
"POT-Creation-Date: 2022-12-06 05:43:49+0000\n"
-"PO-Revision-Date: 2022-12-06 05:52\n"
+"PO-Revision-Date: 2023-01-18 17:58\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
@@ -38,19 +38,19 @@ msgstr "#{0} - Estás baneado: {1}"
#: ../../TShockAPI/Commands.cs:6499
msgid " 'basic', 'sakura', 'willow', 'boreal', 'mahogany', 'ebonwood', 'shadewood', 'pearlwood'."
-msgstr " 'básico', 'cerezo', 'sauce', 'boreal', 'caoba', 'madera de ébano', 'madera oscura', 'madera de perla'."
+msgstr " 'basic', 'sakura', 'willow', 'boreal', 'mahogany', 'ebonwood', 'shadewood', 'pearlwood'."
#: ../../TShockAPI/Commands.cs:6505
msgid " 'cactus', 'herb', 'mushroom'."
-msgstr " 'cactus', 'hierba', 'hongo'."
+msgstr " 'cactus', 'herb', 'mushroom'."
#: ../../TShockAPI/Commands.cs:6501
msgid " 'palm', 'corruptpalm', 'crimsonpalm', 'hallowpalm'."
-msgstr " 'palma', 'palma corrupta, 'palma carmesí', 'palma santa'."
+msgstr " 'palm', 'corruptpalm', 'crimsonpalm', 'hallowpalm'."
#: ../../TShockAPI/Commands.cs:6503
msgid " 'topaz', 'amethyst', 'sapphire', 'emerald', 'ruby', 'diamond', 'amber'."
-msgstr " 'topacio', 'amatista', 'zafiro', 'esmeralda', 'rubí', 'diamante', 'ámbar'."
+msgstr " 'topaz', 'amethyst', 'sapphire', 'emerald', 'ruby', 'diamond', 'amber'."
#: ../../TShockAPI/Commands.cs:1446
#, csharp-format
@@ -60,17 +60,17 @@ msgstr " {0}{1} \"{2}\" (Encuentre la IP asociada con la cuenta del objetivo d
#: ../../TShockAPI/Commands.cs:1444
#, csharp-format
msgid " {0}{1} \"{2}{3}\" {4} {5} (Permanently bans this account name)"
-msgstr " {0}{1} \"{2}{3}\" {4} {5} (Prohíbe permanentemente este nombre de cuenta)"
+msgstr " {0}{1} \"{2}{3}\" {4} {5} (Banea permanentemente este nombre de cuenta)"
#: ../../TShockAPI/Commands.cs:1449
#, csharp-format
msgid " {0}{1} {2} (Find the player index for the target)"
-msgstr " {0}{1} {2} (Buscar el índice del jugador para el objetivo)"
+msgstr " {0}{1} {2} (Buscar el índice de jugador para el objetivo)"
#: ../../TShockAPI/Commands.cs:1450
#, csharp-format
msgid " {0}{1} {2}{3} {4} {5} (Permanently bans the online player by Account, UUID, and IP)"
-msgstr " {0}{1} {2}{3} {4} {5} (Banea permanentemente al jugador conectado por cuenta, UUID o IP)"
+msgstr " {0}{1} {2}{3} {4} {5} (Banea permanentemente al jugador conectado por cuenta, UUID, e IP)"
#: ../../TShockAPI/Commands.cs:1447
#, csharp-format
@@ -99,7 +99,7 @@ msgstr " A menos que se pase {0} al comando, se asume que {1} es un jugador o
#: ../../TShockAPI/Commands.cs:1262
#, csharp-format
msgid " -> Logged-in as: {0}; in group {1}."
-msgstr "-> Sesión iniciada como: {0}; en grupo {1}."
+msgstr " -> Sesión iniciada como: {0}; en grupo {1}."
#: ../../TShockAPI/Commands.cs:1398
#, csharp-format
@@ -131,7 +131,7 @@ msgstr "- Banea a un jugador desconectado por su dirección IP"
#: ../../TShockAPI/Commands.cs:1448
msgid "- Ban an online player by index (Useful for hard to type names)"
-msgstr "- Banea a un jugador conectado por índice (útil para nombres difíciles de escribir)"
+msgstr "- Banea a un jugador conectado por índice (Útil para nombres difíciles de escribir)"
#: ../../TShockAPI/Commands.cs:6498
msgid "- Default trees :"
@@ -143,7 +143,7 @@ msgstr "- Árboles de gemas :"
#: ../../TShockAPI/Commands.cs:1406
msgid "- Lists active bans. Color trends towards green as the ban approaches expiration"
-msgstr "- Enumera las prohibiciones activas. El color tiende al verde a medida que la prohibición se aproxima a su vencimiento"
+msgstr "- Enumera los baneos activos. El color tiende al verde a medida que el ban se aproxima a su vencimiento"
#: ../../TShockAPI/Commands.cs:6504
msgid "- Misc :"
@@ -155,11 +155,11 @@ msgstr "- Palmeras :"
#: ../../TShockAPI/TShock.cs:963
msgid "!!! > Set DisableLoginBeforeJoin to true in the config file and /reload if this is a problem."
-msgstr "!!! > En el archivo config marca DisableLoginBeforeJoin como true y usa /reload si esto es un problema."
+msgstr "!!! > En el archivo de configuración marca DisableLoginBeforeJoin como true y usa /reload si esto es un problema."
#: ../../TShockAPI/TShock.cs:957
msgid "!!! > Set DisableUUIDLogin to true in the config file and /reload if this is a problem."
-msgstr "!!! > En el archivo config marca DisableUUIDLogin como true y usa /reload si esto es un problema."
+msgstr "!!! > En el archivo de configuración marca DisableUUIDLogin como true y usa /reload si esto es un problema."
#: ../../TShockAPI/TShock.cs:962
msgid "!!! Login before join is enabled. Existing accounts can login & the server password will be bypassed."
@@ -181,7 +181,7 @@ msgstr "¡\"{0}\" no es un ID de buff válido!"
#: ../../TShockAPI/Commands.cs:5906
#, csharp-format
msgid "\"{0}\" is not a valid clear option."
-msgstr "\"{0}\" no es una opción clara válida."
+msgstr "\"{0}\" no es una opción de eliminación válida."
#: ../../TShockAPI/Commands.cs:6026
#, csharp-format
@@ -196,16 +196,16 @@ msgstr "\"{0}\" no es un número de página válido."
#: ../../TShockAPI/Commands.cs:5826
#, csharp-format
msgid "\"{0}\" is not a valid radius."
-msgstr "\"{0}\" no es un valor válido."
+msgstr "\"{0}\" no es un radio válido."
#: ../../TShockAPI/Rest/SecureRest.cs:213
#, csharp-format
msgid "\"{0}\" requested REST endpoint: {1}"
-msgstr "\"{0}\" solicitó el parámetro REST: {1}"
+msgstr "\"{0}\" solicitó el punto final REST: {1}"
#: ../../TShockAPI/Commands.cs:2020
msgid "(Server Broadcast) "
-msgstr "(Mensaje del Servidor) "
+msgstr "(Anuncio de Servidor) "
#: ../../TShockAPI/Configuration/TShockConfig.cs:496
msgid "(Super Admin) "
@@ -229,17 +229,17 @@ msgstr "{0} ({1}) ha cambiado la contraseña de la cuenta {2}."
#: ../../TShockAPI/Commands.cs:986
#, csharp-format
msgid "{0} ({1}) failed to change the password for account {2}."
-msgstr "{0} ({1}) falló al cambiar la contraseña para la cuenta {2}."
+msgstr "{0} ({1}) no pudo cambiar la contraseña de la cuenta {2}."
#: ../../TShockAPI/TShock.cs:1659
#, csharp-format
msgid "{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})"
-msgstr "{0} ({1}) de '{2}' grupo de '{3}' unidos. ({4}/{5})"
+msgstr "{0} ({1}) del grupo '{2}' desde '{3}' se ha conectado. ({4}/{5})"
#: ../../TShockAPI/TShock.cs:1667
#, csharp-format
msgid "{0} ({1}) from '{2}' group joined. ({3}/{4})"
-msgstr "{0} ({1}) de '{2}' se unió al grupo ahora son: ({3}/{4})"
+msgstr "{0} ({1}) del grupo '{2}' se ha conectado. ({3}/{4})"
#: ../../TShockAPI/Commands.cs:775
#, csharp-format
@@ -249,7 +249,7 @@ msgstr "{0} ({1}) tuvo {2} o más intentos de inicio de sesión no válidos y fu
#: ../../TShockAPI/TShock.cs:1663
#, csharp-format
msgid "{0} ({1}) has joined."
-msgstr "{0} ({1}) se ha unido."
+msgstr "{0} ({1}) se ha conectado."
#: ../../TShockAPI/Commands.cs:5354
#, csharp-format
@@ -295,7 +295,7 @@ msgstr "{0} {1} {2}"
#: ../../TShockAPI/Commands.cs:1464
#, csharp-format
msgid "{0} {1} on {2} ({3} ago)"
-msgstr "{0} {1} en {2} (hace {3})"
+msgstr "{0} {1} el {2} (hace {3})"
#: ../../TShockAPI/Commands.cs:6368
#, csharp-format
@@ -350,17 +350,17 @@ msgstr "{0} añadió la cuenta {1} al grupo {2}."
#: ../../TShockAPI/GetDataHandlers.cs:3546
#, csharp-format
msgid "{0} applied advanced combat techniques volume 2!"
-msgstr "{0} aplicó el volumen 2 de técnicas avanzadas de combate!"
+msgstr "¡{0} aplicó el volumen 2 de Técnicas Avanzadas de Combate!"
#: ../../TShockAPI/GetDataHandlers.cs:3564
#, csharp-format
msgid "{0} applied advanced combat techniques!"
-msgstr "¡{0} aplicó técnicas avanzadas de combate!"
+msgstr "¡{0} aplicó Técnicas Avanzadas de Combate!"
#: ../../TShockAPI/GetDataHandlers.cs:3543
#, csharp-format
msgid "{0} applied traveling merchant's satchel!"
-msgstr "¡{0} ha aplicado satchel de mercader ambulante!"
+msgstr "¡{0} ha aplicado el sachet del mercader ambulante!"
#: ../../TShockAPI/Commands.cs:1064
#, csharp-format
@@ -371,7 +371,7 @@ msgstr "{0} intentó registrar la cuenta {1} pero ya está en uso."
#: ../../TShockAPI/GetDataHandlers.cs:3212
#, csharp-format
msgid "{0} authenticated successfully as user {1}."
-msgstr "{0} se ha autenticado correctamente como usuario {1}."
+msgstr "{0} se ha autenticado correctamente como el usuario {1}."
#: ../../TShockAPI/Commands.cs:905
#, csharp-format
@@ -387,8 +387,8 @@ msgstr "{0} baneó a {1} por '{2}'."
#, csharp-format
msgid "{0} butchered {1} NPC."
msgid_plural "{0} butchered {1} NPCs."
-msgstr[0] "{0} mató {1} NPC."
-msgstr[1] "{0} mató {1} NPC."
+msgstr[0] "{0} mató {1} PNJ."
+msgstr[1] "{0} mató a {1} PNJ."
#: ../../TShockAPI/Commands.cs:2462
#, csharp-format
@@ -408,12 +408,12 @@ msgstr "{0} cambió la cuenta {1} al grupo {2}."
#: ../../TShockAPI/Commands.cs:4466
#, csharp-format
msgid "{0} changed the maximum spawns to {1}."
-msgstr "{0} cambió la cantidad máxima de enemigos entrantes a {1}."
+msgstr "{0} cambió la cantidad máxima de enemigos generados a {1}."
#: ../../TShockAPI/Commands.cs:4447
#, csharp-format
msgid "{0} changed the maximum spawns to 5."
-msgstr "{0} cambió la cantidad máxima de enemigos entrantes a 5."
+msgstr "{0} cambió la cantidad máxima de enemigos generados a 5."
#: ../../TShockAPI/Commands.cs:1154
#, csharp-format
@@ -446,8 +446,8 @@ msgstr[1] "{0} eliminó {1} objetos en un radio de {2}."
#, csharp-format
msgid "{0} deleted {1} NPC within a radius of {2}."
msgid_plural "{0} deleted {1} NPCs within a radius of {2}."
-msgstr[0] "{0} eliminó {1} NPC en un radio de {2}."
-msgstr[1] "{0} eliminó {1} NPCs en un radio de {2}."
+msgstr[0] "{0} eliminó {1} PNJ en un radio de {2}."
+msgstr[1] "{0} eliminó a {1} PNJ en un radio de {2}."
#: ../../TShockAPI/Commands.cs:5902
#, csharp-format
@@ -516,7 +516,7 @@ msgstr[1] "{0} te ha dado {1} {2}s."
#: ../../TShockAPI/Commands.cs:4247
#, csharp-format
msgid "{0} has been allowed to place tile {1}."
-msgstr "A {0} se le ha permitido colocar la casilla {1}."
+msgstr "A {0} se le ha permitido colocar el bloque {1}."
#: ../../TShockAPI/Commands.cs:3881
#, csharp-format
@@ -573,32 +573,32 @@ msgstr "{0} ha finalizado la invasión del Ejército del Antiguo."
#: ../../TShockAPI/TShock.cs:1670
#, csharp-format
msgid "{0} has joined."
-msgstr "{0} se ha unido."
+msgstr "{0} se ha conectado."
#: ../../TShockAPI/TShock.cs:1674
#, csharp-format
msgid "{0} has joined. IP: {1}"
-msgstr "{0} se ha unido. IP: {1}"
+msgstr "{0} se ha conectado. IP: {1}"
#: ../../TShockAPI/Commands.cs:5655
#, csharp-format
msgid "{0} has launched {1} into space."
-msgstr "{0} ha lanzado a {1} al espacio."
+msgstr "{0} ha lanzado a {1} a la estratosfera."
#: ../../TShockAPI/Commands.cs:5653
#, csharp-format
msgid "{0} has launched herself into space."
-msgstr "{0} se lanzó ella misma al espacio."
+msgstr "{0} se lanzó ella misma a la estratosfera."
#: ../../TShockAPI/Commands.cs:5651
#, csharp-format
msgid "{0} has launched himself into space."
-msgstr "{0} se lanzó el mismo al espacio."
+msgstr "{0} se lanzó el mismo a la estratosfera."
#: ../../TShockAPI/TShock.cs:1397
#, csharp-format
msgid "{0} has left."
-msgstr "{0} ha salido."
+msgstr "{0} se desconectó."
#: ../../TShockAPI/Commands.cs:5500
#, csharp-format
@@ -645,7 +645,7 @@ msgstr "{0} ha invocado un Muro Carnoso."
#: ../../TShockAPI/GetDataHandlers.cs:2620
#, csharp-format
msgid "{0} has SSC data in the database, but has the tshock.ignore.ssc permission. This means their SSC data is being ignored."
-msgstr "{0} tiene datos SSC en la base de datos, pero posee el permiso tshock.ignore.ssc. Esto significa que sus datos SSC están siendo ignorados."
+msgstr "{0} tiene datos SSC en la base de datos, mas tiene el permiso tshock.ignore.ssc; sus datos SSC están siendo ignorados."
#: ../../TShockAPI/Commands.cs:2342
#, csharp-format
@@ -670,7 +670,7 @@ msgstr "{0} inició la invasión de la legión de escarcha."
#: ../../TShockAPI/Commands.cs:5488
#, csharp-format
msgid "{0} has unmuted {1}."
-msgstr "{0} ha dessilenciado a {1}."
+msgstr "{0} ha desmuteado a {1}."
#: ../../TShockAPI/Commands.cs:6356
#, csharp-format
@@ -690,22 +690,22 @@ msgstr "{0} se curó a sí mismo por {1} HP."
#: ../../TShockAPI/Commands.cs:4250
#, csharp-format
msgid "{0} is already allowed to place tile {1}."
-msgstr "{0} ya tiene permiso de colocar el bloque {1}."
+msgstr "{0} ya tenía permiso de colocar el bloque {1}."
#: ../../TShockAPI/Commands.cs:3885
#, csharp-format
msgid "{0} is already allowed to use {1}."
-msgstr "{0} ya tenía permiso de usar {1}"
+msgstr "{0} ya tenía permiso de usar {1}."
#: ../../TShockAPI/Commands.cs:4074
#, csharp-format
msgid "{0} is already allowed to use projectile {1}."
-msgstr "{0} ya tiene permiso de usar el proyectil {1}."
+msgstr "{0} ya tenía permiso de usar el proyectil {1}."
#: ../../TShockAPI/Commands.cs:5940
#, csharp-format
msgid "{0} is already dead!"
-msgstr "¡{0} ya está muerto!"
+msgstr "¡{0} ya había muerto!"
#: ../../TShockAPI/Commands.cs:3956
#, csharp-format
@@ -715,12 +715,12 @@ msgstr "{0} ya tenía prohibido usar {1}."
#: ../../TShockAPI/Commands.cs:4309
#, csharp-format
msgid "{0} is already prevented from placing tile {1}."
-msgstr "A {0} ya se le ha prohibido colocar el bloque {1}."
+msgstr "A {0} ya se le había prohibido colocar el bloque {1}."
#: ../../TShockAPI/Commands.cs:4133
#, csharp-format
msgid "{0} is already prevented from using projectile {1}."
-msgstr "A {0} ya se le ha prohibido usar el proyectil {1}."
+msgstr "A {0} ya se le había prohibido usar el proyectil {1}."
#: ../../TShockAPI/ItemBans.cs:234
#, csharp-format
@@ -747,7 +747,7 @@ msgstr "{0} no está baneado(a)."
#: ../../TShockAPI/Commands.cs:5990
#, csharp-format
msgid "{0} is not dead!"
-msgstr "¡{0} no esta muerto!"
+msgstr "¡{0} no ha muerto!"
#: ../../TShockAPI/Commands.cs:6750
#, csharp-format
@@ -757,7 +757,7 @@ msgstr "{0} está ahora en modo dios."
#: ../../TShockAPI/Commands.cs:5586
#, csharp-format
msgid "{0} is offline and cannot receive your reply."
-msgstr "{0} está desconectado y no puede recibir tu respuesta."
+msgstr "{0} está desconectado(a) y no puede recibir tu respuesta."
#: ../../TShockAPI/Commands.cs:5949
#: ../../TShockAPI/Rest/RestManager.cs:1068
@@ -904,7 +904,7 @@ msgstr "{0} eliminó exitosamente la cuenta: {1}."
#: ../../TShockAPI/GetDataHandlers.cs:3567
#, csharp-format
msgid "{0} summoned a Blood Moon!"
-msgstr "¡{0} invocó una Luna de Sangre!"
+msgstr "¡{0} invocó una Luna Sangrienta!"
#: ../../TShockAPI/GetDataHandlers.cs:3579
#, csharp-format
@@ -950,7 +950,7 @@ msgstr "¡{0} ha invocado a {1}"
#: ../../TShockAPI/GetDataHandlers.cs:2980
#, csharp-format
msgid "{0} summoned the Empress of Light!"
-msgstr "¡{0} ha invocado a la Emperatriz de la luz!"
+msgstr "¡{0} ha invocado a la Emperatriz de la Luz!"
#: ../../TShockAPI/GetDataHandlers.cs:3585
#, csharp-format
@@ -960,7 +960,7 @@ msgstr "¡{0} ha invocado los Piratas!"
#: ../../TShockAPI/GetDataHandlers.cs:3588
#, csharp-format
msgid "{0} summoned the Snow Legion!"
-msgstr "¡{0} ha invocado a la Legión Helada!"
+msgstr "¡{0} ha invocado a la Legión de Escarcha!"
#: ../../TShockAPI/Commands.cs:3004
#: ../../TShockAPI/Commands.cs:3044
@@ -1002,7 +1002,7 @@ msgstr "{0} te transportó hacia {1}."
#: ../../TShockAPI/TSPlayer.cs:1953
#, csharp-format
msgid "{0} was banned for '{1}'."
-msgstr "{0} fue prohibido por '{1}'."
+msgstr "{0} fue baneado por '{1}'."
#: ../../TShockAPI/TSPlayer.cs:1924
#, csharp-format
@@ -1043,7 +1043,7 @@ msgstr "{0}{1} no define ningún alias."
#: ../../TShockAPI/Commands.cs:5284
#, csharp-format
msgid "{0}{1} help: "
-msgstr "Ayuda de {0}{1} : "
+msgstr "Ayuda de {0}{1}: "
#: ../../TShockAPI/Utils.cs:1152
#, csharp-format
@@ -1099,18 +1099,18 @@ msgstr "* **Comandos**: `{0}`"
#: ../../TShockAPI/Configuration/TShockConfig.cs:645
#, csharp-format
msgid "* **Default**: `{0}`"
-msgstr ""
+msgstr "* **Predeterminado**: `{0}`"
#: ../../TShockAPI/Configuration/ServerSideConfig.cs:122
#: ../../TShockAPI/Configuration/TShockConfig.cs:644
#, csharp-format
msgid "* **Field type**: `{0}`"
-msgstr ""
+msgstr "* **Tipo de campo**: `{0}`"
#: ../../TShockAPI/Rest/RestManager.cs:1220
#, csharp-format
msgid "* **Permissions**: `{0}`"
-msgstr ""
+msgstr "* **Permisos**: `{0}`"
#: ../../TShockAPI/Commands.cs:5430
#, csharp-format
@@ -1120,15 +1120,15 @@ msgstr "*{0} {1}"
#: ../../TShockAPI/Rest/RestManager.cs:1253
#, csharp-format
msgid "**Example Usage**: `{0}?{1}`"
-msgstr ""
+msgstr "**Ejemplo de Uso**: `{0}?{1}`"
#: ../../TShockAPI/Rest/RestManager.cs:1243
msgid "**Nouns**:"
-msgstr ""
+msgstr "**Sustantivos**:"
#: ../../TShockAPI/Rest/RestManager.cs:1230
msgid "**Verbs**:"
-msgstr ""
+msgstr "**Verbos**:"
#: ../../TShockAPI/Commands.cs:1613
#, csharp-format
@@ -1149,12 +1149,12 @@ msgstr " {1}"
#: ../../TShockPluginManager/NugetCLI.cs:187
#, csharp-format
msgid "{0} from {1} [{2}]"
-msgstr ""
+msgstr "{0} de {1} [{2}]"
#: ../../TShockPluginManager/NugetCLI.cs:178
#, csharp-format
msgid "{0} from {1} [{2}]"
-msgstr ""
+msgstr "{0} de {1} [{2}]"
#: ../../TShockAPI/Commands.cs:5551
#: ../../TShockAPI/Commands.cs:5582
@@ -1166,15 +1166,15 @@ msgstr " {1}"
#, csharp-format
msgid "=== Dependency ==="
msgid_plural "=== Dependencies ==="
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "=== Dependencia ==="
+msgstr[1] "=== Dependencias ==="
#: ../../TShockPluginManager/NugetCLI.cs:133
#, csharp-format
msgid "=== Requested Plugin ==="
msgid_plural "=== Requested Plugins ==="
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "=== Plugin Solicitado ==="
+msgstr[1] "=== Plugins Solicitados ==="
#: ../../TShockAPI/Commands.cs:2818
msgid "a Deerclops"
@@ -1202,11 +1202,11 @@ msgstr "Un meteoro ha sido desencadenado."
#: ../../TShockAPI/Commands.cs:2803
msgid "a Nebula Pillar"
-msgstr "un Pilar de Nebulosa"
+msgstr "una Columna de Nebulosa"
#: ../../TShockAPI/TShock.cs:974
msgid "A password for this server was set in config.json and is being used."
-msgstr "Una contraseña para este servidor fue establecida en config.json y está siendo usada."
+msgstr "Se estableció una contraseña para este servidor en config.json y está en uso."
#: ../../TShockAPI/Commands.cs:1316
msgid "A player name must be provided to kick a player. Please provide one."
@@ -1219,15 +1219,15 @@ msgstr "Un plugin en el servidor detuvo tu inicio de sesión."
#: ../../TShockAPI/Rest/SecureRest.cs:120
#, csharp-format
msgid "A REST login from {0} was blocked as it currently has {1} rate-limit tokens and is at the RESTMaximumRequestsPerInterval threshold."
-msgstr "Se bloqueó un inicio de sesión REST de {0} dado que ya tiene {1} tókenes restringidos por frecuencia, alcanzando el límite de RESTMaximumRequestsPerInterval."
+msgstr "Se bloqueó un inicio de sesión REST de {0} dado que ya tiene {1} tókens restringidos por frecuencia, alcanzando el umbral de RESTMaximumRequestsPerInterval."
#: ../../TShockAPI/Commands.cs:2798
msgid "a Solar Pillar"
-msgstr "un Pilar Solar"
+msgstr "una Columna Solar"
#: ../../TShockAPI/Commands.cs:2813
msgid "a Stardust Pillar"
-msgstr "un Pilar de Polvo Estelar"
+msgstr "una Columna de Polvo Estelar"
#: ../../TShockAPI/Commands.cs:864
msgid "A user account by that name does not exist."
@@ -1235,7 +1235,7 @@ msgstr "Una cuenta de usuario con ese nombre no existe."
#: ../../TShockAPI/Commands.cs:2808
msgid "a Vortex Pillar"
-msgstr "un Pilar de Vórtice"
+msgstr "una Columna de Vórtice"
#: ../../TShockAPI/Commands.cs:1159
#, csharp-format
@@ -1289,7 +1289,7 @@ msgstr "{0} añadido a la lista blanca."
#: ../../TShockAPI/Bouncer.cs:2041
#, csharp-format
msgid "Added buff to {0} NPC abnormally."
-msgstr "Añadido buff al NPC {0} anormalmente."
+msgstr "Añadido buff al PNJ {0} de forma anormal."
#: ../../TShockAPI/Commands.cs:4855
#, csharp-format
@@ -1317,7 +1317,7 @@ msgstr "Alias de {0}{1}: {0}{2}"
#: ../../TShockAPI/Commands.cs:6013
msgid "All alive NPCs (excluding town NPCs) on the server will be killed if you do not input a name or ID."
-msgstr "Todos los PNJs vivos (excluyendo los PNJs de la ciudad) en el servidor serán asesinados si no introduces un nombre o ID."
+msgstr "Se matará a todos los PNJ vivos (excepto los ciudadanos) en el servidor si no introduces un nombre o ID."
#: ../../TShockAPI/Commands.cs:2626
msgid "all bosses"
@@ -1325,7 +1325,7 @@ msgstr "todos los jefes"
#: ../../TShockPluginManager/NugetCLI.cs:169
msgid "All done! :)"
-msgstr ""
+msgstr "¡Todo listo! :)"
#: ../../TShockAPI/Commands.cs:2104
msgid "All REST tokens have been destroyed."
@@ -1358,15 +1358,15 @@ msgstr "allowg - Permite a un grupo acceder a dicha región."
#: ../../TShockAPI/Commands.cs:6670
msgid "Amber Gemtree"
-msgstr "Gema de Ámbar"
+msgstr "Arbolgema de Ámbar"
#: ../../TShockAPI/Commands.cs:6650
msgid "Amethyst Gemtree"
-msgstr "Gema de Amatista"
+msgstr "Arbolgema de Amatista"
#: ../../TShockAPI/TShock.cs:996
msgid "An account has been detected in the user database, but setup-code.txt is still present."
-msgstr "Se ha detectado una cuenta en la base de datos de usuarios, pero setup-code.txt sigue presente."
+msgstr "Se ha detectado una cuenta de usuario en la base de datos, pero setup-code.txt sigue presente."
#: ../../TShockAPI/DB/GroupManager.cs:474
#, csharp-format
@@ -1380,7 +1380,7 @@ msgstr "Ha ocurrido una excepción durante la transacción de la base de datos:
#: ../../TShockAPI/TShock.cs:1489
msgid "An exception occurred executing a command."
-msgstr ""
+msgstr "Se ha producido una excepción al ejecutar un comando."
#: ../../TShockAPI/DB/BanManager.cs:644
msgid "An identifier for a character name."
@@ -1388,7 +1388,7 @@ msgstr "Un identificador para un nombre de personaje."
#: ../../TShockAPI/DB/BanManager.cs:648
msgid "An identifier for a TShock User Account name."
-msgstr "Un identificador para un nombre de usuario de TShock."
+msgstr "Un identificador para un nombre de Cuenta de Usuario de TShock."
#: ../../TShockAPI/DB/BanManager.cs:640
msgid "An identifier for a UUID."
@@ -1405,16 +1405,16 @@ msgstr "Se pidió comprobar si hay actualizaciones. De haber una se te notificar
#: ../../TShockAPI/Commands.cs:5599
msgid "Annoy Syntax"
-msgstr "Sintaxis molesta"
+msgstr "Sintaxis de Molestar"
#: ../../TShockAPI/Commands.cs:5616
#, csharp-format
msgid "Annoying {0} for {1} seconds."
-msgstr "Molesta a {0} durante {1} segundos."
+msgstr "Molestando a {0} por {1} segundos."
#: ../../TShockAPI/Commands.cs:338
msgid "Annoys a player for an amount of time."
-msgstr "Molesta a un jugador durante un tiempo."
+msgstr "Molesta a un jugador durante un período de tiempo."
#: ../../TShockAPI/Rest/Rest.cs:452
#, csharp-format
@@ -1423,11 +1423,11 @@ msgstr "Anónimo solicitó un punto final REST: {0}"
#: ../../TShockAPI/Commands.cs:5224
msgid "Anti-build is now off."
-msgstr "El Anti-construcción ya no está activo."
+msgstr "Anti-construcción ha sido desactivado."
#: ../../TShockAPI/Commands.cs:5224
msgid "Anti-build is now on."
-msgstr "El Anti-construcción está activo."
+msgstr "Anti-construcción ha sido activado."
#: ../../TShockAPI/Commands.cs:3204
msgid "Arguments: add [warp name], del [warp name], list [page]."
@@ -1442,17 +1442,17 @@ msgstr "Parámetros: send [jugador] [nombre de warp], hide [nombre de warp] [Act
#: ../../TShockAPI/GetDataHandlers.cs:3211
#, csharp-format
msgid "Authenticated as {0} successfully."
-msgstr "Autenticado como {0} con éxito."
+msgstr "Autenticado como {0} correctamente."
#: ../../TShockAPI/TShock.cs:440
#: ../../TShockAPI/TShock.cs:1590
msgid "AutoSave Disabled"
-msgstr "Autoguardado desactivado"
+msgstr "AutoGuardado Desactivado"
#: ../../TShockAPI/TShock.cs:438
#: ../../TShockAPI/TShock.cs:1588
msgid "AutoSave Enabled"
-msgstr "Autoguardado activado."
+msgstr "AutoGuardado Activado"
#: ../../TShockAPI/Rest/RestManager.cs:811
msgid "AutoSave has been disabled"
@@ -1468,19 +1468,19 @@ msgstr "El autoguardado esta actualmente desactivado"
#: ../../TShockAPI/Rest/RestManager.cs:796
msgid "Autosave is currently enabled"
-msgstr "El autoguardado está actualmente activo"
+msgstr "El autoguardado está actualmente activado"
#: ../../TShockAPI/Commands.cs:1368
msgid "Available Ban commands:"
-msgstr "Comandos de prohibición disponibles:"
+msgstr "Comandos de Baneo disponibles:"
#: ../../TShockAPI/Commands.cs:1432
msgid "Available identifiers ({{0}}/{{1}}):"
-msgstr "Identificadores disponibles ({{0}}/{1}}):"
+msgstr "Identificadores disponibles ({{0}}/{{1}}):"
#: ../../TShockAPI/Commands.cs:5208
msgid "Available Region Sub-Commands ({{0}}/{{1}}):"
-msgstr "Subcomandos de región disponibles ({{0}}/{{1}}):"
+msgstr "Sub-Comandos de Región Disponibles ({{0}}/{{1}}):"
#: ../../TShockAPI/Commands.cs:2109
msgid "Available REST Sub-Commands:"
@@ -1501,11 +1501,11 @@ msgstr "Hilo de Respaldo"
#: ../../TShockAPI/TShock.cs:444
msgid "Backups Disabled"
-msgstr "Respaldos desactivados"
+msgstr "Respaldos Desactivados"
#: ../../TShockAPI/TShock.cs:442
msgid "Backups Enabled"
-msgstr "Respaldos activados"
+msgstr "Respaldos Activados"
#: ../../TShockAPI/Commands.cs:837
msgid "Bad login attempt."
@@ -1520,12 +1520,12 @@ msgstr "ban {0}"
#: ../../TShockAPI/Commands.cs:1372
#, csharp-format
msgid "ban {0} "
-msgstr "ban {0} "
+msgstr "ban {0} "
#: ../../TShockAPI/Commands.cs:1369
#, csharp-format
msgid "ban {0} [Flags]"
-msgstr "ban {0} [Flags]"
+msgstr "ban {0} [Parámetros]"
#: ../../TShockAPI/Commands.cs:1633
#, csharp-format
@@ -1539,7 +1539,7 @@ msgstr "El ban {0} acaba de marcarse como expirado."
#: ../../TShockAPI/Commands.cs:1383
msgid "Ban Add Syntax"
-msgstr "Ban Add Syntax"
+msgstr "Sintaxis de Ban Add"
#: ../../TShockAPI/Commands.cs:1490
#, csharp-format
@@ -1553,15 +1553,15 @@ msgstr "Ban añadido. Número de ticket: {0}"
#: ../../TShockAPI/Commands.cs:1396
msgid "Ban Del Syntax"
-msgstr "Sintaxis Ban Del"
+msgstr "Sintaxis de Ban Del"
#: ../../TShockAPI/Commands.cs:1412
msgid "Ban Details Syntax"
-msgstr "Detalles de la prohibición Sintaxis"
+msgstr "Sintaxis de Ban Details"
#: ../../TShockAPI/Commands.cs:1404
msgid "Ban List Syntax"
-msgstr "Sintaxis de la lista de prohibiciones"
+msgstr "Sintaxis de Ban List"
#: ../../TShockAPI/Rest/RestManager.cs:695
msgid "Ban removed."
@@ -1569,12 +1569,12 @@ msgstr "Ban revocado."
#: ../../TShockAPI/Commands.cs:1442
msgid "Ban Usage Examples"
-msgstr "Ejemplos de uso de prohibición"
+msgstr "Ejemplos del Uso de Ban"
#: ../../TShockAPI/Commands.cs:3841
#, csharp-format
msgid "Banned {0}."
-msgstr "Prohibido {0}."
+msgstr "Se ha baneado a {0}."
#: ../../TShockAPI/Commands.cs:4037
#, csharp-format
@@ -1589,7 +1589,7 @@ msgstr "Se ha prohibido el bloque {0}."
#: ../../TShockAPI/TSPlayer.cs:1950
#, csharp-format
msgid "Banned: {0}"
-msgstr "Prohibido: {0}"
+msgstr "Baneado: {0}"
#: ../../TShockAPI/Commands.cs:1517
msgid "Banned."
@@ -1597,11 +1597,11 @@ msgstr "Baneado."
#: ../../TShockAPI/Commands.cs:1670
msgid "Bans ({{0}}/{{1}}):"
-msgstr "Prohibidos ({{0}}/{{1}}):"
+msgstr "Baneos ({{0}}/{{1}}):"
#: ../../TShockAPI/Commands.cs:6592
msgid "Basic Tree"
-msgstr "Árbol básico"
+msgstr "Árbol Básico"
#: ../../TShockAPI/Commands.cs:2755
msgid "Betsy"
@@ -1615,21 +1615,21 @@ msgstr "La Luna Sangrienta fue marcada como {0}"
#: ../../TShockAPI/Rest/RestManager.cs:904
#, csharp-format
msgid "Bloodmoon state: {0}"
-msgstr "Estado de la luna de sangre: {0}"
+msgstr "Estado de la Luna Sangrienta: {0}"
#: ../../TShockAPI/Commands.cs:6596
msgid "Boreal Tree"
-msgstr "Árbol boreal"
+msgstr "Árbol Boreal"
#: ../../TShockAPI/GetDataHandlers.cs:3264
#, csharp-format
msgid "Bouncer / HandleNpcTalk rejected from bouncer out of bounds from {0}"
-msgstr ""
+msgstr "Bouncer / HandleNpcTalk rechazó desde el bouncer fuera de area de {0}"
#: ../../TShockAPI/GetDataHandlers.cs:3257
#, csharp-format
msgid "Bouncer / HandleNpcTalk rejected from bouncer throttle from {0}"
-msgstr ""
+msgstr "Bouncer / HandleNpcTalk rechazó desde el bouncer limitado de {0}"
#: ../../TShockAPI/Bouncer.cs:1465
#, csharp-format
@@ -1689,17 +1689,17 @@ msgstr "Bouncer / OnFishOutNPC rechazo comprobaciones de rango de {0}"
#: ../../TShockAPI/Bouncer.cs:2747
#, csharp-format
msgid "Bouncer / OnFishOutNPC rejected summon boss permissions from {0}"
-msgstr "Bouncer / OnFishOutNPC rechazo permisos de invocación de jefe de {0}"
+msgstr "Bouncer / OnFishOutNPC rechazó permisos de invocación de jefe de {0}"
#: ../../TShockAPI/Bouncer.cs:2781
#, csharp-format
msgid "Bouncer / OnFoodPlatterTryPlacing rejected disabled from {0}"
-msgstr "Bouncer / OnFoodPlatterTryPlacing rechazo desactivación de {0}"
+msgstr "Bouncer / OnFoodPlatterTryPlacing rechazó desactivación de {0}"
#: ../../TShockAPI/Bouncer.cs:2774
#, csharp-format
msgid "Bouncer / OnFoodPlatterTryPlacing rejected item not placed by hand from {0}"
-msgstr "Bouncer / OnFoodPlatterTryPlacing rechazo objeto no puesto manualmente de {0}"
+msgstr "Bouncer / OnFoodPlatterTryPlacing rechazó objeto no puesto manualmente de {0}"
#: ../../TShockAPI/Bouncer.cs:2792
#, csharp-format
@@ -1714,7 +1714,7 @@ msgstr "Bouncer / OnFoodPlatterTryPlacing rechazó las comprobaciones de rango d
#: ../../TShockAPI/Bouncer.cs:2767
#, csharp-format
msgid "Bouncer / OnFoodPlatterTryPlacing rejected tile placement valid from {0}"
-msgstr "Bouncer / OnFoodPlatterTryPlacing rechazó la colocación de baldosas válida desde {0}"
+msgstr "Bouncer / OnFoodPlatterTryPlacing rechazó la colocación de bloques válidos desde {0}"
#: ../../TShockAPI/Bouncer.cs:2508
#, csharp-format
@@ -1724,12 +1724,12 @@ msgstr "Bouncer / OnGemLockToggle colocación inválida/deadmod de {0}"
#: ../../TShockAPI/Bouncer.cs:2501
#, csharp-format
msgid "Bouncer / OnGemLockToggle rejected boundaries check from {0}"
-msgstr "Bouncer / OnGemLockToggle rechazó la comprobación de límites de {0}"
+msgstr "Bouncer / OnGemLockToggle rechazada la comprobación de umbral de {0}"
#: ../../TShockAPI/Bouncer.cs:2515
#, csharp-format
msgid "Bouncer / OnGemLockToggle rejected disabled from {0}"
-msgstr "Bouncer / OnGemLockToggle rechazado deshabilitado desde {0}"
+msgstr "Bouncer / OnGemLockToggle rechazó el estado deshabilitado desde {0}"
#: ../../TShockAPI/Bouncer.cs:2524
#, csharp-format
@@ -1753,211 +1753,211 @@ msgstr "Bouncer / OnHealOtherPlayer 0.2 comprobar desde {0}"
#: ../../TShockAPI/Bouncer.cs:2120
#, csharp-format
msgid "Bouncer / OnHealOtherPlayer rejected disabled/throttled from {0}"
-msgstr ""
+msgstr "Bouncer / OnHealOtherPlayer rechazó el estado deshabilitado/limitado desde {0}"
#: ../../TShockAPI/Bouncer.cs:2112
#, csharp-format
msgid "Bouncer / OnHealOtherPlayer rejected heal other threshold from {0} {1}/{2}"
-msgstr ""
+msgstr "Bouncer / OnHealOtherPlayer rechazó el umbral de sanar a otro desde {0} {1}/{2}"
#: ../../TShockAPI/Bouncer.cs:2086
msgid "Bouncer / OnHealOtherPlayer rejected null checks"
-msgstr ""
+msgstr "Bouncer / OnHealOtherPlayer rechazó comprobaciones nulas"
#: ../../TShockAPI/Bouncer.cs:1074
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from attempt crash from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazó desde un intento de crasheo desde {0}"
#: ../../TShockAPI/Bouncer.cs:1150
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from disabled from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazó desde estado deshabilitado de {0}"
#: ../../TShockAPI/Bouncer.cs:1130
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from drop item ban check / max stack check / min stack check from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazó la comprobación de objeto prohibido arrojado / comprobación de stack máximo / comprobación de stack mínimo desde {0}"
#: ../../TShockAPI/Bouncer.cs:1099
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from dupe range check from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazó desde la comprobación de rango de duplicación de {0}"
#: ../../TShockAPI/Bouncer.cs:1120
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from item drop/pickup check from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazó desde la comprobación de objeto arrojado/recogido de {0}"
#: ../../TShockAPI/Bouncer.cs:1084
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from prefix check from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazado desde la comprobación de prefijo de {0}"
#: ../../TShockAPI/Bouncer.cs:1110
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from range check from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazado desde la comprobación de rango de {0}"
#: ../../TShockAPI/Bouncer.cs:1141
#, csharp-format
msgid "Bouncer / OnItemDrop rejected from sneaky from {0}"
-msgstr ""
+msgstr "Bouncer / OnItemDrop rechazado desde un sospechoso de {0}"
#: ../../TShockAPI/Bouncer.cs:2703
#, csharp-format
msgid "Bouncer / OnKillMe rejected bad length death text from {0}"
-msgstr ""
+msgstr "Bouncer / OnKillMe rechazó el texto de muerte de longitud incorrecta de {0}"
#: ../../TShockAPI/Bouncer.cs:2710
#, csharp-format
msgid "Bouncer / OnKillMe rejected custom death message from {0}"
-msgstr ""
+msgstr "Bouncer / OnKillMe rechazó el mensaje personalizado de muerte de {0}"
#: ../../TShockAPI/Bouncer.cs:2684
#, csharp-format
msgid "Bouncer / OnKillMe rejected high damage from {0} {1}"
-msgstr ""
+msgstr "Bouncer / OnKillMe rechazó el daño elevado de {0} {1}"
#: ../../TShockAPI/Bouncer.cs:2693
#, csharp-format
msgid "Bouncer / OnKillMe rejected index check from {0}"
-msgstr ""
+msgstr "Bouncer / OnKillMe rechazó la comprobación de índice de {0}"
#: ../../TShockAPI/Bouncer.cs:1832
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected build permission from {0}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazó el permiso de construcción de {0}"
#: ../../TShockAPI/Bouncer.cs:1676
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected disabled from {0}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazo por deshabilitamiento de {0}"
#: ../../TShockAPI/Bouncer.cs:1694
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected from liquid threshold from {0} {1}/{2}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazado desde el umbral de líquido de {0} {1}/{2}"
#: ../../TShockAPI/Bouncer.cs:1669
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected invalid check from {0}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazó comprobación inválida desde {0}"
#: ../../TShockAPI/Bouncer.cs:1731
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected liquid type {0} from {1} holding {2}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazó el tipo de líquido {0} de {1} sosteniendo {2}"
#: ../../TShockAPI/Bouncer.cs:1840
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected range checks from {0}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazó las comprobaciones de rango de {0}"
#: ../../TShockAPI/Bouncer.cs:1848
#, csharp-format
msgid "Bouncer / OnLiquidSet rejected throttle from {0}"
-msgstr ""
+msgstr "Bouncer / OnLiquidSet rechazó limitación desde {0}"
#: ../../TShockAPI/Bouncer.cs:2573
#, csharp-format
msgid "Bouncer / OnMassWireOperation rejected build perms from {0}"
-msgstr ""
+msgstr "Bouncer / OnMassWireOperation rechazó los permisos de construcción desde {0}"
#: ../../TShockAPI/Bouncer.cs:2566
#, csharp-format
msgid "Bouncer / OnMassWireOperation rejected disabled from {0}"
-msgstr ""
+msgstr "Bouncer / OnMassWireOperation rechazó por deshabilitación desde {0}"
#: ../../TShockAPI/Bouncer.cs:2559
#, csharp-format
msgid "Bouncer / OnMassWireOperation rejected valid placement from {0}"
-msgstr ""
+msgstr "Bouncer / OnMassWireOperation rechazó la colocación válida desde {0}"
#: ../../TShockAPI/Bouncer.cs:1274
#, csharp-format
msgid "Bouncer / OnNewProjectile please report to tshock about this! normally this is a reject from {0} {1}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile ¡Favor reportar esto a TShock! Normalmente esto es un rechazo desde {0} {1}"
#: ../../TShockAPI/Bouncer.cs:1230
#, csharp-format
msgid "Bouncer / OnNewProjectile please report to tshock about this! normally this is a reject from {0} {1} (golf)"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile ¡Favor reportar esto a TShock! Normalmente esto es un rechazo desde {0} {1} (golf)"
#: ../../TShockAPI/Bouncer.cs:1174
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from above projectile limit from {0}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por estar sobre el límite de proyectil desde {0}"
#: ../../TShockAPI/Bouncer.cs:1315
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from bouncer modified AI from {0}."
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado desde el bouncer por IA modificada de {0}."
#: ../../TShockAPI/Bouncer.cs:1331
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from bouncer modified Zenith projectile from {0}."
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado desde el bouncer por proyectil Cenit modificado de {0}."
#: ../../TShockAPI/Bouncer.cs:1302
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from bouncer throttle from {0}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado desde el bouncer por limitación de {0}"
#: ../../TShockAPI/Bouncer.cs:1201
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from disabled from {0}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazó desde estado deshabilitado de {0}"
#: ../../TShockAPI/Bouncer.cs:1238
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from hostile projectile from {0}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por uso de proyectil hostil de {0}"
#: ../../TShockAPI/Bouncer.cs:1183
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from permission check from {0} {1}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por comprobación de permisos de {0} {1}"
#: ../../TShockAPI/Bouncer.cs:1294
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from projectile create threshold from {0} {1}/{2}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por umbral de creación de proyectil desde {0} {1}/{2}"
#: ../../TShockAPI/Bouncer.cs:1193
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from projectile damage limit from {0} {1}/{2}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por límite de daño de proyectil desde {0} {1}/{2}"
#: ../../TShockAPI/Bouncer.cs:1249
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from tombstones from {0}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por lápidas de {0}"
#: ../../TShockAPI/Bouncer.cs:1269
#, csharp-format
msgid "Bouncer / OnNewProjectile rejected from weird check from {0} {1}"
-msgstr ""
+msgstr "Bouncer / OnNewProjectile rechazado por comprobación extraña de {0} {1}"
#: ../../TShockAPI/Bouncer.cs:2040
#, csharp-format
msgid "Bouncer / OnNPCAddBuff rejected abnormal buff ({0}, last for {4}) added to {1} ({2}) from {3}."
-msgstr ""
+msgstr "Bouncer / OnNPCAddBuff rechazó un buff anormal ({0}, de duración {4}) aplicado a {1} ({2}) por {3}."
#: ../../TShockAPI/Bouncer.cs:1994
#, csharp-format
msgid "Bouncer / OnNPCAddBuff rejected disabled from {0}"
-msgstr ""
+msgstr "Bouncer / OnNPCAddBuff rechazó el estado deshabilitado desde {0}"
#: ../../TShockAPI/Bouncer.cs:1987
#, csharp-format
msgid "Bouncer / OnNPCAddBuff rejected null npc from {0}"
-msgstr ""
+msgstr "Bouncer / OnNPCAddBuff rechazó PNJ nulo de {0}"
#: ../../TShockAPI/Bouncer.cs:1978
#, csharp-format
msgid "Bouncer / OnNPCAddBuff rejected out of bounds NPC update from {0}"
-msgstr ""
+msgstr "Bouncer / OnNPCAddBuff rechazó actualización de PNJ fuera de los bordes desde {0}"
#: ../../TShockAPI/Bouncer.cs:1416
#, csharp-format
@@ -2535,41 +2535,41 @@ msgstr ""
#: ../../TShockAPI/Utils.cs:136
#, csharp-format
msgid "Broadcast: {0}"
-msgstr ""
+msgstr "Anuncio: {0}"
#: ../../TShockAPI/Utils.cs:159
#, csharp-format
msgid "Broadcast: {0}: {1}"
-msgstr ""
+msgstr "Anuncio: {0}: {1}"
#: ../../TShockAPI/Commands.cs:272
msgid "Broadcasts a message to everyone on the server."
-msgstr ""
+msgstr "Transmite un mensaje a todo el servidor."
#: ../../TShockAPI/Commands.cs:6367
msgid "Buff Syntax and Example"
-msgstr ""
+msgstr "Sintaxis y Ejemplo de Buff"
#: ../../TShockAPI/Commands.cs:6010
msgid "Butcher Syntax and Example"
-msgstr ""
+msgstr "Sintaxis y Ejemplo de Butcher"
#: ../../TShockAPI/GetDataHandlers.cs:2619
msgid "Bypass SSC is enabled for your account. SSC data will not be loaded or saved."
-msgstr ""
+msgstr "Tu cuenta tiene activado el bypass de SSC. Los datos SSC no serán cargados ni guardados."
#: ../../TShockAPI/Commands.cs:6676
msgid "Cactus"
-msgstr ""
+msgstr "Cactus"
#: ../../TShockAPI/DB/IQueryBuilder.cs:319
msgid "Can't set to true SqlColumn.DefaultCurrentTimestamp when the MySqlDbType is not DateTime"
-msgstr ""
+msgstr "SqlColumn.DefaultCurrentTimestamp no puede marcarse como true (verdadero) cuando el MySqlDbType no es DateTime"
#: ../../TShockAPI/Modules/ModuleManager.cs:56
#, csharp-format
msgid "Cannot load module {0} as it does not derive from {1}"
-msgstr ""
+msgstr "No se puede cargar el módulo {0} ya que no se deriva de {1}"
#: ../../TShockAPI/Bouncer.cs:1270
msgid "Certain projectiles have been ignored for cheat detection."
@@ -2578,20 +2578,20 @@ msgstr "Algunos proyectiles fueron ignorados por detección de trampas."
#: ../../TShockAPI/Commands.cs:4462
#, csharp-format
msgid "Changed the maximum spawns to {0}."
-msgstr ""
+msgstr "Se cambió la generación máxima de enemigos a {0}."
#: ../../TShockAPI/Commands.cs:4443
msgid "Changed the maximum spawns to 5."
-msgstr ""
+msgstr "Se cambió la generación máxima de enemigos a 5."
#: ../../TShockAPI/Commands.cs:4501
#, csharp-format
msgid "Changed the spawn rate to {0}."
-msgstr ""
+msgstr "Se cambió la tasa de aparición de enemigos a {0}."
#: ../../TShockAPI/Commands.cs:4483
msgid "Changed the spawn rate to 600."
-msgstr ""
+msgstr "Se cambió la tasa de aparición de enemigos a 600."
#: ../../TShockAPI/Commands.cs:368
msgid "Changes the server password."
@@ -2603,7 +2603,7 @@ msgstr "Cambia la velocidad del viento."
#: ../../TShockAPI/Commands.cs:467
msgid "Changes the world mode."
-msgstr ""
+msgstr "Cambia el tipo de mundo."
#: ../../TShockAPI/Commands.cs:252
msgid "Changes your account's password."
@@ -2612,12 +2612,12 @@ msgstr "Cambia la contraseña de tu cuenta."
#: ../../TShockAPI/Commands.cs:3646
#, csharp-format
msgid "Chat color for \"{0}\" is \"{1}\"."
-msgstr ""
+msgstr "El color de chat para \"{0}\" es \"{1}\"."
#: ../../TShockAPI/Commands.cs:3632
#, csharp-format
msgid "Chat color for group \"{0}\" set to \"{1}\"."
-msgstr ""
+msgstr "Color de chat para grupo \"{0}\" establecido como \"{1}\"."
#: ../../TShockAPI/Commands.cs:352
msgid "Checks for TShock updates."
@@ -2625,15 +2625,15 @@ msgstr "Busca actualizaciones de TShock."
#: ../../TShockAPI/Commands.cs:5186
msgid "clear - Clears the temporary region points."
-msgstr ""
+msgstr "clear - Elimina los puntos de región temporales."
#: ../../TShockAPI/Commands.cs:5814
msgid "Clear Syntax"
-msgstr ""
+msgstr "Sintaxis de Clear"
#: ../../TShockAPI/Commands.cs:2531
msgid "Cleared all users from the angler quest completion list for today."
-msgstr "Se han eliminado a todos los usuarios de la lista de misiones completas del pescador."
+msgstr "Eliminados todos los usuarios de la lista de misiones completadas hoy del pescador."
#: ../../TShockAPI/Commands.cs:537
msgid "Clears item drops or projectiles."
@@ -2646,61 +2646,61 @@ msgstr "color - Cambia el color de chat de un grupo."
#: ../../TShockAPI/Commands.cs:5329
#, csharp-format
msgid "Command aliases: {0}, {1}, {2}"
-msgstr ""
+msgstr "Alias del comando: {0}, {1}, {2}"
#: ../../TShockAPI/Commands.cs:162
msgid "Command failed, check logs for more details."
-msgstr ""
+msgstr "Comando fallido, compruebe los registros para más detalles."
#: ../../TShockAPI/Commands.cs:5260
msgid "Commands ({{0}}/{{1}}):"
-msgstr ""
+msgstr "Comandos ({{0}}/{{1}}):"
#: ../../TShockAPI/Commands.cs:3203
msgid "Commands: add, del, hide, list, send, [warpname]."
-msgstr ""
+msgstr "Comandos: add, del, hide, list, send, [nombre de warp]."
#: ../../TShockAPI/TShock.cs:765
#, csharp-format
msgid "Config path has been set to {0}"
-msgstr ""
+msgstr "La ruta de configuración se ha establecido en {0}"
#: ../../TShockAPI/Commands.cs:4395
msgid "Configuration, permissions, and regions reload complete. Some changes may require a server restart."
-msgstr ""
+msgstr "Recarga de configuración, permisos y regiones completada. Algunos cambios pueden requerir un reinicio del servidor."
#: ../../TShockPluginManager/NugetCLI.cs:100
msgid "Connect to the internet to figure out what to download?"
-msgstr ""
+msgstr "¿Conectarse a Internet para averiguar qué descargar?"
#: ../../TShockAPI/TShock.cs:1329
msgid "Connecting via a proxy is not allowed."
-msgstr ""
+msgstr "No se permite la conexión a través de un proxy."
#: ../../TShockAPI/Commands.cs:1778
#, csharp-format
msgid "Correct usage: {0}overridessc|{0}ossc "
-msgstr ""
+msgstr "Uso correcto: {0}overridessc|{0}ossc "
#: ../../TShockAPI/Commands.cs:6641
msgid "Corruption Palm"
-msgstr ""
+msgstr "Palmera Corrupta"
#: ../../TShockAPI/TShock.cs:296
#, csharp-format
msgid "Could not apply the given log path / log format, defaults will be used. Exception details:\n"
"{0}"
-msgstr ""
+msgstr "No se pudo usar la ruta / formato de logs dado; se usarán los valores predeterminados. Detalles de la excepción: {0}"
#: ../../TShockAPI/DB/ResearchDatastore.cs:54
#: ../../TShockAPI/DB/BanManager.cs:82
msgid "Could not find a database library (probably Sqlite3.dll)"
-msgstr ""
+msgstr "No se encontró una biblioteca de base de datos (probablemente Sqlite3.dll)"
#: ../../TShockAPI/Commands.cs:3268
#, csharp-format
msgid "Could not find a warp named {0} to remove."
-msgstr ""
+msgstr "No se pudo encontrar un warp llamado {0} para eliminarlo."
#: ../../TShockAPI/Commands.cs:5526
#: ../../TShockAPI/Commands.cs:5610
@@ -2709,17 +2709,17 @@ msgstr ""
#: ../../TShockAPI/Commands.cs:5972
#, csharp-format
msgid "Could not find any player named \"{0}\""
-msgstr ""
+msgstr "No se encontró ningún jugador llamado \"{0}\""
#: ../../TShockAPI/Commands.cs:5928
#, csharp-format
msgid "Could not find any player named \"{0}\"."
-msgstr ""
+msgstr "No se encontró ningún jugador llamado \"{0}\"."
#: ../../TShockAPI/Commands.cs:5471
#, csharp-format
msgid "Could not find any players named \"{0}\""
-msgstr ""
+msgstr "Ninguno de los jugadores se llama \"{0}\""
#: ../../TShockAPI/Commands.cs:1934
#, csharp-format
@@ -2733,7 +2733,7 @@ msgstr "No se pudo encontrar al jugador {0}."
#: ../../TShockAPI/Commands.cs:5044
msgid "Could not find specified region"
-msgstr ""
+msgstr "No se pudo encontrar la región especificada"
#: ../../TShockAPI/Commands.cs:3291
msgid "Could not find specified warp."
@@ -2753,47 +2753,47 @@ msgstr "No se pudo encontrar la región {0}."
#: ../../TShockAPI/Commands.cs:1581
msgid "Could not find the target specified. Check that you have the correct spelling."
-msgstr "No se pudo encontrar el objetivo especificado. Compruebe que la escritura sea la correcta."
+msgstr "No se pudo encontrar el objetivo especificado. Revise que esté bien escrito."
#: ../../TShockAPI/Commands.cs:6200
#, csharp-format
msgid "Could not rename {0}!"
-msgstr ""
+msgstr "¡No se pudo renombrar a {0}!"
#: ../../TShockAPI/TShock.cs:1448
msgid "Crash attempt via long chat packet."
-msgstr ""
+msgstr "Intento de crasheo a través de un paquete de chat largo."
#: ../../TShockAPI/Commands.cs:595
msgid "Creates a reference tables for Terraria data types and the TShock permission system in the server folder."
-msgstr ""
+msgstr "Crea tablas de referencia para datos de tipo Terraria y el sistema de permisos TShock en la carpeta del servidor."
#: ../../TShockAPI/Commands.cs:5414
msgid "Creates: with the password as part of the owner group."
-msgstr ""
+msgstr "Crea: con la contraseña como parte del grupo owner."
#: ../../TShockAPI/Handlers/NetModules/CreativePowerHandler.cs:53
msgid "CreativePowerHandler received permission check request for unknown creative power"
-msgstr ""
+msgstr "CreativePowerHandler recibió una petición de comprobación de permiso para un poder creativo desconocido"
#: ../../TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs:65
#, csharp-format
msgid "CreativeUnlocksHandler received non-vanilla unlock request. Random field value: {0} but should be 0 from {1}"
-msgstr ""
+msgstr "CreativeUnlocksHandler recibió una petición de apertura no-vainilla. Valor de campo al azar: {0} pero debería ser 0 de {1}"
#: ../../TShockAPI/Commands.cs:6636
msgid "Crimson Palm"
-msgstr ""
+msgstr "Palmera Carmesí"
#: ../../TShockAPI/Commands.cs:4434
#, csharp-format
msgid "Current maximum spawns: {0}."
-msgstr ""
+msgstr "Número máximo actual de enemigos generados: {0}."
#: ../../TShockAPI/Commands.cs:4474
#, csharp-format
msgid "Current spawn rate: {0}."
-msgstr ""
+msgstr "Tasa actual de generación de enemigos: {0}."
#: ../../TShockAPI/Bouncer.cs:2686
#, csharp-format
@@ -2802,129 +2802,129 @@ msgstr "Intento de Exploit de Muerte: Daño {0}"
#: ../../TShockAPI/Bouncer.cs:2704
msgid "Death reason outside of normal bounds."
-msgstr ""
+msgstr "Razón de muerte fuera de los límites normales."
#: ../../TShockAPI/Configuration/TShockConfig.cs:321
#: ../../TShockAPI/Configuration/TShockConfig.cs:357
msgid "Death results in a ban"
-msgstr ""
+msgstr "Morir resulta en un baneo"
#: ../../TShockAPI/Configuration/TShockConfig.cs:349
msgid "Death results in a kick"
-msgstr ""
+msgstr "Morir resulta en una expulsión"
#: ../../TShockAPI/Commands.cs:5187
msgid "define - Defines the region with the given name."
-msgstr ""
+msgstr "define - Establece la región con el nombre otorgado."
#: ../../TShockAPI/Commands.cs:3442
msgid "del - Deletes a group."
-msgstr ""
+msgstr "del - Elimina un grupo."
#: ../../TShockAPI/Commands.cs:3973
msgid "del - - Deletes an item ban."
-msgstr ""
+msgstr "del - Elimina una prohibición de objeto."
#: ../../TShockAPI/Commands.cs:4151
msgid "del - Deletes an projectile ban."
-msgstr ""
+msgstr "del - Elimina una prohibición de proyectil."
#: ../../TShockAPI/Commands.cs:4327
msgid "del - Deletes a tile ban."
-msgstr ""
+msgstr "del - Elimina una prohibición de bloque."
#: ../../TShockAPI/Commands.cs:5188
msgid "delete - Deletes the given region."
-msgstr ""
+msgstr "delete - Elimina la región especificada."
#: ../../TShockAPI/Commands.cs:4746
#, csharp-format
msgid "Deleted region \"{0}\"."
-msgstr ""
+msgstr "Región \"{0}\" eliminada."
#: ../../TShockAPI/Commands.cs:3443
msgid "delperm - Removes permissions from a group."
-msgstr ""
+msgstr "delperm - Quita permisos a un grupo."
#: ../../TShockAPI/Commands.cs:6627
msgid "Desert Palm"
-msgstr ""
+msgstr "Palmera Desértica"
#: ../../TShockAPI/Commands.cs:2111
msgid "destroytokens - Destroys all current REST tokens."
-msgstr ""
+msgstr "destroytokens - Destruye todos los tókens REST actuales."
#: ../../TShockAPI/Bouncer.cs:480
#: ../../TShockAPI/Bouncer.cs:488
#: ../../TShockAPI/Bouncer.cs:496
msgid "Detected DOOM set to ON position."
-msgstr ""
+msgstr "Detectado DOOM establecido en la posición ON."
#: ../../TShockAPI/Commands.cs:6666
msgid "Diamond Gemtree"
-msgstr ""
+msgstr "Arbolgema de Diamante"
#: ../../TShockAPI/Commands.cs:1881
msgid "Disabled halloween mode."
-msgstr ""
+msgstr "Modo halloween desactivado."
#: ../../TShockAPI/Commands.cs:1900
msgid "Disabled xmas mode."
-msgstr ""
+msgstr "Modo navideño desactivado."
#: ../../TShockAPI/Bouncer.cs:543
#, csharp-format
msgid "Disabled. You need to {0}login to load your saved data."
-msgstr "Deshabilitado. Necesitas {0}login para cargar tus datos guardados."
+msgstr "Deshabilitado. Debes iniciar sesión con {0}login para cargar tus datos guardados."
#: ../../TShockAPI/Bouncer.cs:539
msgid "Disabled. You went too far with banned armor."
-msgstr ""
+msgstr "Deshabilitado. Has ido demasiado lejos usando armadura prohibida."
#: ../../TShockAPI/Bouncer.cs:535
msgid "Disabled. You went too far with hacked item stacks."
-msgstr ""
+msgstr "Deshabilitado. Has ido demasiado lejos con pilas de objetos hackeados."
#: ../../TShockAPI/Commands.cs:3974
msgid "disallow
- - Disallows a group from using an item."
-msgstr ""
+msgstr "disallow - Le prohíbe a un grupo usar el objeto."
#: ../../TShockAPI/Commands.cs:4152
msgid "disallow - Disallows a group from using a projectile."
-msgstr ""
+msgstr "disallow - Le prohíbe a un grupo usar el proyectil."
#: ../../TShockAPI/Commands.cs:4328
msgid "disallow - Disallows a group from place a tile."
-msgstr ""
+msgstr "disallow - Le prohíbe a un grupo colocar el bloque."
#: ../../TShockPluginManager/NugetCLI.cs:140
#: ../../TShockPluginManager/NugetCLI.cs:161
msgid "Download and install the given packages?"
-msgstr ""
+msgstr "¿Descargar e instalar los paquetes en cuestión?"
#: ../../TShockAPI/Commands.cs:2648
msgid "Duke Fishron"
-msgstr ""
+msgstr "Duque Fishron"
#: ../../TShockAPI/Commands.cs:6617
msgid "Ebonwood Tree"
-msgstr ""
+msgstr "Árbol de Ébano"
#: ../../TShockAPI/Commands.cs:6658
msgid "Emerald Gemtree"
-msgstr ""
+msgstr "Arbolgema de Esmeralda"
#: ../../TShockAPI/Commands.cs:1879
msgid "Enabled halloween mode."
-msgstr ""
+msgstr "Modo halloween activado."
#: ../../TShockAPI/Commands.cs:1898
msgid "Enabled xmas mode."
-msgstr ""
+msgstr "Modo navideño activado."
#: ../../TShockAPI/Commands.cs:488
msgid "Enables starting and stopping various world events."
-msgstr ""
+msgstr "Permite iniciar y detener varios eventos del mundo."
#: ../../TShockAPI/DB/GroupManager.cs:665
#, csharp-format
@@ -2937,22 +2937,22 @@ msgstr "Error: los dos nombres son iguales."
#: ../../TShockAPI/Commands.cs:2777
msgid "Everscream"
-msgstr ""
+msgstr "Gritoeterno"
#: ../../TShockAPI/Commands.cs:1407
#, csharp-format
msgid "Example usage: {0}"
-msgstr ""
+msgstr "Ejemplo de uso: {0}"
#: ../../TShockAPI/Commands.cs:5463
#, csharp-format
msgid "Example usage: {0} \"{1}\" \"{2}\""
-msgstr ""
+msgstr "Ejemplo de uso: {0} \"{1}\" \"{2}\""
#: ../../TShockAPI/Commands.cs:6369
#, csharp-format
msgid "Example usage: {0} \"{1}\" {2}"
-msgstr ""
+msgstr "Ejemplo de uso: {0} \"{1}\" {2}"
#: ../../TShockAPI/Commands.cs:1399
#: ../../TShockAPI/Commands.cs:1415
@@ -2962,7 +2962,7 @@ msgstr ""
#: ../../TShockAPI/Commands.cs:6012
#, csharp-format
msgid "Example usage: {0} {1}"
-msgstr ""
+msgstr "Ejemplo de uso: {0} {1}"
#: ../../TShockAPI/Commands.cs:5520
#: ../../TShockAPI/Commands.cs:5685
@@ -2970,91 +2970,91 @@ msgstr ""
#: ../../TShockAPI/Commands.cs:6309
#, csharp-format
msgid "Example usage: {0} {1} {2}"
-msgstr ""
+msgstr "Ejemplo de uso: {0} {1} {2}"
#: ../../TShockAPI/Commands.cs:6419
#, csharp-format
msgid "Example usage: {0} {1} {2} {3}"
-msgstr ""
+msgstr "Ejemplo de uso: {0} {1} {2} {3}"
#: ../../TShockAPI/Commands.cs:1391
#, csharp-format
msgid "Example usage: {0} {1} {2} {3} {4}"
-msgstr ""
+msgstr "Ejemplo de uso: {0} {1} {2} {3} {4}"
#: ../../TShockAPI/Commands.cs:5601
#, csharp-format
msgid "Example usage: {0} <{1}> <{2}>"
-msgstr ""
+msgstr "Ejemplo de uso: {0} <{1}> <{2}>"
#: ../../TShockAPI/Commands.cs:2002
msgid "Example: /sudo /ban add particles 2d Hacking."
-msgstr ""
+msgstr "Ejemplo: /sudo /ban add particles 2d Hackeando."
#: ../../TShockAPI/Commands.cs:3206
#, csharp-format
msgid "Examples: {0}warp add foobar, {0}warp hide foobar true, {0}warp foobar."
-msgstr ""
+msgstr "Ejemplos: {0}warp add foobar, {0}warp hide foobar true, {0}warp foobar."
#: ../../TShockAPI/Commands.cs:328
msgid "Executes a command as the super admin."
-msgstr ""
+msgstr "Ejecuta un comando como súper admin."
#: ../../TShockAPI/GetDataHandlers.cs:4371
msgid "Exploit attempt detected!"
-msgstr ""
+msgstr "¡Intento de exploit detectado!"
#: ../../TShockAPI/Commands.cs:1494
#, csharp-format
msgid "Failed to add ban for identifier: {0}."
-msgstr "No se ha podido agregar el ban para el identificador {0}."
+msgstr "No se pudo agregar el ban para el identificador {0}."
#: ../../TShockAPI/Rest/RestManager.cs:671
#, csharp-format
msgid "Failed to add ban. {0}"
-msgstr "Error al agregar el ban. {0}"
+msgstr "No se pudo agregar el ban. {0}"
#: ../../TShockAPI/DB/GroupManager.cs:324
#, csharp-format
msgid "Failed to add group {0}."
-msgstr "No se ha podido agregar el grupo {0}."
+msgstr "No se pudo agregar el grupo {0}."
#: ../../TShockAPI/DB/GroupManager.cs:512
#: ../../TShockAPI/DB/GroupManager.cs:513
#, csharp-format
msgid "Failed to delete group {0}."
-msgstr "No se ha podido eliminar el grupo {0}."
+msgstr "No se pudo eliminar el grupo {0}."
#: ../../TShockAPI/Commands.cs:2524
msgid "Failed to find any users by that name on the list."
-msgstr "No se ha podido encontrar ningún usuario con este nombre en la lista."
+msgstr "No se pudo encontrar ningún usuario con ese nombre en la lista."
#: ../../TShockAPI/Commands.cs:1638
#: ../../TShockAPI/Rest/RestManager.cs:698
msgid "Failed to remove ban."
-msgstr "Error al eliminar el ban."
+msgstr "No se pudo eliminar el baneo."
#: ../../TShockAPI/DB/GroupManager.cs:480
#, csharp-format
msgid "Failed to rename group {0}."
-msgstr "Error al renombrar el grupo {0}."
+msgstr "No se pudo renombrar el grupo {0}."
#: ../../TShockAPI/Commands.cs:5145
msgid "Failed to rename the region."
-msgstr "Error al renombrar la región."
+msgstr "No se pudo renombrar la región."
#: ../../TShockAPI/Bouncer.cs:2685
msgid "Failed to shade polygon normals."
-msgstr ""
+msgstr "Error al sombrear las normales del polígono."
#: ../../TShockAPI/DB/GroupManager.cs:369
#, csharp-format
msgid "Failed to update group \"{0}\"."
-msgstr "Error al actualizar el grupo \"{0}\"."
+msgstr "No se pudo actualizar el grupo \"{0}\"."
#: ../../TShockAPI/Commands.cs:1864
msgid "Failed to upload your character data to the server. Are you logged-in to an account?"
-msgstr "Error al subir los datos de tu personaje al servidor. ¿Has iniciado sesión en una cuenta?"
+msgstr "No se pudo subir tus datos de personaje al servidor. ¿Iniciaste sesión en una cuenta?"
#: ../../TShockAPI/Rest/Rest.cs:245
msgid "Fatal Startup Exception"
@@ -3067,20 +3067,20 @@ msgstr "Excepción fatal en la inicialización de TShock: no se pudo conectar a
#: ../../TShockAPI/DB/UserManager.cs:218
#, csharp-format
msgid "FetchHashedPasswordAndGroup SQL returned an error: {0}"
-msgstr ""
+msgstr "SQL FetchHashedPasswordAndGroup devolvió un error: {0}"
#: ../../TShockAPI/Commands.cs:5683
msgid "Firework Syntax"
-msgstr ""
+msgstr "Sintaxis de Firework"
#: ../../TShockAPI/Commands.cs:1944
msgid "For example, 1d and 10h-30m+2m are both valid time strings, but 2 is not."
-msgstr ""
+msgstr "Por ejemplo, 1d y 10h-30m+2m son ambas cadenas de tiempo válidas, pero 2 no lo es."
#: ../../TShockAPI/Commands.cs:1374
#, csharp-format
msgid "For more info, use {0} {1} or {2} {3}"
-msgstr ""
+msgstr "Para más información, usa {0} {1} o {2} {3}"
#: ../../TShockAPI/Commands.cs:514
msgid "Forces all liquids to update immediately."
@@ -3090,15 +3090,15 @@ msgstr "Hace que todos los líquidos se actualicen inmediatamente."
#, csharp-format
msgid "Gave {0} {1} {2}."
msgid_plural "Gave {0} {1} {2}s."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Dio {0} {1} {2}."
+msgstr[1] "Dio {0} {1} {2}s."
#: ../../TShockAPI/Commands.cs:6140
#, csharp-format
msgid "Gave {0} {1}."
msgid_plural "Gave {0} {1}s."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Dio {0} {1}."
+msgstr[1] "Dio {0} {1}s."
#: ../../TShockAPI/GetDataHandlers.cs:3796
#, csharp-format
@@ -3473,11 +3473,11 @@ msgstr ""
#: ../../TShockAPI/DB/UserManager.cs:291
#, csharp-format
msgid "GetUser SQL returned an error {0}"
-msgstr "SQL GetUser devolvió un error ({0}"
+msgstr "SQL GetUser devolvió un error {0}"
#: ../../TShockAPI/Commands.cs:6417
msgid "Give Buff Syntax and Example"
-msgstr ""
+msgstr "Sintaxis y Ejemplo de Give Buff"
#: ../../TShockAPI/Commands.cs:541
msgid "Gives another player a buff or debuff for an amount of time. Putting -1 for time will set it to 415 days."
@@ -3497,12 +3497,12 @@ msgstr "Te da un objeto."
#: ../../TShockAPI/Commands.cs:6690
msgid "Glowing Mushroom Tree"
-msgstr ""
+msgstr "Árbol de Champiñón Brillante"
#: ../../TShockAPI/Handlers/LandGolfBallInCupHandler.cs:123
#, csharp-format
msgid "GolfPacketHandler: Player did not have create a golf club projectile the last 5 seconds! - From {0}"
-msgstr ""
+msgstr "GolfPacketHandler: ¡El jugador no había creado un proyectil de palo de golf en los últimos 5 segundos! - De {0}"
#: ../../TShockAPI/Commands.cs:3506
#, csharp-format
@@ -3604,7 +3604,7 @@ msgstr "El grupo {0} se agregó correctamente."
#: ../../TShockAPI/Commands.cs:3454
msgid "Group Sub-Commands ({{0}}/{{1}}):"
-msgstr ""
+msgstr "Sub-Comandos de Group ({{0}}/{{1}}):"
#: ../../TShockAPI/Commands.cs:3745
msgid "Groups ({{0}}/{{1}}):"
@@ -3616,12 +3616,12 @@ msgstr "Crece plantas en tu ubicación."
#: ../../TShockAPI/Commands.cs:6631
msgid "Hallow Palm"
-msgstr ""
+msgstr "Palmera Sagrada"
#: ../../TShockAPI/GetDataHandlers.cs:4372
#, csharp-format
msgid "HandleSyncCavernMonsterType: Player is trying to modify NPC cavernMonsterType; this is a crafted packet! - From {0}"
-msgstr ""
+msgstr "HandleSyncCavernMonsterType: El jugador está intentando modificar el cavernMonsterType de PNJ; ¡este es un paquete creado artificialmente! - De {0}"
#: ../../TShockAPI/Commands.cs:2593
msgid "Hardmode is disabled in the server configuration file."
@@ -3637,16 +3637,16 @@ msgstr "Se ha activado el modo difícil."
#: ../../TShockAPI/Commands.cs:6307
msgid "Heal Syntax and Example"
-msgstr ""
+msgstr "Sintaxis y Ejemplo de Heal"
#: ../../TShockAPI/Bouncer.cs:2097
msgid "HealOtherPlayer cheat attempt!"
-msgstr ""
+msgstr "¡Intento de trampa con HealOtherPlayer!"
#: ../../TShockAPI/Bouncer.cs:2106
#, csharp-format
msgid "HealOtherPlayer threshold exceeded {0}."
-msgstr ""
+msgstr "Umbral de HealOtherPlayer excedió {0}."
#: ../../TShockAPI/Commands.cs:549
msgid "Heals a player in HP and MP."
@@ -3654,7 +3654,7 @@ msgstr "Restaura los HP y MP del jugador."
#: ../../TShockAPI/Commands.cs:6684
msgid "Herb"
-msgstr ""
+msgstr "Hierba"
#: ../../TShockAPI/Commands.cs:4658
msgid "Hit a block to get the name of the region."
@@ -3669,7 +3669,7 @@ msgstr "Golpea un bloque para definir el punto {0}."
#: ../../TShockAPI/ItemBans.cs:163
#, csharp-format
msgid "holding banned item: {0}"
-msgstr "Tienes un objeto baneado: {0}"
+msgstr "sosteniendo un objeto prohibido: {0}"
#: ../../TShockAPI/Commands.cs:1235
#, csharp-format
@@ -3678,100 +3678,100 @@ msgstr "ID: {0}"
#: ../../TShockAPI/Commands.cs:6310
msgid "If no amount is specified, it will default to healing the target player by their max HP."
-msgstr "Si no se especifica una cantidad el jugador se curará hasta su vida máxima."
+msgstr "Si no se especifica una cantidad el jugador curará toda su vida."
#: ../../TShockAPI/Bouncer.cs:1392
msgid "If this player wasn't hacking, please report the damage threshold they were disabled for to TShock so we can improve this!"
-msgstr ""
+msgstr "Si este jugador no estaba haciendo trampa, ¡favor reportar a TShock el umbral de daño por el que fue deshabilitado para mejorar el sistema!"
#: ../../TShockAPI/Bouncer.cs:2113
msgid "If this player wasn't hacking, please report the HealOtherPlayer threshold they were disabled for to TShock so we can improve this!"
-msgstr ""
+msgstr "Si este jugador no estaba haciendo trampa, ¡favor reportar a TShock el umbral de HealOtherPlayer por el que fue deshabilitado para mejorar el sistema!"
#: ../../TShockAPI/Bouncer.cs:1295
msgid "If this player wasn't hacking, please report the projectile create threshold they were disabled for to TShock so we can improve this!"
-msgstr ""
+msgstr "Si este jugador no estaba haciendo trampa, ¡favor reportar a TShock el umbral de proyectiles creados por el que fue deshabilitado para mejorar el sistema!"
#: ../../TShockAPI/Bouncer.cs:927
msgid "If this player wasn't hacking, please report the tile kill threshold they were disabled for to TShock so we can improve this!"
-msgstr ""
+msgstr "Si este jugador no estaba haciendo trampa, ¡favor reportar a TShock el umbral de destrucción de bloques por el que fue deshabilitado para mejorar el sistema!"
#: ../../TShockAPI/Bouncer.cs:1695
msgid "If this player wasn't hacking, please report the tile liquid threshold they were disabled for to TShock so we can improve this!"
-msgstr ""
+msgstr "Si este jugador no estaba haciendo trampa, ¡favor reportar a TShock el umbral de bloques líquidos por el que fue deshabilitado para mejorar el sistema!"
#: ../../TShockAPI/Bouncer.cs:945
msgid "If this player wasn't hacking, please report the tile place threshold they were disabled for to TShock so we can improve this!"
-msgstr ""
+msgstr "Si este jugador no estaba haciendo trampa, ¡favor reportar a TShock el umbral de colocación de bloques por el que fue deshabilitado para mejorar el sistema!"
#: ../../TShockAPI/GetDataHandlers.cs:3026
msgid "If this was not skeletron prime related, please report to TShock what happened."
-msgstr ""
+msgstr "Si esto no estaba relacionado con Esqueletrón Prime, por favor reporta a TShock lo ocurrido."
#: ../../TShockAPI/Commands.cs:5377
msgid "If you are locked out of all admin accounts, ask for help on https://tshock.co/"
-msgstr ""
+msgstr "Si perdiste el acceso a todas las cuentas de administrador, pide ayuda en https://tshock.co/"
#: ../../TShockAPI/Commands.cs:5817
#, csharp-format
msgid "If you do not specify a radius, it will use a default radius of {0} around your character."
-msgstr ""
+msgstr "Si no especificas un radio, usará uno predeterminado de {0} alrededor de tu personaje."
#: ../../TShockAPI/Commands.cs:6370
#, csharp-format
msgid "If you don't specify the duration, it will default to {0} seconds."
-msgstr ""
+msgstr "Si no especificas una duración, se usara la predeterminada de {0} segundos."
#: ../../TShockAPI/Commands.cs:857
msgid "If you forgot your password, contact the administrator for help."
-msgstr ""
+msgstr "Si olvidaste tu contraseña, pide ayuda al administrador."
#: ../../TShockAPI/Commands.cs:6371
#, csharp-format
msgid "If you put {0} as the duration, it will use the max possible time of 415 days."
-msgstr ""
+msgstr "Si estableces {0} como la duración, usará el máximo tiempo posible que es 415 días."
#: ../../TShockAPI/Commands.cs:5416
#, csharp-format
msgid "If you understand, please {0}login now, and then type {0}setup."
-msgstr ""
+msgstr "Si has entendido, por favor usa {0}login , y a continuación teclea {0}setup."
#: ../../TShockPluginManager/NugetCLI.cs:142
msgid "If you want to know which plugins need which dependencies, press E."
-msgstr ""
+msgstr "Si deseas saber qué plugins necesitan qué dependencias, pulsa E."
#: ../../TShockPluginManager/NugetCLI.cs:162
msgid "If you'd like to see which plugins need which dependencies again, press E."
-msgstr ""
+msgstr "Si deseas ver nuevamente qué plugins necesitan qué dependencias, pulsa E."
#: ../../TShockAPI/Bouncer.cs:986
msgid "If you're seeing this message and you know what that player did, please report it to TShock for further investigation."
-msgstr ""
+msgstr "Si ves este mensaje y sabes lo que hizo ese jugador, favor reportar a TShock para investigar más a fondo."
#: ../../TShockAPI/Bouncer.cs:1341
msgid "Ignoring shrapnel per config.."
-msgstr ""
+msgstr "Ignorando esquirlas de acuerdo a la configuración.."
#: ../../TShockAPI/GetDataHandlers.cs:2465
msgid "Illegal name: prefixes tsi: and tsn: are forbidden."
-msgstr ""
+msgstr "Nombre ilegal: los prefijos tsi: y tsn: están prohibidos."
#: ../../TShockAPI/Handlers/IllegalPerSe/EmojiPlayerMismatch.cs:19
#, csharp-format
msgid "IllegalPerSe: Emoji packet rejected for ID spoofing. Expected {0}, received {1} from {2}."
-msgstr ""
+msgstr "IllegalPerSe: Paquete de emoji rechazado por falsificación de ID. Esperado {0}, recibido {1} de {2}."
#: ../../TShockAPI/Commands.cs:3189
msgid "Incoming teleports are now allowed."
-msgstr ""
+msgstr "Ahora se permiten teletransportes entrantes."
#: ../../TShockAPI/Commands.cs:3191
msgid "Incoming teleports are now disabled."
-msgstr ""
+msgstr "Ahora se bloquean teletransportes entrantes."
#: ../../TShockAPI/Commands.cs:5403
msgid "Incorrect setup code. This incident has been logged."
-msgstr "El código de configuración es incorrecto. Este incidente se ha reportado."
+msgstr "Código de configuración incorrecto. Incidente archivado en el registro."
#: ../../TShockAPI/DB/TileManager.cs:223
#: ../../TShockAPI/DB/ProjectileManager.cs:223
@@ -3781,12 +3781,12 @@ msgstr "Grupo infinito de jerarquía ({0})"
#: ../../TShockAPI/Commands.cs:5197
msgid "info [-d] - Displays several information about the given region."
-msgstr ""
+msgstr "info [-d] - Muestra bastante información sobre la región dada."
#: ../../TShockAPI/Commands.cs:4986
#, csharp-format
msgid "Information About Region \"{0}\" ({{0}}/{{1}}):"
-msgstr "Información sobre la Región \"{0}\" ({{0}}/{{1}}):"
+msgstr "Información Sobre la Región \"{0}\" ({{0}}/{{1}}):"
#: ../../TShockAPI/Commands.cs:1232
msgid "Information about the currently running world"
@@ -3798,7 +3798,7 @@ msgstr "Se ha producido un error al insertar el ban en la base de datos."
#: ../../TShockPluginManager/NugetCLI.cs:39
msgid "Install the plugins as specified in the plugins.json"
-msgstr ""
+msgstr "Instala los plugins como se especifica en plugins.json"
#: ../../TShockAPI/Rest/Rest.cs:426
msgid "Internal server error."
@@ -3807,26 +3807,26 @@ msgstr "Error interno del servidor."
#: ../../TShockAPI/Commands.cs:1505
#, csharp-format
msgid "Invalid Ban Add syntax. Refer to {0} for details on how to use the {1} command"
-msgstr ""
+msgstr "Sintaxis de Ban Add inválida. Revisa {0} para ver detalles de uso para el comando {1}"
#: ../../TShockAPI/Commands.cs:1621
#, csharp-format
msgid "Invalid Ban Del syntax. Refer to {0} for details on how to use the {1} command"
-msgstr ""
+msgstr "Sintaxis de Ban Del inválida. Revisa {0} para ver detalles de uso para el comando {1}"
#: ../../TShockAPI/Commands.cs:1680
#, csharp-format
msgid "Invalid Ban Details syntax. Refer to {0} for details on how to use the {1} command"
-msgstr ""
+msgstr "Sintaxis de Ban Details inválida. Revisa {0} para ver detalles de uso para el comando {1}"
#: ../../TShockAPI/Commands.cs:1658
#, csharp-format
msgid "Invalid Ban List syntax. Refer to {0} for details on how to use the {1} command"
-msgstr ""
+msgstr "Sintaxis de Ban List inválida. Revisa {0} para ver detalles de uso para el comando {1}"
#: ../../TShockAPI/DB/UserManager.cs:500
msgid "Invalid BCrypt work factor in config file! Creating new hash using default work factor."
-msgstr ""
+msgstr "¡Factor de trabajo BCrypt inválido en el archivo de configuración! Creando nuevo hash usando el factor de trabajo predeterminado."
#: ../../TShockAPI/Commands.cs:2608
msgid "Invalid boss amount."
@@ -3834,11 +3834,11 @@ msgstr "La cantidad de jefes no es válida."
#: ../../TShockAPI/Commands.cs:2821
msgid "Invalid boss type!"
-msgstr "El tipo de jefe no es válido."
+msgstr "¡El tipo de jefe no es válido!"
#: ../../TShockAPI/Commands.cs:6470
msgid "Invalid buff ID!"
-msgstr "El ID del buff no es válido."
+msgstr "¡El ID de buff no es válido!"
#: ../../TShockAPI/Commands.cs:680
#, csharp-format
@@ -3847,27 +3847,27 @@ msgstr "Se ha introducido un comando inválido. Escriba {0}help para obtener una
#: ../../TShockAPI/Commands.cs:5275
msgid "Invalid command."
-msgstr ""
+msgstr "Comando inválido."
#: ../../TShockAPI/Commands.cs:3131
msgid "Invalid destination NPC."
-msgstr "El NPC de destino no es válido."
+msgstr "El PNJ destino no es válido."
#: ../../TShockAPI/Commands.cs:2941
#: ../../TShockAPI/Commands.cs:2972
#: ../../TShockAPI/Commands.cs:3011
#: ../../TShockAPI/Commands.cs:3084
msgid "Invalid destination player."
-msgstr "El jugador de destino no es válido."
+msgstr "El jugador destino no es válido."
#: ../../TShockAPI/Commands.cs:2240
#, csharp-format
msgid "Invalid event type. Valid event types: {0}."
-msgstr ""
+msgstr "Tipo de evento inválido. Tipos de eventos válidos: {0}."
#: ../../TShockAPI/Commands.cs:2382
msgid "Invalid frost moon event wave."
-msgstr ""
+msgstr "Ronda inválida para el evento de luna helada."
#: ../../TShockAPI/Commands.cs:3765
#: ../../TShockAPI/Commands.cs:3868
@@ -3882,44 +3882,44 @@ msgstr "Grupo inválido."
#: ../../TShockAPI/Commands.cs:2401
#, csharp-format
msgid "Invalid invasion type. Valid invasion types: {0}."
-msgstr ""
+msgstr "Tipo de invasión inválido. Tipos válidos de invasión: {0}."
#: ../../TShockAPI/Commands.cs:6083
#: ../../TShockAPI/Commands.cs:6232
#: ../../TShockAPI/Commands.cs:6295
msgid "Invalid item type!"
-msgstr ""
+msgstr "¡Tipo de objeto inválido!"
#: ../../TShockAPI/Commands.cs:3807
#: ../../TShockAPI/Commands.cs:3858
#: ../../TShockAPI/Commands.cs:3903
#: ../../TShockAPI/Commands.cs:3929
msgid "Invalid item."
-msgstr ""
+msgstr "Objeto inválido."
#: ../../TShockAPI/Commands.cs:4455
#, csharp-format
msgid "Invalid maximum spawns. Acceptable range is {0} to {1}."
-msgstr ""
+msgstr "Máximo de enemigos generados inválido. El rango aceptable es de {0} a {1}."
#: ../../TShockAPI/Commands.cs:2860
#: ../../TShockAPI/Commands.cs:6166
msgid "Invalid mob type!"
-msgstr ""
+msgstr "¡Tipo de enemigo inválido!"
#: ../../TShockAPI/Commands.cs:2844
#: ../../TShockAPI/Commands.cs:2900
msgid "Invalid mob type."
-msgstr ""
+msgstr "Tipo de enemigo inválido."
#: ../../TShockAPI/Commands.cs:2569
#, csharp-format
msgid "Invalid mode world mode. Valid modes: {0}"
-msgstr ""
+msgstr "Modo de mundo inválido. Modos válidos: {0}"
#: ../../TShockAPI/Commands.cs:1421
msgid "Invalid page number. Page number must be numeric."
-msgstr ""
+msgstr "Número de página inválido. La cifra debe ser numérica."
#: ../../TShockAPI/DB/GroupManager.cs:309
#, csharp-format
@@ -3933,12 +3933,12 @@ msgstr "Grupo padre {0} inválido para el grupo {1}."
#: ../../TShockAPI/Commands.cs:928
msgid "Invalid password."
-msgstr "Contraseña invalida."
+msgstr "Contraseña inválida."
#: ../../TShockAPI/Commands.cs:6263
#: ../../TShockAPI/Commands.cs:6718
msgid "Invalid player!"
-msgstr ""
+msgstr "¡Jugador inválido!"
#: ../../TShockAPI/Commands.cs:1255
msgid "Invalid player."
@@ -3946,7 +3946,7 @@ msgstr "Jugador inválido."
#: ../../TShockAPI/Commands.cs:4040
msgid "Invalid projectile ID!"
-msgstr "ID de proyectil inválido."
+msgstr "¡ID de proyectil inválido!"
#: ../../TShockAPI/Commands.cs:4077
#: ../../TShockAPI/Commands.cs:4098
@@ -3956,12 +3956,12 @@ msgstr "ID de proyectil inválido."
#: ../../TShockAPI/Commands.cs:2364
msgid "Invalid pumpkin moon event wave."
-msgstr ""
+msgstr "Ronda inválida para el evento de luna calabaza."
#: ../../TShockAPI/Commands.cs:5127
#, csharp-format
msgid "Invalid region \"{0}\"."
-msgstr ""
+msgstr "Región \"{0}\" inválida."
#: ../../TShockAPI/Rest/Rest.cs:247
#, csharp-format
@@ -3969,386 +3969,389 @@ msgid "Invalid REST configuration: \n"
"You may already have a REST service bound to port {0}. \n"
"Please adjust your configuration and restart the server. \n"
"Press any key to exit."
-msgstr ""
+msgstr "Configuración REST inválida: \n"
+"Puede que tengas un servicio REST enlazado al puerto {0}. \n"
+"Por favor ajusta tu configuración y reinicia el servidor. \n"
+"Presiona cualquier tecla para salir."
#: ../../TShockAPI/GetDataHandlers.cs:3231
msgid "Invalid server password."
-msgstr ""
+msgstr "Contraseña de servidor inválida."
#: ../../TShockAPI/Commands.cs:3782
#, csharp-format
msgid "Invalid subcommand! Type {0}group help for more information on valid commands."
-msgstr ""
+msgstr "¡Subcomando inválido! Teclea {0}group help para más información sobre comandos válidos."
#: ../../TShockAPI/Commands.cs:4009
#, csharp-format
msgid "Invalid subcommand. Type {0}itemban help for more information on valid subcommands."
-msgstr ""
+msgstr "¡Subcomando inválido! Teclea {0}itemban help para más información sobre subcomandos válidos."
#: ../../TShockAPI/Commands.cs:4187
#, csharp-format
msgid "Invalid subcommand. Type {0}projban help for more information on valid subcommands."
-msgstr ""
+msgstr "¡Subcomando inválido! Teclea {0}projban help para más información sobre subcomandos válidos."
#: ../../TShockAPI/Commands.cs:4363
#, csharp-format
msgid "Invalid subcommand. Type {0}tileban help for more information on valid subcommands."
-msgstr ""
+msgstr "¡Subcomando inválido! Teclea {0}tileban help para más información sobre subcomandos válidos."
#: ../../TShockAPI/Commands.cs:3641
msgid "Invalid syntax for color, expected \"rrr,ggg,bbb\"."
-msgstr ""
+msgstr "Sintaxis incorrecta para color, se esperaba \"rrr,vvv,aaa\" (rojo, verde, azul)."
#: ../../TShockAPI/Commands.cs:1915
msgid "Invalid syntax."
-msgstr "Sintáxis inválida."
+msgstr "Sintaxis inválida."
#: ../../TShockAPI/Commands.cs:2332
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}worldevent invasion [invasion type] [invasion wave]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}worldevent invasion [tipo de invasión] [ronda de invasión]."
#: ../../TShockAPI/Commands.cs:1270
#: ../../TShockAPI/Commands.cs:1304
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}accountinfo ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}accountinfo ."
#: ../../TShockAPI/Commands.cs:5754
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}aliases "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}aliases "
#: ../../TShockAPI/Commands.cs:3369
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group add [permissions]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group add [permisos]."
#: ../../TShockAPI/Commands.cs:3398
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group addperm ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group addperm ."
#: ../../TShockAPI/Commands.cs:3606
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group color [new color(000,000,000)]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group color [color nuevo(000,000,000)]."
#: ../../TShockAPI/Commands.cs:3679
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group del ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group del ."
#: ../../TShockAPI/Commands.cs:3703
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group delperm ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group delperm ."
#: ../../TShockAPI/Commands.cs:3756
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group listperm [page]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group listperm [página]."
#: ../../TShockAPI/Commands.cs:3466
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group parent [new parent group name]."
-msgstr "Sintaxis no válida. Sintaxis correcta: {0}grupo parent [nuevo nombre del grupo padre]."
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group parent [nuevo nombre del grupo padre]."
#: ../../TShockAPI/Commands.cs:3561
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group prefix [new prefix]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group prefix [prefijo nuevo]."
#: ../../TShockAPI/Commands.cs:3656
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group rename ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group rename ."
#: ../../TShockAPI/Commands.cs:3516
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}group suffix [new suffix]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}group suffix [sufijo nuevo]."
#: ../../TShockAPI/Commands.cs:5241
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}help "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}help "
#: ../../TShockAPI/Commands.cs:6058
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}item
- [item amount] [prefix id/name]"
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}item [cantidad del objeto] [id/nombre del prefijo]"
#: ../../TShockAPI/Commands.cs:3800
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}itemban add
- ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}itemban add ."
#: ../../TShockAPI/Commands.cs:3851
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}itemban allow
- ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}itemban allow ."
#: ../../TShockAPI/Commands.cs:3896
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}itemban del
- ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}itemban del ."
#: ../../TShockAPI/Commands.cs:3922
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}itemban disallow
- ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}itemban disallow ."
#: ../../TShockAPI/Commands.cs:1311
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}kick [reason]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}kick [motivo]."
#: ../../TShockAPI/Commands.cs:5424
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}me "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}me "
#: ../../TShockAPI/Commands.cs:5437
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}p "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}p "
#: ../../TShockAPI/Commands.cs:4030
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}projban add "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}projban add "
#: ../../TShockAPI/Commands.cs:4049
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}projban allow ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}projban allow ."
#: ../../TShockAPI/Commands.cs:4086
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}projban del ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}projban del ."
#: ../../TShockAPI/Commands.cs:4107
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}projban disallow ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}projban disallow ."
#: ../../TShockAPI/Commands.cs:4796
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region allow ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region allow ."
#: ../../TShockAPI/Commands.cs:4866
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region allowg ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region allowg ."
#: ../../TShockAPI/Commands.cs:4710
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region define ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region define ."
#: ../../TShockAPI/Commands.cs:4752
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region delete ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region delete ."
#: ../../TShockAPI/Commands.cs:4925
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region info [-d] [page]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region info [-d] [página]."
#: ../../TShockAPI/Commands.cs:4733
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region protect ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region protect ."
#: ../../TShockAPI/Commands.cs:4831
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region remove ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region remove ."
#: ../../TShockAPI/Commands.cs:4901
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region removeg ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region removeg ."
#: ../../TShockAPI/Commands.cs:5109
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region rename "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region rename "
#: ../../TShockAPI/Commands.cs:5099
#: ../../TShockAPI/Commands.cs:5102
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region resize "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region resize "
#: ../../TShockAPI/Commands.cs:5159
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region tp ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region tp ."
#: ../../TShockAPI/Commands.cs:5047
#: ../../TShockAPI/Commands.cs:5050
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}region z <#>"
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}region z <#>"
#: ../../TShockAPI/Commands.cs:1037
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}register ."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}register ."
#: ../../TShockAPI/Commands.cs:6157
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}renameNPC "
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}renameNPC "
#: ../../TShockAPI/Commands.cs:4402
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}serverpassword \"\"."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}serverpassword \"\"."
#: ../../TShockAPI/Commands.cs:4583
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}slap [damage]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}slap [daño]."
#: ../../TShockAPI/Commands.cs:2601
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}spawnboss [amount]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}spawnboss [cantidad]."
#: ../../TShockAPI/Commands.cs:2839
#: ../../TShockAPI/Commands.cs:2851
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}spawnmob [amount]."
-msgstr ""
+msgstr "Sintaxis inválida. Sintaxis correcta: {0}spawnmob [cantidad]."
#: ../../TShockAPI/Commands.cs:4206
#, csharp-format
msgid "Invalid syntax. Proper syntax: {0}tileban add