diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a650ec..10a852f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ This is the rolling changelog for TShock for Terraria. Use past tense when adding new entries; sign your name off when you add or change something. This should primarily be things like user changes, not necessarily codebase changes unless it's really relevant or large. ## Upcoming Changes +* New permission `tshock.tp.pylon` to enable teleporting via Teleportation Pylons (@QuiCM) +* New permission `tshock.journey.research` to enable sharing research via item sacrifice (@QuiCM) * Add Emoji event to GetDataHandler. This packet is received when a player tries to display an emote. * Adding EmojiHandler to handle an exploit. Adding `tshock.sendemoji` permission and checks. Added this permission to guest group by default. diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 534d670b..b9bb2e05 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -37,14 +37,18 @@ namespace TShockAPI internal sealed class Bouncer { internal Handlers.SendTileSquareHandler STSHandler { get; set; } - internal Handlers.EmojiHandler EmojiHandler { get; set; } + internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; set; } + internal Handlers.EmojiHandler EmojiHandler { get; set; } /// Constructor call initializes Bouncer and related functionality. /// A new Bouncer. internal Bouncer() { STSHandler = new Handlers.SendTileSquareHandler(); - GetDataHandlers.SendTileSquare += STSHandler.OnReceiveSendTileSquare; + GetDataHandlers.SendTileSquare += STSHandler.OnReceive; + + NetModuleHandler = new Handlers.NetModules.NetModulePacketHandler(); + GetDataHandlers.ReadNetModule += NetModuleHandler.OnReceive; EmojiHandler = new Handlers.EmojiHandler(); GetDataHandlers.Emoji += EmojiHandler.OnReceiveEmoji; diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 10d6f0da..4fc949b7 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -1987,7 +1987,7 @@ namespace TShockAPI void FailedPermissionCheck() { - args.Player.SendErrorMessage("You do not have sufficient permissions to start the {0} event.", eventType); + args.Player.SendErrorMessage("You do not have permission to start the {0} event.", eventType); return; } @@ -4873,7 +4873,7 @@ namespace TShockAPI { if (!args.Player.HasPermission(Permissions.tp)) { - args.Player.SendErrorMessage("You don't have the necessary permission to do that."); + args.Player.SendErrorMessage("You do not have permission to teleport."); break; } if (args.Parameters.Count <= 1) @@ -5051,7 +5051,7 @@ namespace TShockAPI } if (displayIdsRequested && !args.Player.HasPermission(Permissions.seeids)) { - args.Player.SendErrorMessage("You don't have the required permission to list player ids."); + args.Player.SendErrorMessage("You do not have permission to list player ids."); return; } @@ -6025,7 +6025,7 @@ namespace TShockAPI { if (!args.Player.HasPermission(Permissions.godmodeother)) { - args.Player.SendErrorMessage("You do not have permission to god mode another player!"); + args.Player.SendErrorMessage("You do not have permission to god mode another player."); return; } string plStr = String.Join(" ", args.Parameters); diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index c9e9608e..dee152b5 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1986,6 +1986,40 @@ namespace TShockAPI return args.Handled; } + /// + /// Used when a net module is loaded + /// + public class ReadNetModuleEventArgs : GetDataHandledEventArgs + { + /// + /// The type of net module being loaded + /// + public NetModuleType ModuleType { get; set; } + } + + /// + /// Called when a net module is received + /// + public static HandlerList ReadNetModule = new HandlerList(); + + private static bool OnReadNetModule(TSPlayer player, MemoryStream data, NetModuleType moduleType) + { + if (ReadNetModule == null) + { + return false; + } + + var args = new ReadNetModuleEventArgs + { + Player = player, + Data = data, + ModuleType = moduleType + }; + + ReadNetModule.Invoke(null, args); + return args.Handled; + } + #endregion private static bool HandlePlayerInfo(GetDataHandlerArgs args) @@ -2243,11 +2277,10 @@ namespace TShockAPI else if ((Main.ServerSideCharacter) && (args.Player.sX > 0) && (args.Player.sY > 0) && (args.TPlayer.SpawnX > 0) && ((args.TPlayer.SpawnX != args.Player.sX) && (args.TPlayer.SpawnY != args.Player.sY))) { - args.Player.sX = args.TPlayer.SpawnX; args.Player.sY = args.TPlayer.SpawnY; - if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == 79)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) + if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == TileID.Beds)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) { args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); TShock.Log.ConsoleDebug("GetDataHandlers / HandleSpawn force teleport phase 1 {0}", args.Player.Name); @@ -2256,7 +2289,7 @@ namespace TShockAPI else if ((Main.ServerSideCharacter) && (args.Player.sX > 0) && (args.Player.sY > 0)) { - if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == 79)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) + if (((Main.tile[args.Player.sX, args.Player.sY - 1].active() && Main.tile[args.Player.sX, args.Player.sY - 1].type == TileID.Beds)) && (WorldGen.StartRoomCheck(args.Player.sX, args.Player.sY - 1))) { args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); TShock.Log.ConsoleDebug("GetDataHandlers / HandleSpawn force teleport phase 2 {0}", args.Player.Name); @@ -2963,21 +2996,21 @@ namespace TShockAPI if (bosses.Contains(thingType) && !args.Player.HasPermission(Permissions.summonboss)) { TShock.Log.ConsoleDebug("GetDataHandlers / HandleSpawnBoss rejected boss {0} {1}", args.Player.Name, thingType); - args.Player.SendErrorMessage("You don't have permission to summon a boss."); + args.Player.SendErrorMessage("You do not have permission to summon a boss."); return true; } if (invasions.Contains(thingType) && !args.Player.HasPermission(Permissions.startinvasion)) { TShock.Log.ConsoleDebug("GetDataHandlers / HandleSpawnBoss rejected invasion {0} {1}", args.Player.Name, thingType); - args.Player.SendErrorMessage("You don't have permission to start an invasion."); + args.Player.SendErrorMessage("You do not have permission to start an invasion."); return true; } if (pets.Contains(thingType) && !args.Player.HasPermission(Permissions.spawnpets)) { TShock.Log.ConsoleDebug("GetDataHandlers / HandleSpawnBoss rejected pet {0} {1}", args.Player.Name, thingType); - args.Player.SendErrorMessage("You don't have permission to spawn pets."); + args.Player.SendErrorMessage("You do not have permission to spawn pets."); return true; } @@ -3260,160 +3293,12 @@ namespace TShockAPI private static bool HandleLoadNetModule(GetDataHandlerArgs args) { short moduleId = args.Data.ReadInt16(); - if (moduleId == (int)NetModulesTypes.CreativePowers) + + if (OnReadNetModule(args.Player, args.Data, (NetModuleType)moduleId)) { - CreativePowerTypes powerId = (CreativePowerTypes)args.Data.ReadInt16(); - switch (powerId) - { - case CreativePowerTypes.FreezeTime: - { - if (!args.Player.HasPermission(Permissions.journey_timefreeze)) - { - args.Player.SendErrorMessage("You don't have permission to freeze the time of the server!"); - return true; - } - break; - } - case CreativePowerTypes.SetDawn: - case CreativePowerTypes.SetNoon: - case CreativePowerTypes.SetDusk: - case CreativePowerTypes.SetMidnight: - { - if (!args.Player.HasPermission(Permissions.journey_timeset)) - { - args.Player.SendErrorMessage("You don't have permission to modify the time of the server!"); - return true; - } - break; - } - case CreativePowerTypes.Godmode: - { - if (!args.Player.HasPermission(Permissions.journey_godmode)) - { - args.Player.SendErrorMessage("You don't have permission to toggle godmode!"); - return true; - } - break; - } - case CreativePowerTypes.WindStrength: - { - if (!args.Player.HasPermission(Permissions.journey_windstrength)) - { - args.Player.SendErrorMessage("You don't have permission to modify the wind strength of the server!"); - return true; - } - break; - } - case CreativePowerTypes.RainStrength: - { - if (!args.Player.HasPermission(Permissions.journey_rainstrength)) - { - args.Player.SendErrorMessage("You don't have permission to modify the rain strength of the server!"); - return true; - } - break; - } - case CreativePowerTypes.TimeSpeed: - { - if (!args.Player.HasPermission(Permissions.journey_timespeed)) - { - args.Player.SendErrorMessage("You don't have permission to modify the time speed of the server!"); - return true; - } - break; - } - case CreativePowerTypes.RainFreeze: - { - if (!args.Player.HasPermission(Permissions.journey_rainfreeze)) - { - args.Player.SendErrorMessage("You don't have permission to freeze the rain strength of the server!"); - return true; - } - break; - } - case CreativePowerTypes.WindFreeze: - { - if (!args.Player.HasPermission(Permissions.journey_windfreeze)) - { - args.Player.SendErrorMessage("You don't have permission to freeze the wind strength of the server!"); - return true; - } - break; - } - case CreativePowerTypes.IncreasePlacementRange: - { - if (!args.Player.HasPermission(Permissions.journey_placementrange)) - { - args.Player.SendErrorMessage("You don't have permission to modify the tile placement range of your character!"); - return true; - } - break; - } - case CreativePowerTypes.WorldDifficulty: - { - if (!args.Player.HasPermission(Permissions.journey_setdifficulty)) - { - args.Player.SendErrorMessage("You don't have permission to modify the world difficulty of the server!"); - return true; - } - break; - } - case CreativePowerTypes.BiomeSpreadFreeze: - { - if (!args.Player.HasPermission(Permissions.journey_biomespreadfreeze)) - { - args.Player.SendErrorMessage("You don't have permission to freeze the biome spread of the server!"); - return true; - } - break; - } - case CreativePowerTypes.SetSpawnRate: - { - // This is a monkeypatch because the 1.4.0.4 seemingly at random sends NPC spawn rate changes even outside of journey mode - // (with SSC on) -- particles, May 25, 2 Reiwa - if (!Main.GameModeInfo.IsJourneyMode) - { - return true; - } - if (!args.Player.HasPermission(Permissions.journey_setspawnrate)) - { - args.Player.SendErrorMessage("You don't have permission to modify the NPC spawn rate of the server!"); - return true; - } - break; - } - default: - { - return true; - } - } - } else if (moduleId == (int)NetModulesTypes.CreativeUnlocksPlayerReport && Main.GameModeInfo.IsJourneyMode) - { - var unknownField = args.Data.ReadByte(); - - if (unknownField == 0) //this is required or something??? - { - var itemId = args.Data.ReadUInt16(); - var amount = args.Data.ReadUInt16(); - - var totalSacrificed = TShock.ResearchDatastore.SacrificeItem(itemId, amount, args.Player); - - var response = NetCreativeUnlocksModule.SerializeItemSacrifice(itemId, totalSacrificed); - NetManager.Instance.Broadcast(response); - } + return true; } - // As of 1.4.x.x, this is now used for more things: - // NetCreativePowersModule - // NetCreativePowerPermissionsModule - // NetLiquidModule - // NetParticlesModule - // NetPingModule - // NetTeleportPylonModule - // NetTextModule - // I (particles) have disabled the original return here, which means that we need to - // handle this more. In the interm, this unbreaks parts of vanilla. Originally - // we just blocked this because it was a liquid exploit. return false; } @@ -3611,7 +3496,7 @@ namespace TShockAPI if (!args.Player.HasPermission(Permissions.startdd2)) { TShock.Log.ConsoleDebug("GetDataHandlers / HandleOldOnesArmy rejected permissions {0}", args.Player.Name); - args.Player.SendErrorMessage("You don't have permission to start the Old One's Army event."); + args.Player.SendErrorMessage("You do not have permission to start the Old One's Army event."); return true; } @@ -3939,7 +3824,7 @@ namespace TShockAPI public bool Killed { get; internal set; } } - public enum NetModulesTypes + public enum NetModuleType { Liquid, Text, diff --git a/TShockAPI/Handlers/IPacketHandler.cs b/TShockAPI/Handlers/IPacketHandler.cs new file mode 100644 index 00000000..9b2e0444 --- /dev/null +++ b/TShockAPI/Handlers/IPacketHandler.cs @@ -0,0 +1,16 @@ +namespace TShockAPI.Handlers +{ + /// + /// Describes a packet handler that receives a packet from a GetDataHandler + /// + /// + public interface IPacketHandler where TEventArgs : GetDataHandledEventArgs + { + /// + /// Invoked when the packet is received + /// + /// + /// + void OnReceive(object sender, TEventArgs args); + } +} diff --git a/TShockAPI/Handlers/NetModules/AmbienceHandler.cs b/TShockAPI/Handlers/NetModules/AmbienceHandler.cs new file mode 100644 index 00000000..0252c0c3 --- /dev/null +++ b/TShockAPI/Handlers/NetModules/AmbienceHandler.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Rejects ambience new modules from clients + /// + public class AmbienceHandler : INetModuleHandler + { + /// + /// No deserialization needed. This should never be received by the server + /// + /// + public void Deserialize(MemoryStream data) + { + } + + /// + /// This should never be received by the server + /// + /// + /// + public void HandlePacket(TSPlayer player, out bool rejectPacket) + { + rejectPacket = true; + } + } +} diff --git a/TShockAPI/Handlers/NetModules/BestiaryHandler.cs b/TShockAPI/Handlers/NetModules/BestiaryHandler.cs new file mode 100644 index 00000000..6338b1ce --- /dev/null +++ b/TShockAPI/Handlers/NetModules/BestiaryHandler.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Rejects client->server bestiary net modules as the client should never send this to the server + /// + public class BestiaryHandler : INetModuleHandler + { + /// + /// No deserialization needed. This should never be received by the server + /// + /// + public void Deserialize(MemoryStream data) + { + } + + /// + /// This should never be received by the server + /// + /// + /// + public void HandlePacket(TSPlayer player, out bool rejectPacket) + { + rejectPacket = true; + } + } +} diff --git a/TShockAPI/Handlers/NetModules/CreativePowerHandler.cs b/TShockAPI/Handlers/NetModules/CreativePowerHandler.cs new file mode 100644 index 00000000..470890d1 --- /dev/null +++ b/TShockAPI/Handlers/NetModules/CreativePowerHandler.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.IO; +using System.IO.Streams; +using static TShockAPI.GetDataHandlers; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Provides handling for the Creative Power net module. Checks permissions on all creative powers + /// + public class CreativePowerHandler : INetModuleHandler + { + /// + /// The power type being activated + /// + public CreativePowerTypes PowerType { get; set; } + + /// + /// Reads the power type from the stream + /// + /// + public void Deserialize(MemoryStream data) + { + PowerType = (CreativePowerTypes)data.ReadInt16(); + } + + /// + /// Determines if the player has permission to use the power type + /// + /// + /// + public void HandlePacket(TSPlayer player, out bool rejectPacket) + { + if (!HasPermission(PowerType, player)) + { + rejectPacket = true; + return; + } + + rejectPacket = false; + } + + /// + /// Determines if a player has permission to use a specific creative power + /// + /// + /// + /// + public static bool HasPermission(CreativePowerTypes powerType, TSPlayer player) + { + if (!PowerToPermissionMap.ContainsKey(powerType)) + { + TShock.Log.ConsoleDebug("CreativePowerHandler received permission check request for unknown creative power"); + return false; + } + + string permission = PowerToPermissionMap[powerType]; + + if (!player.HasPermission(permission)) + { + player.SendErrorMessage("You do not have permission to {0}.", PermissionToDescriptionMap[permission]); + return false; + } + + return true; + } + + + /// + /// Maps creative powers to permission nodes + /// + public static Dictionary PowerToPermissionMap = new Dictionary + { + { CreativePowerTypes.FreezeTime, Permissions.journey_timefreeze }, + { CreativePowerTypes.SetDawn, Permissions.journey_timeset }, + { CreativePowerTypes.SetNoon, Permissions.journey_timeset }, + { CreativePowerTypes.SetDusk, Permissions.journey_timeset }, + { CreativePowerTypes.SetMidnight, Permissions.journey_timeset }, + { CreativePowerTypes.Godmode, Permissions.journey_godmode }, + { CreativePowerTypes.WindStrength, Permissions.journey_windstrength }, + { CreativePowerTypes.RainStrength, Permissions.journey_rainstrength }, + { CreativePowerTypes.TimeSpeed, Permissions.journey_timespeed }, + { CreativePowerTypes.RainFreeze, Permissions.journey_rainfreeze }, + { CreativePowerTypes.WindFreeze, Permissions.journey_windfreeze }, + { CreativePowerTypes.IncreasePlacementRange, Permissions.journey_placementrange }, + { CreativePowerTypes.WorldDifficulty, Permissions.journey_setdifficulty }, + { CreativePowerTypes.BiomeSpreadFreeze, Permissions.journey_biomespreadfreeze }, + { CreativePowerTypes.SetSpawnRate, Permissions.journey_setspawnrate }, + }; + + /// + /// Maps journey mode permission nodes to descriptions of what the permission allows + /// + public static Dictionary PermissionToDescriptionMap = new Dictionary + { + { Permissions.journey_timefreeze, "freeze the time of the server" }, + { Permissions.journey_timeset, "modify the time of the server" }, + { Permissions.journey_timeset, "modify the time of the server" }, + { Permissions.journey_timeset, "modify the time of the server" }, + { Permissions.journey_timeset, "modify the time of the server" }, + { Permissions.journey_godmode, "toggle godmode" }, + { Permissions.journey_windstrength, "modify the wind strength of the server" }, + { Permissions.journey_rainstrength, "modify the rain strength of the server" }, + { Permissions.journey_timespeed, "modify the time speed of the server" }, + { Permissions.journey_rainfreeze, "freeze the rain strength of the server" }, + { Permissions.journey_windfreeze, "freeze the wind strength of the server" }, + { Permissions.journey_placementrange, "modify the tile placement range of your character" }, + { Permissions.journey_setdifficulty, "modify the world difficulty of the server" }, + { Permissions.journey_biomespreadfreeze, "freeze the biome spread of the server" }, + { Permissions.journey_setspawnrate, "modify the NPC spawn rate of the server" }, + }; + } +} diff --git a/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs b/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs new file mode 100644 index 00000000..68fe4f1a --- /dev/null +++ b/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs @@ -0,0 +1,90 @@ +using System.IO; +using System.IO.Streams; +using Terraria; +using Terraria.GameContent.NetModules; +using Terraria.Net; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Handles creative unlock requests + /// + public class CreativeUnlocksHandler : INetModuleHandler + { + /// + /// An unknown field. If this does not have a value of '0' the packet should be rejected. + /// + public byte UnknownField { get; set; } + /// + /// ID of the item being sacrificed + /// + public ushort ItemId { get; set; } + /// + /// Stack size of the item being sacrificed + /// + public ushort Amount { get; set; } + + /// + /// Reads the unlock data from the stream + /// + /// + public void Deserialize(MemoryStream data) + { + // For whatever reason Terraria writes '0' to the stream at the beginning of this packet. + // If this value is not 0 then its been crafted by a non-vanilla client. + // We don't actually know why the 0 is written, so we're just going to call this UnknownField for now + UnknownField = data.ReadInt8(); + if (UnknownField == 0) + { + ItemId = data.ReadUInt16(); + Amount = data.ReadUInt16(); + } + } + + /// + /// Determines if the unlock is valid and the player has permission to perform the unlock. + /// Syncs unlock status if the packet is accepted + /// + /// + /// + public void HandlePacket(TSPlayer player, out bool rejectPacket) + { + if (!Main.GameModeInfo.IsJourneyMode) + { + TShock.Log.ConsoleDebug( + "NetModuleHandler received attempt to unlock sacrifice while not in journey mode from", + player.Name + ); + + rejectPacket = true; + return; + } + + if (UnknownField != 0) + { + TShock.Log.ConsoleDebug( + "CreativeUnlocksHandler received non-vanilla unlock request. Random field value: {0} but should be 0 from {1}", + UnknownField, + player.Name + ); + + rejectPacket = true; + return; + } + + if (!player.HasPermission(Permissions.journey_contributeresearch)) + { + player.SendErrorMessage("You do not have permission to contribute research."); + rejectPacket = true; + return; + } + + var totalSacrificed = TShock.ResearchDatastore.SacrificeItem(ItemId, Amount, player); + + var response = NetCreativeUnlocksModule.SerializeItemSacrifice(ItemId, totalSacrificed); + NetManager.Instance.Broadcast(response); + + rejectPacket = false; + } + } +} diff --git a/TShockAPI/Handlers/NetModules/INetModuleHandler.cs b/TShockAPI/Handlers/NetModules/INetModuleHandler.cs new file mode 100644 index 00000000..459d22f4 --- /dev/null +++ b/TShockAPI/Handlers/NetModules/INetModuleHandler.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Describes a handler for a net module + /// + public interface INetModuleHandler + { + /// + /// Reads the net module's data from the given stream + /// + /// + void Deserialize(MemoryStream data); + + /// + /// Provides handling for the packet and determines if it should be accepted or rejected + /// + /// + /// + void HandlePacket(TSPlayer player, out bool rejectPacket); + } +} diff --git a/TShockAPI/Handlers/NetModules/LiquidHandler.cs b/TShockAPI/Handlers/NetModules/LiquidHandler.cs new file mode 100644 index 00000000..e7fe647f --- /dev/null +++ b/TShockAPI/Handlers/NetModules/LiquidHandler.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Handles the NetLiquidModule. Rejects all incoming net liquid requests, as clients should never send them + /// + public class LiquidHandler : INetModuleHandler + { + /// + /// Does nothing. We should not deserialize this data + /// + /// + public void Deserialize(MemoryStream data) + { + // No need to deserialize + } + + /// + /// Rejects the packet. Clients should not send this to us + /// + /// + /// + public void HandlePacket(TSPlayer player, out bool rejectPacket) + { + rejectPacket = true; + } + } +} diff --git a/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs b/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs new file mode 100644 index 00000000..16d58640 --- /dev/null +++ b/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Terraria; +using static TShockAPI.GetDataHandlers; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Handles packet 82 - Load Net Module packets + /// + public class NetModulePacketHandler : IPacketHandler + { + /// + /// Maps net module types to handlers for the net module type. Add to or edit this dictionary to customise handling + /// + public static Dictionary NetModulesToHandlersMap = new Dictionary + { + { NetModuleType.CreativePowers, typeof(CreativePowerHandler) }, + { NetModuleType.CreativeUnlocksPlayerReport, typeof(CreativeUnlocksHandler) }, + { NetModuleType.TeleportPylon, typeof(PylonHandler) }, + { NetModuleType.Liquid, typeof(LiquidHandler) }, + { NetModuleType.Bestiary, typeof(BestiaryHandler) }, + { NetModuleType.Ambience, typeof(AmbienceHandler) } + }; + + /// + /// Invoked when a load net module packet is received. This method picks a based on the + /// net module type being loaded, then forwards the data to the chosen handler to process + /// + /// + /// + public void OnReceive(object sender, ReadNetModuleEventArgs args) + { + INetModuleHandler handler; + + if (NetModulesToHandlersMap.ContainsKey(args.ModuleType)) + { + handler = (INetModuleHandler)Activator.CreateInstance(NetModulesToHandlersMap[args.ModuleType]); + } + else + { + // We don't have handlers for NetModuleType.Ping and NetModuleType.Particles. + // These net modules are fairly innocuous and can be processed normally by the game + args.Handled = false; + return; + } + + handler.Deserialize(args.Data); + handler.HandlePacket(args.Player, out bool rejectPacket); + + args.Handled = rejectPacket; + } + } +} diff --git a/TShockAPI/Handlers/NetModules/PylonHandler.cs b/TShockAPI/Handlers/NetModules/PylonHandler.cs new file mode 100644 index 00000000..75ca0c1d --- /dev/null +++ b/TShockAPI/Handlers/NetModules/PylonHandler.cs @@ -0,0 +1,62 @@ +using System.IO; +using System.IO.Streams; +using Terraria.GameContent; +using static Terraria.GameContent.NetModules.NetTeleportPylonModule; + +namespace TShockAPI.Handlers.NetModules +{ + /// + /// Handles a pylon net module + /// + public class PylonHandler : INetModuleHandler + { + /// + /// Event occuring + /// + public SubPacketType PylonEventType { get; set; } + /// + /// Tile X coordinate of the pylon + /// + public short TileX { get; set; } + /// + /// Tile Y coordinate of the pylon + /// + public short TileY { get; set; } + /// + /// Type of Pylon + /// + public TeleportPylonType PylonType { get; set; } + + /// + /// Reads the pylon data from the net module + /// + /// + public void Deserialize(MemoryStream data) + { + PylonEventType = (SubPacketType)data.ReadInt8(); + TileX = data.ReadInt16(); + TileY = data.ReadInt16(); + PylonType = (TeleportPylonType)data.ReadInt8(); + } + + /// + /// Rejects a pylon teleport request if the player does not have permission + /// + /// + /// + public void HandlePacket(TSPlayer player, out bool rejectPacket) + { + if (PylonEventType == SubPacketType.PlayerRequestsTeleport) + { + if (!player.HasPermission(Permissions.pylon)) + { + rejectPacket = true; + player.SendErrorMessage("You do not have permission to teleport with pylons."); + return; + } + } + + rejectPacket = false; + } + } +} diff --git a/TShockAPI/Handlers/SendTileSquareHandler.cs b/TShockAPI/Handlers/SendTileSquareHandler.cs index 48462a03..39a7e751 100644 --- a/TShockAPI/Handlers/SendTileSquareHandler.cs +++ b/TShockAPI/Handlers/SendTileSquareHandler.cs @@ -14,12 +14,12 @@ namespace TShockAPI.Handlers /// /// Provides processors for handling Tile Square packets /// - public class SendTileSquareHandler + public class SendTileSquareHandler : IPacketHandler { /// /// Maps grass-type blocks to flowers that can be grown on them with flower boots /// - Dictionary> _grassToPlantMap = new Dictionary> + public static Dictionary> GrassToPlantMap = new Dictionary> { { TileID.Grass, new List { TileID.Plants, TileID.Plants2 } }, { TileID.HallowedGrass, new List { TileID.HallowedPlants, TileID.HallowedPlants2 } }, @@ -29,7 +29,7 @@ namespace TShockAPI.Handlers /// /// Item IDs that can spawn flowers while you walk /// - List _flowerBootItems = new List + public static List FlowerBootItems = new List { ItemID.FlowerBoots, ItemID.FairyBoots @@ -40,7 +40,7 @@ namespace TShockAPI.Handlers /// 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 /// - Dictionary _tileEntityIdToTileIdMap = new Dictionary + public static Dictionary TileEntityIdToTileIdMap = new Dictionary { { TileID.TargetDummy, TETrainingDummy._myEntityID }, { TileID.ItemFrame, TEItemFrame._myEntityID }, @@ -57,7 +57,7 @@ namespace TShockAPI.Handlers /// /// /// - public void OnReceiveSendTileSquare(object sender, GetDataHandlers.SendTileSquareEventArgs args) + public void OnReceive(object sender, GetDataHandlers.SendTileSquareEventArgs args) { // By default, we'll handle everything args.Handled = true; @@ -191,14 +191,20 @@ namespace TShockAPI.Handlers TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from no permission for tile object from {0}", args.Player.Name); return; } + + if (TShock.TileBans.TileIsBanned((short)tileType)) + { + TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected for banned tile"); + return; + } // 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)) + if (TileEntityIdToTileIdMap.ContainsKey(tileType)) { - TileEntity.PlaceEntityNet(realX, realY, _tileEntityIdToTileIdMap[tileType]); + TileEntity.PlaceEntityNet(realX, realY, TileEntityIdToTileIdMap[tileType]); } } @@ -214,7 +220,7 @@ namespace TShockAPI.Handlers { // Some boots allow growing flowers on grass. This process sends a 1x1 tile square to grow the flowers // The square size must be 1 and the player must have an accessory that allows growing flowers in order for this square to be valid - if (squareSize == 1 && args.Player.Accessories.Any(a => a != null && _flowerBootItems.Contains(a.type))) + if (squareSize == 1 && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type))) { ProcessFlowerBoots(realX, realY, newTile, args); return; @@ -254,7 +260,7 @@ namespace TShockAPI.Handlers } ITile tile = Main.tile[realX, realY + 1]; - if (!_grassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) + if (!GrassToPlantMap.TryGetValue(tile.type, out List plantTiles) && !plantTiles.Contains(newTile.Type)) { // If the tile below the tile square isn't a valid plant tile (eg grass) then we don't update the server tile state return; diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index 4b196380..643882f8 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -267,6 +267,9 @@ namespace TShockAPI [Description("User can use wormhole potions.")] public static readonly string wormhole = "tshock.tp.wormhole"; + + [Description("User can use pylons to teleport")] + public static readonly string pylon = "tshock.tp.pylon"; #endregion #region tshock.world nodes @@ -409,6 +412,9 @@ namespace TShockAPI [Description("User can use Creative UI to set the NPC spawn rate of the world.")] public static readonly string journey_setspawnrate = "tshock.journey.setspawnrate"; + + [Description("User can contribute research by sacrificing items")] + public static readonly string journey_contributeresearch = "tshock.journey.research"; #endregion #region Non-grouped diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 54be5041..ade16234 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -664,13 +664,13 @@ namespace TShockAPI switch (failure) { case BuildPermissionFailPoint.GeneralBuild: - SendErrorMessage("You lack permission to build on this server."); + SendErrorMessage("You do not have permission to build on this server."); break; case BuildPermissionFailPoint.SpawnProtect: - SendErrorMessage("You lack permission to build in the spawn point."); + SendErrorMessage("You do not have permission to build in the spawn point."); break; case BuildPermissionFailPoint.Regions: - SendErrorMessage("You lack permission to build in this region."); + SendErrorMessage("You do not have permission to build in this region."); break; } diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 3647b476..fef6f380 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -88,6 +88,15 @@ + + + + + + + + +