Merge branch 'general-devel' into commandhooks

This commit is contained in:
Lucas Nicodemus 2025-01-27 10:06:01 +09:00 committed by GitHub
commit 97d88b3b3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1899 additions and 1069 deletions

View file

@ -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 # Ignore other specific files that aren't needed for the build itself.
!i18n/ /.all-contributorsrc
!prebuilts/ /.dockerignore
!TerrariaServerAPI/ /.editorconfig
!TShockAPI/ /.vscode
!TShockLauncher/ /appveyor.yml
!TShockLauncher.Tests/ /COPYING
!TShock.sln /crowdin.yml
/Dockerfile
# but exclude build artifacts /docs
*/bin/ /README.md
*/obj/ /README_cn.md
/renovate.json
/scripts
/SECURITY.md

2
.github/FUNDING.yml vendored
View file

@ -1,2 +1,2 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [SignatureBeef, hakusaro, Stealownz, QuiCM] github: [SignatureBeef, QuiCM]

60
.github/workflows/ci-docker.yml vendored Normal file
View file

@ -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

View file

@ -1,10 +1,8 @@
ARG TARGETPLATFORM=linux/amd64 # TARGETPLATFORM and BUILDPLATFORM are automatically filled in by Docker buildx.
ARG BUILDPLATFORM=${TARGETPLATFORM} # They should not be set in the global scope manually.
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:6.0 AS builder FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:6.0 AS builder
ARG TARGETPLATFORM
# Copy build context # Copy build context
WORKDIR /TShock WORKDIR /TShock
COPY . ./ COPY . ./
@ -12,6 +10,10 @@ COPY . ./
# Build and package release based on target architecture # Build and package release based on target architecture
RUN dotnet build -v m RUN dotnet build -v m
WORKDIR /TShock/TShockLauncher WORKDIR /TShock/TShockLauncher
# Make TARGETPLATFORM available to the container.
ARG TARGETPLATFORM
RUN \ RUN \
case "${TARGETPLATFORM}" in \ case "${TARGETPLATFORM}" in \
"linux/amd64") export ARCH="linux-x64" \ "linux/amd64") export ARCH="linux-x64" \

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="https://tshock.co/newlogo.png" alt="TShock for Terraria"><br /> <img src="https://tshock.s3.us-west-001.backblazeb2.com/newlogo.png" alt="TShock for Terraria"><br />
<a href="https://ci.appveyor.com/project/hakusaro/tshock"> <a href="https://ci.appveyor.com/project/hakusaro/tshock">
<img src="https://ci.appveyor.com/api/projects/status/chhe61q227lqdlg1?svg=true" alt="AppVeyor Build Status"> <img src="https://ci.appveyor.com/api/projects/status/chhe61q227lqdlg1?svg=true" alt="AppVeyor Build Status">
</a> </a>

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="https://tshock.co/newlogo.png" alt="TShock for Terraria"><br /> <img src="https://tshock.s3.us-west-001.backblazeb2.com/newlogo.png" alt="TShock for Terraria"><br />
<a href="https://ci.appveyor.com/project/hakusaro/tshock"> <a href="https://ci.appveyor.com/project/hakusaro/tshock">
<img src="https://ci.appveyor.com/api/projects/status/chhe61q227lqdlg1?svg=true" alt="AppVeyor Build Status"> <img src="https://ci.appveyor.com/api/projects/status/chhe61q227lqdlg1?svg=true" alt="AppVeyor Build Status">
</a> </a>

View file

