diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
index fda85267..fa928eb4 100644
--- a/TShockAPI/Bouncer.cs
+++ b/TShockAPI/Bouncer.cs
@@ -37,6 +37,7 @@ namespace TShockAPI
internal sealed class Bouncer
{
internal Handlers.SendTileSquareHandler STSHandler { get; set; }
+ internal Handlers.NetModules.NetModulePacketHandler NetModuleHandler { get; set; }
/// Constructor call initializes Bouncer and related functionality.
/// A new Bouncer.
@@ -45,6 +46,9 @@ namespace TShockAPI
STSHandler = new Handlers.SendTileSquareHandler();
GetDataHandlers.SendTileSquare += STSHandler.OnReceive;
+ NetModuleHandler = new Handlers.NetModules.NetModulePacketHandler();
+ GetDataHandlers.LoadNetModule += NetModuleHandler.OnReceive;
+
// Setup hooks
GetDataHandlers.GetSection += OnGetSection;
GetDataHandlers.PlayerUpdate += OnPlayerUpdate;
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 65fe622f..52394f38 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -1951,6 +1951,40 @@ namespace TShockAPI
return args.Handled;
}
+ ///
+ /// Used when a net module is loaded
+ ///
+ public class LoadNetModuleEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The type of net module being loaded
+ ///
+ public NetModulesTypes ModuleType { get; set; }
+ }
+
+ ///
+ /// Called when a net module is loaded
+ ///
+ public static HandlerList LoadNetModule = new HandlerList();
+
+ private static bool OnLoadNetModule(TSPlayer player, MemoryStream data, NetModulesTypes moduleType)
+ {
+ if (LoadNetModule == null)
+ {
+ return false;
+ }
+
+ var args = new LoadNetModuleEventArgs
+ {
+ Player = player,
+ Data = data,
+ ModuleType = moduleType
+ };
+
+ LoadNetModule.Invoke(null, args);
+ return args.Handled;
+ }
+
#endregion
private static bool HandlePlayerInfo(GetDataHandlerArgs args)
@@ -3225,160 +3259,12 @@ namespace TShockAPI
private static bool HandleLoadNetModule(GetDataHandlerArgs args)
{
short moduleId = args.Data.ReadInt16();
- if (moduleId == (int)NetModulesTypes.CreativePowers)
+
+ if (OnLoadNetModule(args.Player, args.Data, (NetModulesTypes)moduleId))
{
- CreativePowerTypes powerId = (CreativePowerTypes)args.Data.ReadInt16();
- switch (powerId)
- {
- case CreativePowerTypes.FreezeTime:
- {
- if (!args.Player.HasPermission(Permissions.journey_timefreeze))
- {
- args.Player.SendErrorMessage("You do not 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 do not 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 do not have permission to toggle godmode.");
- return true;
- }
- break;
- }
- case CreativePowerTypes.WindStrength:
- {
- if (!args.Player.HasPermission(Permissions.journey_windstrength))
- {
- args.Player.SendErrorMessage("You do not 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 do not 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 do not 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 do not 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 do not 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 do not 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 do not 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 do not 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 do not 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;
}
diff --git a/TShockAPI/Handlers/NetModules/CreativePowerHandler.cs b/TShockAPI/Handlers/NetModules/CreativePowerHandler.cs
new file mode 100644
index 00000000..06e43107
--- /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 (!CheckPermission(PowerType, player))
+ {
+ rejectPacket = true;
+ return;
+ }
+
+ rejectPacket = false;
+ }
+
+ ///
+ /// Determines if a player has permission to use a specific creative power
+ ///
+ ///
+ ///
+ ///
+ public static bool CheckPermission(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..39f83d67
--- /dev/null
+++ b/TShockAPI/Handlers/NetModules/CreativeUnlocksHandler.cs
@@ -0,0 +1,75 @@
+using System.IO;
+using System.IO.Streams;
+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)
+ {
+ 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 (UnknownField != 0)
+ {
+ TShock.Log.ConsoleDebug(
+ "CreativeUnlocksNetModuleHandler received non-vanilla unlock request. Random field value: {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/NetModulePacketHandler.cs b/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs
new file mode 100644
index 00000000..f4d20bb4
--- /dev/null
+++ b/TShockAPI/Handlers/NetModules/NetModulePacketHandler.cs
@@ -0,0 +1,75 @@
+using System;
+using Terraria;
+using static TShockAPI.GetDataHandlers;
+
+namespace TShockAPI.Handlers.NetModules
+{
+ ///
+ /// Handles packet 82 - Load Net Module packets
+ ///
+ public class NetModulePacketHandler : IPacketHandler
+ {
+ ///
+ /// 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, LoadNetModuleEventArgs args)
+ {
+ INetModuleHandler handler;
+
+ switch (args.ModuleType)
+ {
+ case NetModulesTypes.CreativePowers:
+ {
+ handler = new CreativePowerHandler();
+ break;
+ }
+
+ case NetModulesTypes.CreativeUnlocksPlayerReport:
+ {
+ if (!Main.GameModeInfo.IsJourneyMode)
+ {
+ TShock.Log.ConsoleDebug(
+ "NetModuleHandler received attempt to unlock sacrifice while not in journey mode from",
+ args.Player.Name
+ );
+
+ args.Handled = true;
+ return;
+ }
+
+ handler = new CreativeUnlocksHandler();
+ break;
+ }
+ case NetModulesTypes.TeleportPylon:
+ {
+ handler = new PylonHandler();
+ break;
+ }
+ default:
+ {
+ // 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.
+ 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/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/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj
index af383252..b129837f 100644
--- a/TShockAPI/TShockAPI.csproj
+++ b/TShockAPI/TShockAPI.csproj
@@ -89,6 +89,11 @@
+
+
+
+
+
@@ -213,7 +218,7 @@
-
+