diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 4a126d567..5c75eb6b3 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -265,6 +265,25 @@ private static bool OnPlayerSlot(TSPlayer player, MemoryStream data, byte _plr, return args.Handled; } + /// + /// Connecting - called at a Connecting event + /// + public static HandlerList Connecting = new HandlerList(); + private static bool OnConnecting(TSPlayer player, MemoryStream data) + { + if (Connecting == null) + return false; + + var args = new GetDataHandledEventArgs + { + Player = player, + + Data = data + }; + Connecting.Invoke(null, args); + return args.Handled; + } + /// The arguments to a GetSection packet. public class GetSectionEventArgs : GetDataHandledEventArgs { @@ -936,6 +955,35 @@ private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, B PlayerZone.Invoke(null, args); return args.Handled; } + + /// + /// For use in a Password Event + /// + public class PasswordEventArgs : GetDataHandledEventArgs + { + /// + /// The Password + /// + public string Password { get; set; } + } + /// + /// Password - When clients send password to server + /// + public static HandlerList Password = new HandlerList(); + private static bool OnPassword(TSPlayer player, MemoryStream data, string password) + { + if (Password == null) + return false; + + var args = new PasswordEventArgs + { + Player = player, + Data = data, + Password = password + }; + Password.Invoke(null, args); + return args.Handled; + } /// /// For use with a PlayerAnimation event @@ -1942,29 +1990,24 @@ private static bool HandleConnecting(GetDataHandlerArgs args) args.Player.tempGroup = null; args.Player.Account = account; args.Player.IsLoggedIn = true; - args.Player.IsDisabledForSSC = false; - if (Main.ServerSideCharacter) + if (OnConnecting(args.Player, args.Data)) { - if (args.Player.HasPermission(Permissions.bypassssc)) - { - args.Player.PlayerData.CopyCharacter(args.Player); - TShock.CharacterDB.InsertPlayerData(args.Player); - } - args.Player.PlayerData.RestoreCharacter(args.Player); + return true; } - args.Player.LoginFailsBySsi = false; - - if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) - args.Player.IsDisabledForStackDetection = false; + else + { + if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) + args.Player.IsDisabledForStackDetection = false; - if (args.Player.HasPermission(Permissions.usebanneditem)) - args.Player.IsDisabledForBannedWearable = false; + if (args.Player.HasPermission(Permissions.usebanneditem)) + args.Player.IsDisabledForBannedWearable = false; - args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully."); - TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); - Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); - return true; + args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully."); + TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); + Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); + return true; + } } } else if (account != null && !TShock.Config.DisableLoginBeforeJoin) @@ -2010,23 +2053,7 @@ private static bool HandleSpawn(GetDataHandlerArgs args) if (OnPlayerSpawn(args.Player, args.Data, player, spawnx, spawny)) return true; - - 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))) - args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); - } - - 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))) - args.Player.Teleport(args.Player.sX * 16, (args.Player.sY * 16) - 48); - } - + args.Player.Dead = false; return false; } @@ -2138,36 +2165,7 @@ private static bool HandlePlayerUpdate(GetDataHandlerArgs args) { args.TPlayer.direction = -1; } - - if (args.Player.Confused && Main.ServerSideCharacter && args.Player.IsLoggedIn) - { - if (args.TPlayer.controlUp) - { - args.TPlayer.controlDown = true; - args.TPlayer.controlUp = false; - } - else if (args.TPlayer.controlDown) - { - args.TPlayer.controlDown = false; - args.TPlayer.controlUp = true; - } - - if (args.TPlayer.controlLeft) - { - args.TPlayer.controlRight = true; - args.TPlayer.controlLeft = false; - } - else if (args.TPlayer.controlRight) - { - args.TPlayer.controlRight = false; - args.TPlayer.controlLeft = true; - } - - args.TPlayer.Update(args.TPlayer.whoAmI); - NetMessage.SendData((int)PacketTypes.PlayerUpdate, -1, -1, NetworkText.Empty, args.Player.Index); - return true; - } - + NetMessage.SendData((int)PacketTypes.PlayerUpdate, -1, args.Player.Index, NetworkText.Empty, args.Player.Index); return true; } @@ -2513,31 +2511,25 @@ private static bool HandlePassword(GetDataHandlerArgs args) args.Player.tempGroup = null; args.Player.Account = account; args.Player.IsLoggedIn = true; - args.Player.IsDisabledForSSC = false; - if (Main.ServerSideCharacter) + if (OnPassword(args.Player, args.Data, password)) { - if (args.Player.HasPermission(Permissions.bypassssc)) - { - args.Player.PlayerData.CopyCharacter(args.Player); - TShock.CharacterDB.InsertPlayerData(args.Player); - } - args.Player.PlayerData.RestoreCharacter(args.Player); + return true; + } + else + { + if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) + args.Player.IsDisabledForStackDetection = false; + + if (args.Player.HasPermission(Permissions.usebanneditem)) + args.Player.IsDisabledForBannedWearable = false; + + args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); + TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); + TShock.UserAccounts.SetUserAccountUUID(account, args.Player.UUID); + Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); + return true; } - args.Player.LoginFailsBySsi = false; - - if (args.Player.HasPermission(Permissions.ignorestackhackdetection)) - args.Player.IsDisabledForStackDetection = false; - - if (args.Player.HasPermission(Permissions.usebanneditem)) - args.Player.IsDisabledForBannedWearable = false; - - - args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); - TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user " + args.Player.Name + "."); - TShock.UserAccounts.SetUserAccountUUID(account, args.Player.UUID); - Hooks.PlayerHooks.OnPlayerPostLogin(args.Player); - return true; } args.Player.Kick("Your password did not match this character's password.", true, true); return true; @@ -3319,15 +3311,6 @@ private static bool HandlePlayerKillMeV2(GetDataHandlerArgs args) } } - if (args.TPlayer.difficulty == 2 && Main.ServerSideCharacter && args.Player.IsLoggedIn) - { - if (TShock.CharacterDB.RemovePlayer(args.Player.Account.ID)) - { - args.Player.SendErrorMessage("You have fallen in hardcore mode, and your items have been lost forever."); - TShock.CharacterDB.SeedInitialData(args.Player.Account); - } - } - return false; } diff --git a/TShockAPI/SSC.cs b/TShockAPI/SSC.cs new file mode 100644 index 000000000..e55d57ed1 --- /dev/null +++ b/TShockAPI/SSC.cs @@ -0,0 +1,207 @@ +/* +TShock, a server mod for Terraria +Copyright (C) 2011-2018 Pryaxis & TShock Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Terraria.ID; +using TShockAPI.DB; +using TShockAPI.Net; +using Terraria; +using Microsoft.Xna.Framework; +using OTAPI.Tile; +using TShockAPI.Localization; +using static TShockAPI.GetDataHandlers; +using TerrariaApi.Server; +using Terraria.ObjectData; +using Terraria.DataStructures; +using Terraria.Localization; + +namespace TShockAPI +{ + /// The TShock server side character subsystem. + internal sealed class SSC + { + /// The database connection layer to for the ssc subsystem. + private CharacterManager DataModel; + /// A reference to the TShock plugin so we can register events. + private TShock Plugin; + + /// A new SSC system. + internal SSC(TShock plugin, IDbConnection database) + { + DataModel = new CharacterManager(database); + Plugin = plugin; + + // Setup GetDataHandlers + GetDataHandlers.Connecting += Login; + GetDataHandlers.PlayerSpawn += OnSpawn; + GetDataHandlers.PlayerUpdate += OnPlayerUpdate; + GetDataHandlers.Password += Login; + GetDataHandlers.KillMe += OnKillMe; + + // Setup ServerApi Hook Handler + ServerApi.Hooks.GameUpdate.Register(Plugin, OnGameUpdate); + ServerApi.Hooks.NetGreetPlayer.Register(Plugin, OnGreetPlayer); + ServerApi.Hooks.ServerLeave.Register(Plugin, OnLeave); + } + + internal void Login(object sender, GetDataHandledEventArgs args) + { + try + { + args.Player.IsDisabledForSSC = false; + + if (args.Player.HasPermission(Permissions.bypassssc)) + { + args.Player.PlayerData.CopyCharacter(args.Player); + DataModel.InsertPlayerData(args.Player); + } + + args.Player.PlayerData.RestoreCharacter(args.Player); + args.Player.LoginFailsBySsi = false; + } + catch (Exception ex) + { + TShock.Log.ConsoleError(ex.ToString()); + + args.Player.IsDisabledForSSC = true; + args.Player.LoginFailsBySsi = true; + } + } + + internal void OnSpawn(object sender, SpawnEventArgs args) + { + var p = args.Player; + if ((p.sX > 0) && (p.sY > 0) && (p.TPlayer.SpawnX > 0) && + ((p.TPlayer.SpawnX != p.sX) && (p.TPlayer.SpawnY != p.sY))) + { + + p.sX = p.TPlayer.SpawnX; + p.sY = p.TPlayer.SpawnY; + + if (((Main.tile[p.sX, p.sY - 1].active() && Main.tile[p.sX, p.sY - 1].type == 79)) && + (WorldGen.StartRoomCheck(p.sX, p.sY - 1))) + p.Teleport(p.sX * 16, (p.sY * 16) - 48); + } + + else if ((p.sX > 0) && (p.sY > 0)) + { + if (((Main.tile[p.sX, p.sY - 1].active() && Main.tile[p.sX, p.sY - 1].type == 79)) && + (WorldGen.StartRoomCheck(p.sX, p.sY - 1))) + p.Teleport(p.sX * 16, (p.sY * 16) - 48); + } + } + + internal void OnPlayerUpdate(object sender, PlayerUpdateEventArgs args) + { + var tplr = args.Player.TPlayer; + if (args.Player.Confused && args.Player.IsLoggedIn) + { + if (tplr.controlUp) + { + tplr.controlDown = true; + tplr.controlUp = false; + } + else if (tplr.controlDown) + { + tplr.controlDown = false; + tplr.controlUp = true; + } + + if (tplr.controlLeft) + { + tplr.controlRight = true; + tplr.controlLeft = false; + } + else if (tplr.controlRight) + { + tplr.controlRight = false; + tplr.controlLeft = true; + } + + tplr.Update(tplr.whoAmI); + NetMessage.SendData((int) PacketTypes.PlayerUpdate, -1, -1, NetworkText.Empty, args.Player.Index); + args.Handled = true; + } + } + + internal void OnKillMe(object sender, KillMeEventArgs args) + { + if (args.Player.TPlayer.difficulty == 2 && args.Player.IsLoggedIn) + { + if (DataModel.RemovePlayer(args.Player.Account.ID)) + { + args.Player.SendErrorMessage( + "You have fallen in hardcore mode, and your items have been lost forever."); + TShock.CharacterDB.SeedInitialData(args.Player.Account); + } + } + } + + internal void OnGameUpdate(EventArgs args) + { + if ((DateTime.UtcNow - Plugin.LastSave).TotalMinutes >= + TShock.ServerSideCharacterConfig.ServerSideCharacterSave) + { + foreach (TSPlayer player in TShock.Players) + { + // prevent null point exceptions + if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval) + { + + DataModel.InsertPlayerData(player); + } + } + + Plugin.LastSave = DateTime.UtcNow; + } + } + + internal void OnGreetPlayer(GreetPlayerEventArgs args) + { + var player = TShock.Players[args.Who]; + + if (!player.IsLoggedIn) + { + player.IsDisabledForSSC = true; + player.SendErrorMessage(String.Format( + "Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier)); + player.LoginHarassed = true; + } + } + + private void OnLeave(LeaveEventArgs args) + { + var tsplr = TShock.Players[args.Who]; + if (tsplr == null) + { + return; + } + + if (tsplr.ReceivedInfo) + { + if (tsplr.IsLoggedIn && !tsplr.IsDisabledPendingTrashRemoval && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2)) + { + tsplr.PlayerData.CopyCharacter(tsplr); + DataModel.InsertPlayerData(tsplr); + } + } + } + } +} diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index f839019d6..370ff49c6 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -139,6 +139,11 @@ public class TShock : TerrariaPlugin /// internal RegionHandler RegionSystem; + /// + /// TShock's SSC subsystem. + /// + internal SSC SSC; + /// /// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. /// @@ -325,6 +330,8 @@ public override void Initialize() RestManager.RegisterRestfulCommands(); Bouncer = new Bouncer(); RegionSystem = new RegionHandler(Regions); + if(Main.ServerSideCharacter) + SSC = new SSC(this, DB); var geoippath = "GeoIP.dat"; if (Config.EnableGeoIP && File.Exists(geoippath)) @@ -897,10 +904,10 @@ private void OnPostInit(EventArgs args) } /// LastCheck - Used to keep track of the last check for basically all time based checks. - private DateTime LastCheck = DateTime.UtcNow; + internal DateTime LastCheck = DateTime.UtcNow; /// LastSave - Used to keep track of SSC save intervals. - private DateTime LastSave = DateTime.UtcNow; + internal DateTime LastSave = DateTime.UtcNow; /// OnUpdate - Called when ever the server ticks. /// args - EventArgs args @@ -914,20 +921,6 @@ private void OnUpdate(EventArgs args) OnSecondUpdate(); LastCheck = DateTime.UtcNow; } - - if (Main.ServerSideCharacter && (DateTime.UtcNow - LastSave).TotalMinutes >= ServerSideCharacterConfig.ServerSideCharacterSave) - { - foreach (TSPlayer player in Players) - { - // prevent null point exceptions - if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval) - { - - CharacterDB.InsertPlayerData(player); - } - } - LastSave = DateTime.UtcNow; - } } /// OnSecondUpdate - Called effectively every second for all time based checks. @@ -1634,13 +1627,7 @@ private void OnGreetPlayer(GreetPlayerEventArgs args) if (!player.IsLoggedIn) { - if (Main.ServerSideCharacter) - { - player.IsDisabledForSSC = true; - player.SendErrorMessage(String.Format("Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier)); - player.LoginHarassed = true; - } - else if (Config.RequireLogin) + if (Main.ServerSideCharacter && Config.RequireLogin) { player.SendErrorMessage("Please {0}register or {0}login to play!", Commands.Specifier); player.LoginHarassed = true; diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index b3deaaeaf..a76a2aeda 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -97,6 +97,7 @@ +