@ -424,7 +424,7 @@ namespace TShockAPI
}; };
PlayerAddBuffWhitelist[BuffID.BrainOfConfusionBuff] = new BuffLimit PlayerAddBuffWhitelist[BuffID.BrainOfConfusionBuff] = new BuffLimit
{ {
MaxTicks = 240, MaxTicks = 60 * 4,
CanBeAddedWithoutHostile = true, CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true CanOnlyBeAppliedToSender = true
}; };
@ -434,6 +434,12 @@ namespace TShockAPI
CanBeAddedWithoutHostile = true, CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true CanOnlyBeAppliedToSender = true
}; };
PlayerAddBuffWhitelist[BuffID.ParryDamageBuff] = new BuffLimit
{
MaxTicks = 60 * 5,
CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true
};
#endregion Whitelist #endregion Whitelist
} }
@ -1878,7 +1884,7 @@ namespace TShockAPI
return; return;
} }
if (TShock.Players[id] == null) if (TShock.Players[id] == null || !TShock.Players[id].Active)
{ {
TShock.Log.ConsoleDebug(GetString( TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: target is null", args.Player.Name, "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; short amount = args.Amount;
byte plr = args.TargetPlayerIndex; 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")); TShock.Log.ConsoleDebug(GetString("Bouncer / OnHealOtherPlayer rejected null checks"));
args.Handled = true; args.Handled = true;
@ -2589,7 +2595,7 @@ namespace TShockAPI
byte direction = args.Direction; byte direction = args.Direction;
PlayerDeathReason reason = args.PlayerDeathReason; 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")); TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerDamage rejected null check"));
args.Handled = true; args.Handled = true;
@ -2854,7 +2860,7 @@ namespace TShockAPI
{ BuffID.Poisoned, 3600 }, // BuffID: 20 { BuffID.Poisoned, 3600 }, // BuffID: 20
{ BuffID.OnFire, 1200 }, // BuffID: 24 { BuffID.OnFire, 1200 }, // BuffID: 24
{ BuffID.Confused, short.MaxValue }, // BuffID: 31 Brain of Confusion Internal Item ID: 3223 { 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.Frostburn, 900 }, // BuffID: 44
{ BuffID.Ichor, 1200 }, // BuffID: 69 { BuffID.Ichor, 1200 }, // BuffID: 69
{ BuffID.Venom, 1800 }, // BuffID: 70 { BuffID.Venom, 1800 }, // BuffID: 70

View file

@ -1186,7 +1186,7 @@ namespace TShockAPI
try 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])); 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])); args.Player.SendSuccessMessage(GetString("Account {0} has been changed to group {1}.", account.Name, args.Parameters[2]));
@ -1203,6 +1203,10 @@ namespace TShockAPI
{ {
args.Player.SendErrorMessage(GetString($"User {account.Name} does not exist.")); 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) catch (UserAccountManagerException e)
{ {
args.Player.SendErrorMessage(GetString($"User {account.Name} could not be added. Check console for details.")); args.Player.SendErrorMessage(GetString($"User {account.Name} could not be added. Check console for details."));
@ -2054,6 +2058,7 @@ namespace TShockAPI
private static void OffNoSave(CommandArgs args) private static void OffNoSave(CommandArgs args)
{ {
string reason = ((args.Parameters.Count > 0) ? GetString("Server shutting down: ") + String.Join(" ", args.Parameters) : GetString("Server shutting down.")); 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); TShock.Utils.StopServer(false, reason);
} }
@ -3080,12 +3085,12 @@ namespace TShockAPI
args.Player.SendErrorMessage(GetString("You do not have permission to teleport all other players.")); args.Player.SendErrorMessage(GetString("You do not have permission to teleport all other players."));
return; 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)) if (player.Teleport(args.TPlayer.position.X, args.TPlayer.position.Y))
TShock.Players[i].SendSuccessMessage(GetString("You were teleported to {0}.", args.Player.Name)); player.SendSuccessMessage(GetString("You were teleported to {0}.", args.Player.Name));
} }
} }
args.Player.SendSuccessMessage(GetString("Teleported everyone to yourself.")); args.Player.SendSuccessMessage(GetString("Teleported everyone to yourself."));
@ -4632,21 +4637,22 @@ namespace TShockAPI
{ {
if (args.Parameters.Count != 1) if (args.Parameters.Count != 1)
{ {
args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}wind <speed>.", Specifier)); args.Player.SendErrorMessage(GetString("Invalid syntax. Proper syntax: {0}wind <speed in mph>.", Specifier));
return; return;
} }
int speed; float mph;
if (!int.TryParse(args.Parameters[0], out speed) || speed * 100 < 0) 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; return;
} }
float speed = mph / 50f; // -40 to 40 mph -> -0.8 to 0.8
Main.windSpeedCurrent = speed; Main.windSpeedCurrent = speed;
Main.windSpeedTarget = speed; Main.windSpeedTarget = speed;
TSPlayer.All.SendData(PacketTypes.WorldInfo); 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 #endregion Time/PvpFun Commands

View file

