diff --git a/CHANGELOG.md b/CHANGELOG.md index 139de5c6..effb8191 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) * Added LandGolfBallInCup event which is accessible for developers to work with, as well as LandGolfBallInCup handler to handle exploits where players could send direct packets to trigger and imitate golf ball cup landing anywhere in the game world. Added two public lists in Handlers.LandGolfBallInCupHandler: GolfBallProjectileIDs and GolfClubItemIDs. (@Patrikkk) ## TShock 4.4.0 (Pre-release 10) diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 7cf7b87f..95a4cb40 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.LandGolfBallInCupHandler LandGolfBallInCupHandler { get; set; } + internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; set; } + internal Handlers.LandGolfBallInCupHandler LandGolfBallInCupHandler { 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; LandGolfBallInCupHandler = new Handlers.LandGolfBallInCupHandler(); GetDataHandlers.LandGolfBallInCup += LandGolfBallInCupHandler.OnLandGolfBallInCup; 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 fcb05489..2abb5757 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -2002,6 +2002,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) @@ -2259,11 +2293,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); @@ -2272,7 +2305,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); @@ -2979,21 +3012,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; } @@ -3276,160 +3309,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; } @@ -3627,7 +3512,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; } @@ -3958,7 +3843,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 baf21a73..3b401004 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 fedc3f0b..d1e03b61 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -88,6 +88,15 @@ + + + + + + + + +