@ -320,8 +320,8 @@ namespace TShockAPI.Configuration
[Description("The reason given if banning a mediumcore player on death.")] [Description("The reason given if banning a mediumcore player on death.")]
public string MediumcoreBanReason = GetString("Death results in a ban"); public string MediumcoreBanReason = GetString("Death results in a ban");
/// <summary>Disbales IP bans by default, if no arguments are passed to the ban command.</summary> /// <summary>Disables IP bans by default, if no arguments are passed to the ban command.</summary>
[Description("Disbales IP bans by default, if no arguments are passed to the ban command.")] [Description("Disables IP bans by default, if no arguments are passed to the ban command.")]
public bool DisableDefaultIPBan; public bool DisableDefaultIPBan;
/// <summary>Enable or disable the whitelist based on IP addresses in the whitelist.txt file.</summary> /// <summary>Enable or disable the whitelist based on IP addresses in the whitelist.txt file.</summary>

View file

@ -25,6 +25,7 @@ using MySql.Data.MySqlClient;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using BCrypt.Net; using BCrypt.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using TShockAPI.Hooks;
namespace TShockAPI.DB namespace TShockAPI.DB
{ {
@ -166,7 +167,41 @@ namespace TShockAPI.DB
if (null == grp) if (null == grp)
throw new GroupNotExistsException(group); 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);
}
}
/// <summary>
/// Sets the group for a given username
/// </summary>
/// <param name="author">Who changes the group</param>
/// <param name="account">The user account</param>
/// <param name="group">The user account group to be set</param>
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); throw new UserAccountNotExistException(account.Name);
try try
@ -619,7 +654,7 @@ namespace TShockAPI.DB
public class UserAccountNotExistException : UserAccountManagerException public class UserAccountNotExistException : UserAccountManagerException
{ {
/// <summary>Creates a new UserAccountNotExistException object, with the user account name in the message.</summary> /// <summary>Creates a new UserAccountNotExistException object, with the user account name in the message.</summary>
/// <param name="name">The user account name to be pasesd in the message.</param> /// <param name="name">The user account name to be passed in the message.</param>
/// <returns>A new UserAccountNotExistException object with a message containing the user account name that does not exist.</returns> /// <returns>A new UserAccountNotExistException object with a message containing the user account name that does not exist.</returns>
public UserAccountNotExistException(string name) public UserAccountNotExistException(string name)
: base(GetString($"User account {name} does not exist")) : base(GetString($"User account {name} does not exist"))
@ -627,6 +662,20 @@ namespace TShockAPI.DB
} }
} }
/// <summary>The UserGroupUpdateLockedException used when the user group update failed and the request failed as a result.</summary>.
[Serializable]
public class UserGroupUpdateLockedException : UserAccountManagerException
{
/// <summary>Creates a new UserGroupUpdateLockedException object.</summary>
/// <param name="name">The name of the user who failed to change the group.</param>
/// <returns>New UserGroupUpdateLockedException object with a message containing the name of the user account that failed to change the group.</returns>
public UserGroupUpdateLockedException(string name) :
base(GetString($"Unable to update group of user {name}."))
{
}
}
/// <summary>A GroupNotExistsException, used when a group does not exist.</summary> /// <summary>A GroupNotExistsException, used when a group does not exist.</summary>
[Serializable] [Serializable]
public class GroupNotExistsException : UserAccountManagerException public class GroupNotExistsException : UserAccountManagerException

View file

@ -4445,6 +4445,11 @@ namespace TShockAPI
return true; 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, // 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 // 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. // swap around the PlayerData items ourself.

View file

@ -20,6 +20,8 @@ using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace TShockAPI namespace TShockAPI
{ {
/// <summary> /// <summary>
@ -52,17 +54,17 @@ namespace TShockAPI
/// <summary> /// <summary>
/// The group that this group inherits permissions from. /// The group that this group inherits permissions from.
/// </summary> /// </summary>
public Group Parent { get; set; } public virtual Group Parent { get; set; }
/// <summary> /// <summary>
/// The chat prefix for this group. /// The chat prefix for this group.
/// </summary> /// </summary>
public string Prefix { get; set; } public virtual string Prefix { get; set; }
/// <summary> /// <summary>
/// The chat suffix for this group. /// The chat suffix for this group.
/// </summary> /// </summary>
public string Suffix { get; set; } public virtual string Suffix { get; set; }
/// <summary> /// <summary>
/// The name of the parent, not particularly sure why this is here. /// The name of the parent, not particularly sure why this is here.
@ -164,6 +166,20 @@ namespace TShockAPI
/// </summary> /// </summary>
public byte B = 255; public byte B = 255;
/// <summary>
/// Simplifies work with the <see cref="R"/>, <see cref="G"/>, <see cref="B"/> properties.
/// </summary>
public virtual Color Color
{
get => new Color(R, G, B);
set
{
R = value.R;
G = value.G;
B = value.B;
}
}
/// <summary> /// <summary>
/// The default group attributed to unregistered users. /// The default group attributed to unregistered users.
/// </summary> /// </summary>
@ -242,7 +258,7 @@ namespace TShockAPI
/// Adds a permission to the list of negated permissions. /// Adds a permission to the list of negated permissions.
/// </summary> /// </summary>
/// <param name="permission">The permission to negate.</param> /// <param name="permission">The permission to negate.</param>
public void NegatePermission(string permission) public virtual void NegatePermission(string permission)
{ {
// Avoid duplicates // Avoid duplicates
if (!negatedpermissions.Contains(permission)) if (!negatedpermissions.Contains(permission))
@ -256,7 +272,7 @@ namespace TShockAPI
/// Adds a permission to the list of permissions. /// Adds a permission to the list of permissions.
/// </summary> /// </summary>
/// <param name="permission">The permission to add.</param> /// <param name="permission">The permission to add.</param>
public void AddPermission(string permission) public virtual void AddPermission(string permission)
{ {
if (permission.StartsWith("!")) if (permission.StartsWith("!"))
{ {
@ -276,7 +292,7 @@ namespace TShockAPI
/// will parse "!permission" and add it to the negated permissions. /// will parse "!permission" and add it to the negated permissions.
/// </summary> /// </summary>
/// <param name="permission">The new list of permissions to associate with the group.</param> /// <param name="permission">The new list of permissions to associate with the group.</param>
public void SetPermission(List<string> permission) public virtual void SetPermission(List<string> permission)
{ {
permissions.Clear(); permissions.Clear();
negatedpermissions.Clear(); negatedpermissions.Clear();
@ -288,7 +304,7 @@ namespace TShockAPI
/// where "!permission" will remove a negated permission. /// where "!permission" will remove a negated permission.
/// </summary> /// </summary>
/// <param name="permission"></param> /// <param name="permission"></param>
public void RemovePermission(string permission) public virtual void RemovePermission(string permission)
{ {
if (permission.StartsWith("!")) if (permission.StartsWith("!"))
{ {
@ -302,7 +318,7 @@ namespace TShockAPI
/// Assigns all fields of this instance to another. /// Assigns all fields of this instance to another.
/// </summary> /// </summary>
/// <param name="otherGroup">The other instance.</param> /// <param name="otherGroup">The other instance.</param>
public void AssignTo(Group otherGroup) public virtual void AssignTo(Group otherGroup)
{ {
otherGroup.Name = Name; otherGroup.Name = Name;
otherGroup.Parent = Parent; otherGroup.Parent = Parent;

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System.ComponentModel;
using TShockAPI.DB; using TShockAPI.DB;
namespace TShockAPI.Hooks 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
{
/// <summary>
/// The player who updated the user's group
/// </summary>
public TSPlayer Player { get; private set; }
public AccountGroupUpdateByPlayerEventArgs(TSPlayer player, string accountName, Group group) : base(accountName, group)
{
this.Player = player;
}
}
public class AccountHooks public class AccountHooks
{ {
public delegate void AccountCreateD(AccountCreateEventArgs e); public delegate void AccountCreateD(AccountCreateEventArgs e);
@ -62,5 +88,25 @@ namespace TShockAPI.Hooks
AccountDelete(new AccountDeleteEventArgs(u)); 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;
}
} }
} }

View file

@ -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; return CultureInfo.CurrentUICulture;
} }
} }

View file

@ -20,7 +20,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Terraria; using Terraria;
using Terraria.Initializers;
using Terraria.Localization; using Terraria.Localization;
using Terraria.UI.Chat;
namespace TShockAPI.Localization namespace TShockAPI.Localization
{ {
@ -37,6 +39,8 @@ namespace TShockAPI.Localization
private static readonly Dictionary<int, string> Buffs = new Dictionary<int, string>(); private static readonly Dictionary<int, string> Buffs = new Dictionary<int, string>();
private static readonly Dictionary<string,string> VanillaCommandsPrefixs = new Dictionary<string, string>();
internal static void Initialize() internal static void Initialize()
{ {
var culture = Language.ActiveCulture; var culture = Language.ActiveCulture;
@ -71,6 +75,15 @@ namespace TShockAPI.Localization
var i = (int)field.GetValue(null); var i = (int)field.GetValue(null);
Prefixs.Add(i, Lang.prefix[i].Value); 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 finally
{ {
@ -136,5 +149,18 @@ namespace TShockAPI.Localization
return null; return null;
} }
/// <summary>
/// Get vanilla command prefix in English
/// </summary>
/// <param name="name">vanilla command name</param>
/// <returns>vanilla command prefix in English</returns>
public static string GetCommandPrefixByName(string name)
{
string commandText;
if (VanillaCommandsPrefixs.TryGetValue(name, out commandText))
return commandText;
return null;
}
} }
} }

View file

@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -155,13 +155,39 @@ namespace TShockAPI
/// <param name="netId">The net ID.</param> /// <param name="netId">The net ID.</param>
/// <param name="stack">The stack.</param> /// <param name="stack">The stack.</param>
/// <param name="prefixId">The prefix ID.</param> /// <param name="prefixId">The prefix ID.</param>
public NetItem(int netId, int stack, byte prefixId) public NetItem(int netId, int stack = 1, byte prefixId = 0)
{ {
_netId = netId; _netId = netId;
_stack = stack; _stack = stack;
_prefixId = prefixId; _prefixId = prefixId;
} }
/// <summary>
/// Creates a new <see cref="NetItem"/>.
/// </summary>
/// <param name="item">Item in the game.</param>
public NetItem(Item item)
{
_netId = item.netID;
_stack = item.stack;
_prefixId = item.prefix;
}
/// <summary>
/// Creates <see cref="Terraria.Item"/> based on data from this structure.
/// </summary>
/// <returns>A copy of the item.</returns>
public Item ToItem()
{
Item item = new Item();
item.netDefaults(_netId);
item.stack = _stack;
item.prefix = _prefixId;
return item;
}
/// <summary> /// <summary>
/// Converts the <see cref="NetItem"/> to a string. /// Converts the <see cref="NetItem"/> to a string.
/// </summary> /// </summary>

View file

@ -529,18 +529,9 @@ namespace TShockAPI
field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute; 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 desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : GetString("No description available.");
var commands = GetCommands(name); var strs = GetCommands(name).Select(c => c.Names.Count > 1
foreach (var c in commands) ? $"/{c.Name} (/{string.Join(" /", c.Names.Skip(1))})"
{ : $"/{c.Name}");
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)) : ""));
sb.AppendLine($"## {name}"); sb.AppendLine($"## {name}");
sb.AppendLine($"{desc}"); sb.AppendLine($"{desc}");

View file

@ -351,7 +351,6 @@ namespace Rests
{ {
str = string.Format("{0}({1});", jsonp, str); 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.ContentType = new ContentTypeHeader("application/json; charset=utf-8");
e.Response.Add(serverHeader); e.Response.Add(serverHeader);
var bytes = Encoding.UTF8.GetBytes(str); var bytes = Encoding.UTF8.GetBytes(str);

View file

@ -402,7 +402,7 @@ namespace TShockAPI
{"serverversion", Main.versionNumber}, {"serverversion", Main.versionNumber},
{"tshockversion", TShock.VersionNum}, {"tshockversion", TShock.VersionNum},
{"port", TShock.Config.Settings.ServerPort}, {"port", TShock.Config.Settings.ServerPort},
{"playercount", Main.player.Where(p => null != p && p.active).Count()}, {"playercount", TShock.Utils.GetActivePlayerCount()},
{"maxplayers", TShock.Config.Settings.MaxSlots}, {"maxplayers", TShock.Config.Settings.MaxSlots},
{"world", (TShock.Config.Settings.UseServerName ? TShock.Config.Settings.ServerName : Main.worldName)}, {"world", (TShock.Config.Settings.UseServerName ? TShock.Config.Settings.ServerName : Main.worldName)},
{"uptime", (DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime).ToString(@"d'.'hh':'mm':'ss")}, {"uptime", (DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime).ToString(@"d'.'hh':'mm':'ss")},
@ -555,7 +555,8 @@ namespace TShockAPI
{ {
try 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"); response.Add("group-response", "Group updated successfully");
} }
catch (Exception e) catch (Exception e)
@ -944,8 +945,8 @@ namespace TShockAPI
[Token] [Token]
private object PlayerList(RestRequestArgs args) private object PlayerList(RestRequestArgs args)
{ {
var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); var activeplayers = TShock.Players.Where(p => null != p && p.Active).Select(p => p.Name);
return new RestObject() { { "players", string.Join(", ", activeplayers.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.")] [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.")]

View file

@ -428,6 +428,8 @@ namespace TShockAPI
Hooks.AccountHooks.AccountDelete += OnAccountDelete; Hooks.AccountHooks.AccountDelete += OnAccountDelete;
Hooks.AccountHooks.AccountCreate += OnAccountCreate; Hooks.AccountHooks.AccountCreate += OnAccountCreate;
On.Terraria.RemoteClient.Reset += RemoteClient_Reset;
GetDataHandlers.InitGetDataHandler(); GetDataHandlers.InitGetDataHandler();
Commands.InitCommands(); 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) private static void OnAchievementInitializerLoad(ILContext il)
{ {
// Modify AchievementInitializer.Load to remove the Main.netMode == 2 check (occupies the first 4 IL instructions) // Modify AchievementInitializer.Load to remove the Main.netMode == 2 check (occupies the first 4 IL instructions)
@ -1497,11 +1505,11 @@ namespace TShockAPI
{ {
if (!String.IsNullOrEmpty(text)) if (!String.IsNullOrEmpty(text))
{ {
text = item.Key.Value + ' ' + text; text = EnglishLanguage.GetCommandPrefixByName(item.Value._name) + ' ' + text;
} }
else else
{ {
text = item.Key.Value; text = EnglishLanguage.GetCommandPrefixByName(item.Value._name);
} }
break; break;
} }

View file

@ -18,7 +18,7 @@
Also, be sure to release on github with the exact assembly version tag as below 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) so that the update manager works correctly (via the Github releases api and mimic)
--> -->
<Version>5.2.0</Version> <Version>5.2.1</Version>
<AssemblyTitle>TShock for Terraria</AssemblyTitle> <AssemblyTitle>TShock for Terraria</AssemblyTitle>
<Company>Pryaxis &amp; TShock Contributors</Company> <Company>Pryaxis &amp; TShock Contributors</Company>
<Product>TShockAPI</Product> <Product>TShockAPI</Product>
@ -34,7 +34,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="GetText.NET" Version="1.7.14" /> <PackageReference Include="GetText.NET" Version="1.7.14" />
<PackageReference Include="MySql.Data" Version="8.0.31" /> <PackageReference Include="MySql.Data" Version="8.4.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.11" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.11" />
</ItemGroup> </ItemGroup>

View file

@ -29,7 +29,8 @@ namespace TShockAPI
/// </summary> /// </summary>
public class TextLog : ILog, IDisposable public class TextLog : ILog, IDisposable
{ {
private readonly StreamWriter _logWriter; private readonly bool ClearFile;
private StreamWriter _logWriter;
/// <summary> /// <summary>
/// File name of the Text log /// File name of the Text log
@ -44,7 +45,7 @@ namespace TShockAPI
public TextLog(string filename, bool clear) public TextLog(string filename, bool clear)
{ {
FileName = filename; FileName = filename;
_logWriter = new StreamWriter(filename, !clear); ClearFile = clear;
} }
public bool MayWriteType(TraceLevel type) public bool MayWriteType(TraceLevel type)
@ -247,6 +248,10 @@ namespace TShockAPI
{ {
if (!MayWriteType(level)) if (!MayWriteType(level))
return; return;
if (_logWriter is null)
{
_logWriter = new StreamWriter(FileName, !ClearFile);
}
var caller = "TShock"; var caller = "TShock";
@ -278,7 +283,10 @@ namespace TShockAPI
public void Dispose() public void Dispose()
{ {
_logWriter.Dispose(); if (_logWriter != null)
{
_logWriter.Dispose();
}
} }
} }
} }

View file

@ -172,7 +172,7 @@ namespace TShockAPI
foreach (TSPlayer player in TShock.Players) foreach (TSPlayer player in TShock.Players)
{ {
if (player != null && player != excludedPlayer && player.Active && player.HasPermission(Permissions.logs) && 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); player.SendMessage(log, color);
} }
} }
@ -183,7 +183,7 @@ namespace TShockAPI
/// <returns>The number of active players on the server.</returns> /// <returns>The number of active players on the server.</returns>
public int GetActivePlayerCount() 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 //Random should not be generated in a method
@ -1149,11 +1149,15 @@ namespace TShockAPI
/// <param name="empty">If the server is empty; determines if we should use Utils.GetActivePlayerCount() for player count or 0.</param> /// <param name="empty">If the server is empty; determines if we should use Utils.GetActivePlayerCount() for player count or 0.</param>
internal void SetConsoleTitle(bool empty) internal void SetConsoleTitle(bool empty)
{ {
if (ShouldSkipTitle)
return;
Console.Title = GetString("{0}{1}/{2} on {3} @ {4}:{5} (TShock for Terraria v{6})", 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 + " - " : "", !string.IsNullOrWhiteSpace(TShock.Config.Settings.ServerName) ? TShock.Config.Settings.ServerName + " - " : "",
empty ? 0 : GetActivePlayerCount(), empty ? 0 : GetActivePlayerCount(),
TShock.Config.Settings.MaxSlots, Main.worldName, Netplay.ServerIP.ToString(), Netplay.ListenPort, TShock.VersionNum); 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);
/// <summary>Determines the distance between two vectors.</summary> /// <summary>Determines the distance between two vectors.</summary>
/// <param name="value1">The first vector location.</param> /// <param name="value1">The first vector location.</param>

View file

@ -12,6 +12,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.4.1" /> <PackageReference Include="SharpZipLib" Version="1.4.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -30,7 +30,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="MySql.Data" Version="8.0.31" /> <PackageReference Include="MySql.Data" Version="8.4.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.11" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.11" />
<PackageReference Include="ModFramework" Version="1.1.7" GeneratePathProperty="true" /> <!-- only used to extract out to ./bin. --> <PackageReference Include="ModFramework" Version="1.1.7" GeneratePathProperty="true" /> <!-- only used to extract out to ./bin. -->
<PackageReference Include="GetText.NET" Version="1.7.14" /> <!-- only used to extract out to ./bin. --> <PackageReference Include="GetText.NET" Version="1.7.14" /> <!-- only used to extract out to ./bin. -->

View file

@ -30,7 +30,6 @@ using NuGet.Versioning;
namespace TShockPluginManager namespace TShockPluginManager
{ {
public class Nugetter public class Nugetter
{ {
// this object can figure out the right framework folders to use from a set of packages // 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 // make sure the source repository can actually tell us about dependencies
var dependencyInfoResource = await sourceRepository.GetResourceAsync<DependencyInfoResource>(); var dependencyInfoResource = await sourceRepository.GetResourceAsync<DependencyInfoResource>();
// get the try and dependencies // get the try and dependencies
// (the above function returns a nullable value, but doesn't properly indicate it as such) if (dependencyInfoResource is null) continue;
#pragma warning disable CS8602 var dependencyInfo = await dependencyInfoResource.ResolvePackage(
var dependencyInfo = await dependencyInfoResource?.ResolvePackage(
package, framework, cacheContext, logger, CancellationToken.None); 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 // 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! // it wasn't found. let's try the next source repository!
if (dependencyInfo == null) continue; if (dependencyInfo is null) continue;
availablePackages.Add(dependencyInfo); availablePackages.Add(dependencyInfo);
foreach (var dependency in dependencyInfo.Dependencies) foreach (var dependency in dependencyInfo.Dependencies)
@ -302,8 +299,11 @@ namespace TShockPluginManager
var relativeFolder = Path.GetDirectoryName(packageRelativeFilePath); var relativeFolder = Path.GetDirectoryName(packageRelativeFilePath);
var targetFolder = Path.Join(isPlugin ? "./ServerPlugins" : "./bin", relativeFolder); var targetFolder = Path.Join(isPlugin ? "./ServerPlugins" : "./bin", relativeFolder);
Directory.CreateDirectory(targetFolder); if (File.Exists(filePath))
File.Copy(filePath, Path.Join(targetFolder, Path.GetFileName(filePath)), true); {
Directory.CreateDirectory(targetFolder);
File.Copy(filePath, Path.Join(targetFolder, Path.GetFileName(filePath)), true);
}
} }
} }
} }

View file

@ -7,8 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NuGet.Packaging" Version="6.3.1" /> <PackageReference Include="NuGet.Packaging" Version="6.3.4" />
<PackageReference Include="NuGet.Protocol" Version="6.3.1" /> <PackageReference Include="NuGet.Protocol" Version="6.3.3" />
<PackageReference Include="NuGet.Resolver" Version="6.3.1" /> <PackageReference Include="NuGet.Resolver" Version="6.3.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" /> <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" /> <PackageReference Include="System.Reflection.MetadataLoadContext" Version="6.0.0" />

@ -1 +1 @@
Subproject commit 8a3fffd71db401736ea80619122c70c449c10ff3 Subproject commit d4bb7e3a21e875cfeb23bcf5cf847c85d9470ccf

View file

@ -78,6 +78,21 @@ 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. --> * If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change. -->
## Upcoming changes ## Upcoming changes
* 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)
## TShock 5.2.1
* Updated `TSPlayer.GodMode`. (@AgaSpace) * 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. * 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) * Added the `TSPlayer.Client` property. It allows the developer to get the `RemoteClient` player, without an additional call to `Terraria.Netplay.Clients`. (@AgaSpace)
@ -88,6 +103,7 @@ Use past tense when adding new entries; sign your name off when you add or chang
* 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.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 method `TSPlayer.GiveItem`, which has `TShockAPI.NetItem` structure in its arguments. (@AgaSpace)
* Added a property `TSPlayer.Hostile`, which gets pvp player mode. (@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) * Fixed typo in `/gbuff`. (@sgkoishi, #2955)
* Added `PlayerHooks.PrePlayerCommand` hook, which fired before command execution. (@AgaSpace) * Added `PlayerHooks.PrePlayerCommand` hook, which fired before command execution. (@AgaSpace)
* Added `PlayerHooks.PostPlayerCommand` hook, which fired after command execution. (@AgaSpace) * Added `PlayerHooks.PostPlayerCommand` hook, which fired after command execution. (@AgaSpace)
@ -124,7 +140,6 @@ Use past tense when adding new entries; sign your name off when you add or chang
* Relaxed custom death message restrictions to allow Inferno potions in PvP. (@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) * 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) * Removed unnecessary range check that artifically shortened quick stack reach. (@boddyn, #2885, @bcat)
* Improved the exploit protection in tile rect handling. (@punchready)
## TShock 5.1.3 ## TShock 5.1.3
* Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef) * Added support for Terraria 1.4.4.9 via OTAPI 3.1.20. (@SignatureBeef)

View file

@ -14,32 +14,27 @@ Open ports can also be passed through using `-p <host_port>:<container_port>`.
For Example: For Example:
```bash ```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 \ docker run -p 7777:7777 -p 7878:7878 \
-v /home/cider/tshock/:/tshock \ -v /home/cider/tshock/:/tshock \
-v /home/cider/.local/share/Terraria/Worlds:/worlds \ -v /home/cider/.local/share/Terraria/Worlds:/worlds \
-v /home/cider/tshock/plugins:/plugins \ -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" -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 ```bash
# Building the image using buildx and loading it into docker docker buildx build -t tshock:latest --load .
sudo docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --load . ```
# Running the image 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`):
docker run -p 7777:7777 -p 7878:7878 \
-v /home/cider/tshock/:/tshock \ ```bash
-v /home/cider/.local/share/Terraria/Worlds:/worlds \ docker buildx build -t tshock:linux-arm64 --platform linux/arm64 --load .
-v /home/cider/tshock/plugins:/plugins \
--rm -it tshock:linux-arm64 \
-world /worlds/backflip.wld -motd "ARM64 ftw"
``` ```

View file

@ -11,12 +11,7 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script> <script>
window.$docsify = { window.location.replace("https://github.com/Pryaxis/TShock/wiki");
name: 'TShock for Terraria',
repo: 'pryaxis/tshock',
loadSidebar: true,
subMaxLevel: 9
}
</script> </script>
<!-- Docsify v4 --> <!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script> <script src="//cdn.jsdelivr.net/npm/docsify@4"></script>

File diff suppressed because it is too large Load diff

View file

@ -1,17 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"git-submodules": {
"enabled": true
},
"packageRules": [
{
"matchPackageNames": ["OTAPI.Upcoming", "ModFramework", "TerrariaServerAPI"],
"ignoreUnstable": "false",
"bumpVersion": "prerelease",
"groupName": "OTAPI things"
}
]
}