TShock/TShockAPI/TShock.cs

1924 lines
66 KiB
C#
Executable file

/*
TShock, a server mod for Terraria
Copyright (C) 2011-2015 Nyx Studios (fka. The TShock Team)
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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using MaxMind;
using Mono.Data.Sqlite;
using MySql.Data.MySqlClient;
using Newtonsoft.Json;
using Rests;
using Terraria;
using TerrariaApi.Server;
using TShockAPI.DB;
using TShockAPI.Hooks;
using TShockAPI.Net;
using TShockAPI.ServerSideCharacters;
namespace TShockAPI
{
/// <summary>
/// This is the TShock main class. TShock is a plugin on the TerrariaServerAPI, so it extends the base TerrariaPlugin.
/// TShock also complies with the API versioning system, and defines its required API version here.
/// </summary>
[ApiVersion(1, 18)]
public class TShock : TerrariaPlugin
{
/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
public static readonly string VersionCodename = "Yoraiz0r R0cks";
/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
public static string SavePath = "tshock";
/// <summary>LogFormatDefault - This is the default log file naming format. Actually, this is the only log format, because it never gets set again.</summary>
private const string LogFormatDefault = "yyyy-MM-dd_HH-mm-ss";
//TODO: Set the log path in the config file.
/// <summary>LogFormat - This is the log format, which is never set again.</summary>
private static string LogFormat = LogFormatDefault;
/// <summary>LogPathDefault - The default log path.</summary>
private const string LogPathDefault = "tshock";
/// <summary>This is the log path, which is initially set to the default log path, and then to the config file log path later.</summary>
private static string LogPath = LogPathDefault;
/// <summary>LogClear - Determines whether or not the log file should be cleared on initialization.</summary>
private static bool LogClear;
/// <summary>Players - Contains all TSPlayer objects for accessing TSPlayers currently on the server</summary>
public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers];
/// <summary>Bans - Static reference to the ban manager for accessing bans & related functions.</summary>
public static BanManager Bans;
/// <summary>Warps - Static reference to the warp manager for accessing the warp system.</summary>
public static WarpManager Warps;
/// <summary>Regions - Static reference to the region manager for accessing the region system.</summary>
public static RegionManager Regions;
/// <summary>Backups - Static reference to the backup manager for accessing the backup system.</summary>
public static BackupManager Backups;
/// <summary>Groups - Static reference to the group manager for accessing the group system.</summary>
public static GroupManager Groups;
/// <summary>Users - Static reference to the user manager for accessing the user database system.</summary>
public static UserManager Users;
/// <summary>Itembans - Static reference to the item ban system.</summary>
public static ItemManager Itembans;
/// <summary>ProjectileBans - Static reference to the projectile ban system.</summary>
public static ProjectileManagager ProjectileBans;
/// <summary>TileBans - Static reference to the tile ban system.</summary>
public static TileManager TileBans;
/// <summary>RememberedPos - Static reference to the remembered position manager.</summary>
public static RememberedPosManager RememberedPos;
/// <summary>CharacterDB - Static reference to the SSC character manager.</summary>
public static CharacterManager CharacterDB;
/// <summary>Config - Static reference to the config system, for accessing values set in users' config files.</summary>
public static ConfigFile Config { get; set; }
/// <summary>ServerSideCharacterConfig - Static reference to the server side character config, for accessing values set by users to modify SSC.</summary>
public static ServerSideConfig ServerSideCharacterConfig;
/// <summary>DB - Static reference to the database.</summary>
public static IDbConnection DB;
/// <summary>OverridePort - Determines if TShock should override the server port.</summary>
public static bool OverridePort;
/// <summary>PacketBuffer - Static reference to the packet bufferer system, which buffers packets to clients for better performance.</summary>
public static PacketBufferer PacketBuffer;
/// <summary>Geo - Static reference to the GeoIP system which determines the location of an IP address.</summary>
public static GeoIPCountry Geo;
/// <summary>RestApi - Static reference to the Rest API authentication manager.</summary>
public static SecureRest RestApi;
/// <summary>RestManager - Static reference to the Rest API manager.</summary>
public static RestManager RestManager;
/// <summary>Utils - Static reference to the utilities class, which contains a variety of utility functions.</summary>
public static Utils Utils = Utils.Instance;
/// <summary>StatTracker - Static reference to the stat tracker, which is created immediately when declared.</summary>
public static StatTracker StatTracker = new StatTracker();
/// <summary>UpdateManager - Static reference to the update checker, which checks for updates and notifies server admins of updates.</summary>
public static UpdateManager UpdateManager;
/// <summary>Log - Static reference to the log system, which outputs to either SQL or a text file, depending on user config.</summary>
public static ILog Log;
/// <summary>instance - Static reference to the TerrariaPlugin instance.</summary>
public static TerrariaPlugin instance;
/// <summary>
/// Used for implementing REST Tokens prior to the REST system starting up.
/// </summary>
public static Dictionary<string, SecureRest.TokenData> RESTStartupTokens = new Dictionary<string, SecureRest.TokenData>();
/// <summary>
/// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded.
/// </summary>
public static event Action Initialized;
/// <summary>Version - The version required by the TerrariaAPI to be passed back for checking & loading the plugin.</summary>
/// <value>value - The version number specified in the Assembly, based on the VersionNum variable set in this class.</value>
public override Version Version
{
get { return VersionNum; }
}
/// <summary>Name - The plugin name.</summary>
/// <value>value - "TShock"</value>
public override string Name
{
get { return "TShock"; }
}
/// <summary>Author - The author of the plugin.</summary>
/// <value>value - "The TShock Team"</value>
public override string Author
{
get { return "The TShock Team"; }
}
/// <summary>Description - The plugin description.</summary>
/// <value>value - "The administration modification of the future."</value>
public override string Description
{
get { return "The administration modification of the future."; }
}
/// <summary>TShock - The constructor for the TShock plugin.</summary>
/// <param name="game">game - The Terraria main game.</param>
public TShock(Main game)
: base(game)
{
Config = new ConfigFile();
ServerSideCharacterConfig = new ServerSideConfig();
ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-15, 1, 0));
ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-13, 1, 0));
ServerSideCharacterConfig.StartingInventory.Add(new NetItem(-16, 1, 0));
Order = 0;
instance = this;
}
/// <summary>Initialize - Called by the TerrariaServerAPI during initialization.</summary>
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
public override void Initialize()
{
string logFilename;
string logPathSetupWarning;
try
{
HandleCommandLine(Environment.GetCommandLineArgs());
if (!Directory.Exists(SavePath))
Directory.CreateDirectory(SavePath);
ConfigFile.ConfigRead += OnConfigRead;
FileTools.SetupConfig();
Main.ServerSideCharacter = ServerSideCharacterConfig.Enabled;
DateTime now = DateTime.Now;
// Log path was not already set by the command line parameter?
if (LogPath == LogPathDefault)
LogPath = Config.LogPath;
try
{
logFilename = Path.Combine(LogPath, now.ToString(LogFormat) + ".log");
if (!Directory.Exists(LogPath))
Directory.CreateDirectory(LogPath);
}
catch (Exception ex)
{
logPathSetupWarning =
"Could not apply the given log path / log format, defaults will be used. Exception details:\n" + ex;
ServerApi.LogWriter.PluginWriteLine(this, logPathSetupWarning, TraceLevel.Error);
// Problem with the log path or format use the default
logFilename = Path.Combine(LogPathDefault, now.ToString(LogFormatDefault) + ".log");
}
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}
catch (Exception ex)
{
// Will be handled by the server api and written to its crashlog.txt.
throw new Exception("Fatal TShock initialization exception. See inner exception for details.", ex);
}
// Further exceptions are written to TShock's log from now on.
try
{
if (Config.StorageType.ToLower() == "sqlite")
{
string sql = Path.Combine(SavePath, "tshock.sqlite");
DB = new SqliteConnection(string.Format("uri=file://{0},Version=3", sql));
}
else if (Config.StorageType.ToLower() == "mysql")
{
try
{
var hostport = Config.MySqlHost.Split(':');
DB = new MySqlConnection();
DB.ConnectionString =
String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};",
hostport[0],
hostport.Length > 1 ? hostport[1] : "3306",
Config.MySqlDbName,
Config.MySqlUsername,
Config.MySqlPassword
);
}
catch (MySqlException ex)
{
ServerApi.LogWriter.PluginWriteLine(this, ex.ToString(), TraceLevel.Error);
throw new Exception("MySql not setup correctly");
}
}
else
{
throw new Exception("Invalid storage type");
}
if (Config.UseSqlLogs)
Log = new SqlLog(DB, logFilename, LogClear);
else
Log = new TextLog(logFilename, LogClear);
if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
{
Log.ConsoleInfo(
"TShock was improperly shut down. Please use the exit command in the future to prevent this.");
File.Delete(Path.Combine(SavePath, "tshock.pid"));
}
File.WriteAllText(Path.Combine(SavePath, "tshock.pid"),
Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));
HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
Backups = new BackupManager(Path.Combine(SavePath, "backups"));
Backups.KeepFor = Config.BackupKeepFor;
Backups.Interval = Config.BackupInterval;
Bans = new BanManager(DB);
Warps = new WarpManager(DB);
Regions = new RegionManager(DB);
Users = new UserManager(DB);
Groups = new GroupManager(DB);
Itembans = new ItemManager(DB);
ProjectileBans = new ProjectileManagager(DB);
TileBans = new TileManager(DB);
RememberedPos = new RememberedPosManager(DB);
CharacterDB = new CharacterManager(DB);
RestApi = new SecureRest(Netplay.ServerIP, Config.RestApiPort);
RestApi.Port = Config.RestApiPort;
RestManager = new RestManager(RestApi);
RestManager.RegisterRestfulCommands();
var geoippath = Path.Combine(SavePath, "GeoIP.dat");
if (Config.EnableGeoIP && File.Exists(geoippath))
Geo = new GeoIPCountry(geoippath);
Log.ConsoleInfo("TShock {0} ({1}) now running.", Version, VersionCodename);
ServerApi.Hooks.GamePostInitialize.Register(this, OnPostInit);
ServerApi.Hooks.GameUpdate.Register(this, OnUpdate);
ServerApi.Hooks.GameHardmodeTileUpdate.Register(this, OnHardUpdate);
ServerApi.Hooks.GameStatueSpawn.Register(this, OnStatueSpawn);
ServerApi.Hooks.ServerConnect.Register(this, OnConnect);
ServerApi.Hooks.ServerJoin.Register(this, OnJoin);
ServerApi.Hooks.ServerLeave.Register(this, OnLeave);
ServerApi.Hooks.ServerChat.Register(this, OnChat);
ServerApi.Hooks.ServerCommand.Register(this, ServerHooks_OnCommand);
ServerApi.Hooks.NetGetData.Register(this, OnGetData);
ServerApi.Hooks.NetSendData.Register(this, NetHooks_SendData);
ServerApi.Hooks.NetGreetPlayer.Register(this, OnGreetPlayer);
ServerApi.Hooks.NpcStrike.Register(this, NpcHooks_OnStrikeNpc);
ServerApi.Hooks.ProjectileSetDefaults.Register(this, OnProjectileSetDefaults);
ServerApi.Hooks.WorldStartHardMode.Register(this, OnStartHardMode);
ServerApi.Hooks.WorldSave.Register(this, SaveManager.Instance.OnSaveWorld);
ServerApi.Hooks.WorldChristmasCheck.Register(this, OnXmasCheck);
ServerApi.Hooks.WorldHalloweenCheck.Register(this, OnHalloweenCheck);
ServerApi.Hooks.NetNameCollision.Register(this, NetHooks_NameCollision);
Hooks.PlayerHooks.PlayerPreLogin += OnPlayerPreLogin;
Hooks.PlayerHooks.PlayerPostLogin += OnPlayerLogin;
Hooks.AccountHooks.AccountDelete += OnAccountDelete;
Hooks.AccountHooks.AccountCreate += OnAccountCreate;
GetDataHandlers.InitGetDataHandler();
Commands.InitCommands();
if (Config.RestApiEnabled)
RestApi.Start();
if (Config.BufferPackets)
PacketBuffer = new PacketBufferer(this);
Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled"));
Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled"));
if (Initialized != null)
Initialized();
Log.ConsoleInfo("Welcome to TShock for Terraria. Initialization complete.");
}
catch (Exception ex)
{
Log.Error("Fatal Startup Exception");
Log.Error(ex.ToString());
Environment.Exit(1);
}
}
/// <summary>Dispose - Called when disposing.</summary>
/// <param name="disposing">disposing - If set, disposes of all hooks and other systems.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
// NOTE: order is important here
if (Geo != null)
{
Geo.Dispose();
}
SaveManager.Instance.Dispose();
ServerApi.Hooks.GamePostInitialize.Deregister(this, OnPostInit);
ServerApi.Hooks.GameUpdate.Deregister(this, OnUpdate);
ServerApi.Hooks.GameHardmodeTileUpdate.Deregister(this, OnHardUpdate);
ServerApi.Hooks.GameStatueSpawn.Deregister(this, OnStatueSpawn);
ServerApi.Hooks.ServerConnect.Deregister(this, OnConnect);
ServerApi.Hooks.ServerJoin.Deregister(this, OnJoin);
ServerApi.Hooks.ServerLeave.Deregister(this, OnLeave);
ServerApi.Hooks.ServerChat.Deregister(this, OnChat);
ServerApi.Hooks.ServerCommand.Deregister(this, ServerHooks_OnCommand);
ServerApi.Hooks.NetGetData.Deregister(this, OnGetData);
ServerApi.Hooks.NetSendData.Deregister(this, NetHooks_SendData);
ServerApi.Hooks.NetGreetPlayer.Deregister(this, OnGreetPlayer);
ServerApi.Hooks.NpcStrike.Deregister(this, NpcHooks_OnStrikeNpc);
ServerApi.Hooks.ProjectileSetDefaults.Deregister(this, OnProjectileSetDefaults);
ServerApi.Hooks.WorldStartHardMode.Deregister(this, OnStartHardMode);
ServerApi.Hooks.WorldSave.Deregister(this, SaveManager.Instance.OnSaveWorld);
ServerApi.Hooks.WorldChristmasCheck.Deregister(this, OnXmasCheck);
ServerApi.Hooks.WorldHalloweenCheck.Deregister(this, OnHalloweenCheck);
ServerApi.Hooks.NetNameCollision.Deregister(this, NetHooks_NameCollision);
TShockAPI.Hooks.PlayerHooks.PlayerPostLogin -= OnPlayerLogin;
if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
{
File.Delete(Path.Combine(SavePath, "tshock.pid"));
}
RestApi.Dispose();
Log.Dispose();
}
base.Dispose(disposing);
}
/// <summary>OnPlayerLogin - Fires the PlayerLogin hook to listening plugins.</summary>
/// <param name="args">args - The PlayerPostLoginEventArgs object.</param>
private void OnPlayerLogin(PlayerPostLoginEventArgs args)
{
List<String> KnownIps = new List<string>();
if (!string.IsNullOrWhiteSpace(args.Player.User.KnownIps))
{
KnownIps = JsonConvert.DeserializeObject<List<String>>(args.Player.User.KnownIps);
}
bool found = KnownIps.Any(s => s.Equals(args.Player.IP));
if (!found)
{
if (KnownIps.Count == 100)
{
KnownIps.RemoveAt(0);
}
KnownIps.Add(args.Player.IP);
}
args.Player.User.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
Users.UpdateLogin(args.Player.User);
}
/// <summary>OnAccountDelete - Internal hook fired on account delete.</summary>
/// <param name="args">args - The AccountDeleteEventArgs object.</param>
private void OnAccountDelete(Hooks.AccountDeleteEventArgs args)
{
CharacterDB.RemovePlayer(args.User.ID);
}
/// <summary>OnAccountCreate - Internal hook fired on account creation.</summary>
/// <param name="args">args - The AccountCreateEventArgs object.</param>
private void OnAccountCreate(Hooks.AccountCreateEventArgs args)
{
CharacterDB.SeedInitialData(Users.GetUser(args.User));
}
/// <summary>OnPlayerPreLogin - Internal hook fired when on player pre login.</summary>
/// <param name="args">args - The PlayerPreLoginEventArgs object.</param>
private void OnPlayerPreLogin(Hooks.PlayerPreLoginEventArgs args)
{
if (args.Player.IsLoggedIn)
args.Player.SaveServerCharacter();
if (args.Player.ItemInHand.type != 0)
{
args.Player.SendErrorMessage("Attempting to bypass SSC with item in hand.");
args.Handled = true;
}
}
/// <summary>NetHooks_NameCollision - Internal hook fired when a name collision happens.</summary>
/// <param name="args">args - The NameCollisionEventArgs object.</param>
private void NetHooks_NameCollision(NameCollisionEventArgs args)
{
string ip = Utils.GetRealIP(Netplay.Clients[args.Who].Socket.GetRemoteAddress().ToString());
var player = Players.First(p => p != null && p.Name == args.Name && p.Index != args.Who);
if (player != null)
{
if (player.IP == ip)
{
Netplay.Clients[player.Index].PendingTermination = true;
args.Handled = true;
return;
}
if (player.IsLoggedIn)
{
var ips = JsonConvert.DeserializeObject<List<string>>(player.User.KnownIps);
if (ips.Contains(ip))
{
Netplay.Clients[player.Index].PendingTermination = true;
args.Handled = true;
}
}
}
}
/// <summary>OnXmasCheck - Internal hook fired when the XMasCheck happens.</summary>
/// <param name="args">args - The ChristmasCheckEventArgs object.</param>
private void OnXmasCheck(ChristmasCheckEventArgs args)
{
if (args.Handled)
return;
if (Config.ForceXmas)
{
args.Xmas = true;
args.Handled = true;
}
}
/// <summary>OnHalloweenCheck - Internal hook fired when the HalloweenCheck happens.</summary>
/// <param name="args">args - The HalloweenCheckEventArgs object.</param>
private void OnHalloweenCheck(HalloweenCheckEventArgs args)
{
if (args.Handled)
return;
if (Config.ForceHalloween)
{
args.Halloween = true;
args.Handled = true;
}
}
/// <summary>
/// Handles exceptions that we didn't catch earlier in the code, or in Terraria.
/// </summary>
/// <param name="sender">sender - The object that sent the exception.</param>
/// <param name="e">e - The UnhandledExceptionEventArgs object.</param>
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Log.Error(e.ExceptionObject.ToString());
if (e.ExceptionObject.ToString().Contains("Terraria.Netplay.ListenForClients") ||
e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop"))
{
var sb = new List<string>();
for (int i = 0; i < Netplay.Clients.Length; i++)
{
if (Netplay.Clients[i] == null)
{
sb.Add("Client[" + i + "]");
}
else if (Netplay.Clients[i].Socket == null)
{
sb.Add("Tcp[" + i + "]");
}
}
Log.Error(string.Join(", ", sb));
}
if (e.IsTerminating)
{
if (Main.worldPathName != null && Config.SaveWorldOnCrash)
{
Main.worldPathName += ".crash";
SaveManager.Instance.SaveWorld();
}
}
}
/// <summary>HandleCommandLine - Handles the command line parameters passed to the server.</summary>
/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
private void HandleCommandLine(string[] parms)
{
string path;
for (int i = 0; i < parms.Length; i++)
{
switch (parms[i].ToLower())
{
case "-configpath":
path = parms[++i];
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
SavePath = path;
ServerApi.LogWriter.PluginWriteLine(this, "Config path has been set to " + path, TraceLevel.Info);
}
break;
case "-worldpath":
path = parms[++i];
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
Main.WorldPath = path;
ServerApi.LogWriter.PluginWriteLine(this, "World path has been set to " + path, TraceLevel.Info);
}
break;
case "-logpath":
path = parms[++i];
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1)
{
LogPath = path;
ServerApi.LogWriter.PluginWriteLine(this, "Log path has been set to " + path, TraceLevel.Info);
}
break;
case "-logformat":
LogFormat = parms[++i];
break;
case "-logclear":
bool.TryParse(parms[++i], out LogClear);
break;
case "-dump":
ConfigFile.DumpDescriptions();
Permissions.DumpDescriptions();
ServerSideConfig.DumpDescriptions();
RestManager.DumpDescriptions();
Environment.Exit(1);
break;
}
}
}
/// <summary>HandleCommandLinePostConfigLoad - Handles additional command line options after the config file is read.</summary>
/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
public static void HandleCommandLinePostConfigLoad(string[] parms)
{
for (int i = 0; i < parms.Length; i++)
{
switch (parms[i].ToLower())
{
case "-port":
int port = Convert.ToInt32(parms[++i]);
Netplay.ListenPort = port;
Config.ServerPort = port;
OverridePort = true;
Log.ConsoleInfo("Port overridden by startup argument. Set to " + port);
break;
case "-rest-token":
string token = Convert.ToString(parms[++i]);
RESTStartupTokens.Add(token, new SecureRest.TokenData { Username = "null", UserGroupName = "superadmin" });
Console.WriteLine("Startup parameter overrode REST token.");
break;
case "-rest-enabled":
Config.RestApiEnabled = Convert.ToBoolean(parms[++i]);
Console.WriteLine("Startup parameter overrode REST enable.");
break;
case "-rest-port":
Config.RestApiPort = Convert.ToInt32(parms[++i]);
Console.WriteLine("Startup parameter overrode REST port.");
break;
case "-maxplayers":
case "-players":
Config.MaxSlots = Convert.ToInt32(parms[++i]);
Console.WriteLine("Startup parameter overrode maximum player slot configuration value.");
break;
}
}
}
/// <summary>AuthToken - The auth token used by the /auth system to grant temporary superadmin access to new admins.</summary>
public static int AuthToken = -1;
/// <summary>OnPostInit - Fired when the server loads a map, to perform world specific operations.</summary>
/// <param name="args">args - The EventArgs object.</param>
private void OnPostInit(EventArgs args)
{
SetConsoleTitle(false);
if (!File.Exists(Path.Combine(SavePath, "auth.lck")) && !File.Exists(Path.Combine(SavePath, "authcode.txt")))
{
var r = new Random((int)DateTime.Now.ToBinary());
AuthToken = r.Next(100000, 10000000);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("TShock Notice: To become SuperAdmin, join the game and type {0}auth {1}", Commands.Specifier, AuthToken);
Console.WriteLine("This token will display until disabled by verification. ({0}auth-verify)", Commands.Specifier);
Console.ForegroundColor = ConsoleColor.Gray;
FileTools.CreateFile(Path.Combine(SavePath, "authcode.txt"));
using (var tw = new StreamWriter(Path.Combine(SavePath, "authcode.txt")))
{
tw.WriteLine(AuthToken);
}
}
else if (File.Exists(Path.Combine(SavePath, "authcode.txt")))
{
using (var tr = new StreamReader(Path.Combine(SavePath, "authcode.txt")))
{
AuthToken = Convert.ToInt32(tr.ReadLine());
}
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(
"TShock Notice: authcode.txt is still present, and the AuthToken located in that file will be used.");
Console.WriteLine("To become superadmin, join the game and type {0}auth {1}", Commands.Specifier, AuthToken);
Console.WriteLine("This token will display until disabled by verification. ({0}auth-verify)", Commands.Specifier);
Console.ForegroundColor = ConsoleColor.Gray;
}
else
{
AuthToken = 0;
}
Regions.Reload();
Warps.ReloadWarps();
Lighting.lightMode = 2;
ComputeMaxStyles();
FixChestStacks();
UpdateManager = new UpdateManager();
StatTracker.Initialize();
}
/// <summary>ComputeMaxStyles - Computes the max styles...</summary>
private void ComputeMaxStyles()
{
var item = new Item();
for (int i = 0; i < Main.maxItemTypes; i++)
{
item.netDefaults(i);
if (item.placeStyle > 0)
{
if (GetDataHandlers.MaxPlaceStyles.ContainsKey(item.createTile))
{
if (item.placeStyle > GetDataHandlers.MaxPlaceStyles[item.createTile])
GetDataHandlers.MaxPlaceStyles[item.createTile] = item.placeStyle;
}
else
GetDataHandlers.MaxPlaceStyles.Add(item.createTile, item.placeStyle);
}
}
}
/// <summary>FixChestStacks - Verifies that each stack in each chest is valid and not over the max stack count.</summary>
private void FixChestStacks()
{
if (Config.IgnoreChestStacksOnLoad)
return;
foreach (Chest chest in Main.chest)
{
if (chest != null)
{
foreach (Item item in chest.item)
{
if (item != null && item.stack > item.maxStack)
item.stack = item.maxStack;
}
}
}
}
/// <summary>LastCheck - Used to keep track of the last check for basically all time based checks.</summary>
private DateTime LastCheck = DateTime.UtcNow;
/// <summary>LastSave - Used to keep track of SSC save intervals.</summary>
private DateTime LastSave = DateTime.UtcNow;
/// <summary>OnUpdate - Called when ever the server ticks.</summary>
/// <param name="args">args - EventArgs args</param>
private void OnUpdate(EventArgs args)
{
if (Backups.IsBackupTime)
Backups.Backup();
//call these every second, not every update
if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1)
{
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.IgnoreActionsForClearingTrashCan)
{
CharacterDB.InsertPlayerData(player);
}
}
LastSave = DateTime.UtcNow;
}
}
/// <summary>OnSecondUpdate - Called effectively every second for all time based checks.</summary>
private void OnSecondUpdate()
{
if (Config.ForceTime != "normal")
{
switch (Config.ForceTime)
{
case "day":
TSPlayer.Server.SetTime(true, 27000.0);
break;
case "night":
TSPlayer.Server.SetTime(false, 16200.0);
break;
}
}
foreach (TSPlayer player in Players)
{
if (player != null && player.Active)
{
if (player.TilesDestroyed != null)
{
if (player.TileKillThreshold >= Config.TileKillThreshold)
{
player.Disable("Reached TileKill threshold.");
TSPlayer.Server.RevertTiles(player.TilesDestroyed);
player.TilesDestroyed.Clear();
}
}
if (player.TileKillThreshold > 0)
{
player.TileKillThreshold = 0;
//We don't want to revert the entire map in case of a disable.
player.TilesDestroyed.Clear();
}
if (player.TilesCreated != null)
{
if (player.TilePlaceThreshold >= Config.TilePlaceThreshold)
{
player.Disable("Reached TilePlace threshold");
TSPlayer.Server.RevertTiles(player.TilesCreated);
player.TilesCreated.Clear();
}
}
if (player.TilePlaceThreshold > 0)
{
player.TilePlaceThreshold = 0;
}
if (player.RecentFuse > 0)
player.RecentFuse--;
if ((Main.ServerSideCharacter) && (player.TPlayer.SpawnX > 0) && (player.sX != player.TPlayer.SpawnX))
{
player.sX = player.TPlayer.SpawnX;
player.sY = player.TPlayer.SpawnY;
}
if ((Main.ServerSideCharacter) && (player.sX > 0) && (player.sY > 0) && (player.TPlayer.SpawnX < 0))
{
player.TPlayer.SpawnX = player.sX;
player.TPlayer.SpawnY = player.sY;
}
if (player.RPPending > 0)
{
if (player.RPPending == 1)
{
var pos = RememberedPos.GetLeavePos(player.Name, player.IP);
player.Teleport(pos.X * 16, pos.Y * 16);
player.RPPending = 0;
}
else
{
player.RPPending--;
}
}
if (player.TileLiquidThreshold >= Config.TileLiquidThreshold)
{
player.Disable("Reached TileLiquid threshold");
}
if (player.TileLiquidThreshold > 0)
{
player.TileLiquidThreshold = 0;
}
if (player.ProjectileThreshold >= Config.ProjectileThreshold)
{
player.Disable("Reached projectile threshold");
}
if (player.ProjectileThreshold > 0)
{
player.ProjectileThreshold = 0;
}
if (player.PaintThreshold >= Config.TilePaintThreshold)
{
player.Disable("Reached paint threshold");
}
if (player.PaintThreshold > 0)
{
player.PaintThreshold = 0;
}
if (player.RespawnTimer > 0 && --player.RespawnTimer == 0 && player.Difficulty != 2)
{
player.Spawn();
}
string check = "none";
foreach (Item item in player.TPlayer.inventory)
{
if (!player.Group.HasPermission(Permissions.ignorestackhackdetection) && (item.stack > item.maxStack || item.stack < 0) &&
item.type != 0)
{
check = "Remove item " + item.name + " (" + item.stack + ") exceeds max stack of " + item.maxStack;
player.SendErrorMessage(check);
break;
}
}
player.IgnoreActionsForCheating = check;
check = "none";
// Please don't remove this for the time being; without it, players wearing banned equipment will only get debuffed once
foreach (Item item in player.TPlayer.armor)
{
if (Itembans.ItemIsBanned(item.name, player))
{
player.SetBuff(30, 120); //Bleeding
player.SetBuff(36, 120); //Broken Armor
check = "Remove armor/accessory " + item.name;
player.SendErrorMessage("You are wearing banned equipment. {0}", check);
break;
}
}
player.IgnoreActionsForDisabledArmor = check;
if (CheckIgnores(player))
{
player.Disable("check ignores failed in SecondUpdate()", false);
}
else if (Itembans.ItemIsBanned(player.TPlayer.inventory[player.TPlayer.selectedItem].name, player))
{
player.SetBuff(23, 120); //Cursed
}
var oldRegion = player.CurrentRegion;
player.CurrentRegion = Regions.GetTopRegion(Regions.InAreaRegion(player.TileX, player.TileY));
if (oldRegion != player.CurrentRegion)
{
if (oldRegion != null)
{
Hooks.RegionHooks.OnRegionLeft(player, oldRegion);
}
if (player.CurrentRegion != null)
{
Hooks.RegionHooks.OnRegionEntered(player);
}
}
}
}
SetConsoleTitle(false);
}
/// <summary>SetConsoleTitle - Updates the console title with some pertinent information.</summary>
/// <param name="empty">empty - True/false if the server is empty; determines if we should use Utils.ActivePlayers() for player count or 0.</param>
private void SetConsoleTitle(bool empty)
{
Console.Title = string.Format("{0}{1}/{2} @ {3}:{4} (TShock for Terraria v{5})",
!string.IsNullOrWhiteSpace(Config.ServerName) ? Config.ServerName + " - " : "",
empty ? 0 : Utils.ActivePlayers(),
Config.MaxSlots, Netplay.ServerIP.ToString(), Netplay.ListenPort, Version);
}
/// <summary>OnHardUpdate - Fired when a hardmode tile update event happens.</summary>
/// <param name="args">args - The HardmodeTileUpdateEventArgs object.</param>
private void OnHardUpdate(HardmodeTileUpdateEventArgs args)
{
if (args.Handled)
return;
if (!Config.AllowCrimsonCreep && (args.Type == 0 || args.Type == 199 || args.Type == 200 || args.Type == 203
|| args.Type == 234))
{
args.Handled = true;
return;
}
if (!Config.AllowCorruptionCreep && (args.Type == 23 || args.Type == 25 || args.Type == 0 ||
args.Type == 112 || args.Type == 32))
{
args.Handled = true;
return;
}
if (!Config.AllowHallowCreep && (args.Type == 109 || args.Type == 117 || args.Type == 116 || args.Type == 115
|| args.Type == 164))
{
args.Handled = true;
}
}
/// <summary>OnStatueSpawn - Fired when a statue spawns.</summary>
/// <param name="args">args - The StatueSpawnEventArgs object.</param>
private void OnStatueSpawn(StatueSpawnEventArgs args)
{
if (args.Within200 < Config.StatueSpawn200 && args.Within600 < Config.StatueSpawn600 && args.WorldWide < Config.StatueSpawnWorld)
{
args.Handled = true;
}
else
{
args.Handled = false;
}
}
/// <summary>OnConnect - Fired when a player connects to the server.</summary>
/// <param name="args">args - The ConnectEventArgs object.</param>
private void OnConnect(ConnectEventArgs args)
{
var player = new TSPlayer(args.Who);
if (Utils.ActivePlayers() + 1 > Config.MaxSlots + Config.ReservedSlots)
{
Utils.ForceKick(player, Config.ServerFullNoReservedReason, true, false);
args.Handled = true;
return;
}
if (!FileTools.OnWhitelist(player.IP))
{
Utils.ForceKick(player, Config.WhitelistKickReason, true, false);
args.Handled = true;
return;
}
if (Geo != null)
{
var code = Geo.TryGetCountryCode(IPAddress.Parse(player.IP));
player.Country = code == null ? "N/A" : GeoIPCountry.GetCountryNameByCode(code);
if (code == "A1")
{
if (Config.KickProxyUsers)
{
Utils.ForceKick(player, "Proxies are not allowed.", true, false);
args.Handled = true;
return;
}
}
}
Players[args.Who] = player;
}
/// <summary>OnJoin - Internal hook called when a player joins. This is called after OnConnect.</summary>
/// <param name="args">args - The JoinEventArgs object.</param>
private void OnJoin(JoinEventArgs args)
{
var player = Players[args.Who];
if (player == null)
{
args.Handled = true;
return;
}
if (Config.KickEmptyUUID && String.IsNullOrWhiteSpace(player.UUID))
{
Utils.ForceKick(player, "Your client did not send a UUID, this server is not configured to accept such a client.", true);
args.Handled = true;
return;
}
Ban ban = null;
if (Config.EnableBanOnUsernames)
{
var newban = Bans.GetBanByName(player.Name);
if (null != newban)
ban = newban;
}
if (Config.EnableIPBans && null == ban)
{
ban = Bans.GetBanByIp(player.IP);
}
if (Config.EnableUUIDBans && null == ban && !String.IsNullOrWhiteSpace(player.UUID))
{
ban = Bans.GetBanByUUID(player.UUID);
}
if (ban != null)
{
if (!Utils.HasBanExpired(ban))
{
DateTime exp;
if (!DateTime.TryParse(ban.Expiration, out exp))
{
player.Disconnect("You are banned forever: " + ban.Reason);
}
else
{
TimeSpan ts = exp - DateTime.UtcNow;
int months = ts.Days / 30;
if (months > 0)
{
player.Disconnect(String.Format("You are banned for {0} month{1} and {2} day{3}: {4}",
months, months == 1 ? "" : "s", ts.Days, ts.Days == 1 ? "" : "s", ban.Reason));
}
else if (ts.Days > 0)
{
player.Disconnect(String.Format("You are banned for {0} day{1} and {2} hour{3}: {4}",
ts.Days, ts.Days == 1 ? "" : "s", ts.Hours, ts.Hours == 1 ? "" : "s", ban.Reason));
}
else if (ts.Hours > 0)
{
player.Disconnect(String.Format("You are banned for {0} hour{1} and {2} minute{3}: {4}",
ts.Hours, ts.Hours == 1 ? "" : "s", ts.Minutes, ts.Minutes == 1 ? "" : "s", ban.Reason));
}
else if (ts.Minutes > 0)
{
player.Disconnect(String.Format("You are banned for {0} minute{1} and {2} second{3}: {4}",
ts.Minutes, ts.Minutes == 1 ? "" : "s", ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
}
else
{
player.Disconnect(String.Format("You are banned for {0} second{1}: {2}",
ts.Seconds, ts.Seconds == 1 ? "" : "s", ban.Reason));
}
}
args.Handled = true;
}
}
}
/// <summary>OnLeave - Called when a player leaves the server.</summary>
/// <param name="args">args - The LeaveEventArgs object.</param>
private void OnLeave(LeaveEventArgs args)
{
var tsplr = Players[args.Who];
Players[args.Who] = null;
if (tsplr != null && tsplr.ReceivedInfo)
{
if (!tsplr.SilentKickInProgress && tsplr.State >= 3)
Utils.Broadcast(tsplr.Name + " has left.", Color.Yellow);
Log.Info("{0} disconnected.", tsplr.Name);
if (tsplr.IsLoggedIn && !tsplr.IgnoreActionsForClearingTrashCan && Main.ServerSideCharacter && (!tsplr.Dead || tsplr.TPlayer.difficulty != 2))
{
tsplr.PlayerData.CopyCharacter(tsplr);
CharacterDB.InsertPlayerData(tsplr);
}
if (Config.RememberLeavePos && !tsplr.LoginHarassed)
{
RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int)(tsplr.X / 16), (int)(tsplr.Y / 16));
}
if (tsplr.tempGroupTimer != null)
{
tsplr.tempGroupTimer.Stop();
}
}
// Fire the OnPlayerLogout hook too, if the player was logged in and they have a TSPlayer object.
if (tsplr != null && tsplr.IsLoggedIn)
{
Hooks.PlayerHooks.OnPlayerLogout(tsplr);
}
// The last player will leave after this hook is executed.
if (Utils.ActivePlayers() == 1)
{
if (Config.SaveWorldOnLastPlayerExit)
SaveManager.Instance.SaveWorld();
SetConsoleTitle(true);
}
}
/// <summary>OnChat - Fired when a player chats. Used for handling chat and commands.</summary>
/// <param name="args">args - The ServerChatEventArgs object.</param>
private void OnChat(ServerChatEventArgs args)
{
if (args.Handled)
return;
var tsplr = Players[args.Who];
if (tsplr == null)
{
args.Handled = true;
return;
}
if (args.Text.Length > 500)
{
Utils.Kick(tsplr, "Crash attempt via long chat packet.", true);
args.Handled = true;
return;
}
if ((args.Text.StartsWith(Config.CommandSpecifier) || args.Text.StartsWith(Config.CommandSilentSpecifier))
&& !string.IsNullOrWhiteSpace(args.Text.Substring(1)))
{
try
{
args.Handled = Commands.HandleCommand(tsplr, args.Text);
}
catch (Exception ex)
{
Log.ConsoleError("An exeption occurred executing a command.");
Log.Error(ex.ToString());
}
}
else
{
if (!tsplr.Group.HasPermission(Permissions.canchat))
{
args.Handled = true;
}
else if (tsplr.mute)
{
tsplr.SendErrorMessage("You are muted!");
args.Handled = true;
}
else if (!TShock.Config.EnableChatAboveHeads)
{
var text = String.Format(Config.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix,
args.Text);
Hooks.PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text);
Utils.Broadcast(text, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
args.Handled = true;
}
else
{
Player ply = Main.player[args.Who];
string name = ply.name;
ply.name = String.Format(Config.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix);
NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, ply.name, args.Who, 0, 0, 0, 0);
ply.name = name;
var text = args.Text;
Hooks.PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text);
NetMessage.SendData((int)PacketTypes.ChatText, -1, args.Who, text, args.Who, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, -1, name, args.Who, 0, 0, 0, 0);
string msg = String.Format("<{0}> {1}",
String.Format(Config.ChatAboveHeadsFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix),
text);
tsplr.SendMessage(msg, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
TSPlayer.Server.SendMessage(msg, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B);
Log.Info("Broadcast: {0}", msg);
args.Handled = true;
}
}
}
/// <summary>
/// Called when a command is issued from the server console.
/// </summary>
/// <param name="args">The CommandEventArgs object</param>
private void ServerHooks_OnCommand(CommandEventArgs args)
{
if (args.Handled)
return;
// Damn you ThreadStatic and Redigit
if (Main.rand == null)
{
Main.rand = new Random();
}
if (WorldGen.genRand == null)
{
WorldGen.genRand = new Random();
}
if (args.Command == "autosave")
{
Main.autoSave = Config.AutoSave = !Config.AutoSave;
Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled"));
}
else if (args.Command.StartsWith(Commands.Specifier) || args.Command.StartsWith(Commands.SilentSpecifier))
{
Commands.HandleCommand(TSPlayer.Server, args.Command);
}
else
{
Commands.HandleCommand(TSPlayer.Server, "/" + args.Command);
}
args.Handled = true;
}
/// <summary>OnGetData - Called when the server gets raw data packets.</summary>
/// <param name="e">e - The GetDataEventArgs object.</param>
private void OnGetData(GetDataEventArgs e)
{
if (e.Handled)
return;
PacketTypes type = e.MsgID;
Debug.WriteLine("Recv: {0:X}: {2} ({1:XX})", e.Msg.whoAmI, (byte)type, type);
var player = Players[e.Msg.whoAmI];
if (player == null || !player.ConnectionAlive)
{
e.Handled = true;
return;
}
if (player.RequiresPassword && type != PacketTypes.PasswordSend)
{
e.Handled = true;
return;
}
if ((player.State < 10 || player.Dead) && (int)type > 12 && (int)type != 16 && (int)type != 42 && (int)type != 50 &&
(int)type != 38 && (int)type != 21 && (int)type != 22)
{
e.Handled = true;
return;
}
using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length - 1))
{
// Exceptions are already handled
e.Handled = GetDataHandlers.HandlerGetData(type, player, data);
}
}
/// <summary>OnGreetPlayer - Fired when a player is greeted by the server. Handles things like the MOTD, join messages, etc.</summary>
/// <param name="args">args - The GreetPlayerEventArgs object.</param>
private void OnGreetPlayer(GreetPlayerEventArgs args)
{
var player = Players[args.Who];
if (player == null)
{
args.Handled = true;
return;
}
player.LoginMS = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
if (Config.EnableGeoIP && TShock.Geo != null)
{
Log.Info("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", player.Name, player.IP,
player.Group.Name, player.Country, TShock.Utils.ActivePlayers(),
TShock.Config.MaxSlots);
if (!player.SilentJoinInProgress)
Utils.Broadcast(string.Format("{0} ({1}) has joined.", player.Name, player.Country), Color.Yellow);
}
else
{
Log.Info("{0} ({1}) from '{2}' group joined. ({3}/{4})", player.Name, player.IP,
player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots);
if (!player.SilentJoinInProgress)
Utils.Broadcast(player.Name + " has joined.", Color.Yellow);
}
if (Config.DisplayIPToAdmins)
Utils.SendLogs(string.Format("{0} has joined. IP: {1}", player.Name, player.IP), Color.Blue);
Utils.ShowFileToUser(player, "motd.txt");
string pvpMode = Config.PvPMode.ToLowerInvariant();
if (pvpMode == "always")
{
player.TPlayer.hostile = true;
player.SendData(PacketTypes.TogglePvp, "", player.Index);
TSPlayer.All.SendData(PacketTypes.TogglePvp, "", player.Index);
}
if (!player.IsLoggedIn)
{
if (Main.ServerSideCharacter)
{
player.SendErrorMessage(
player.IgnoreActionsForInventory = String.Format("Server side characters is enabled! Please {0}register or {0}login to play!", Commands.Specifier));
player.LoginHarassed = true;
}
else if (Config.RequireLogin)
{
player.SendErrorMessage("Please {0}register or {0}login to play!", Commands.Specifier);
player.LoginHarassed = true;
}
}
player.LastNetPosition = new Vector2(Main.spawnTileX * 16f, Main.spawnTileY * 16f);
if (Config.RememberLeavePos && (RememberedPos.GetLeavePos(player.Name, player.IP) != Vector2.Zero) && !player.LoginHarassed)
{
player.RPPending = 3;
player.SendInfoMessage("You will be teleported to your last known location...");
}
args.Handled = true;
}
/// <summary>NpcHooks_OnStrikeNpc - Fired when an NPC strike packet happens.</summary>
/// <param name="e">e - The NpcStrikeEventArgs object.</param>
private void NpcHooks_OnStrikeNpc(NpcStrikeEventArgs e)
{
if (Config.InfiniteInvasion)
{
if (Main.invasionSize < 10)
{
Main.invasionSize = 20000000;
}
}
}
/// <summary>OnProjectileSetDefaults - Called when a projectile sets the default attributes for itself.</summary>
/// <param name="e">e - The SetDefaultsEventArgs object praameterized with Projectile and int.</param>
private void OnProjectileSetDefaults(SetDefaultsEventArgs<Projectile, int> e)
{
//tombstone fix.
if (e.Info == 43 || (e.Info >= 201 && e.Info <= 205))
if (Config.DisableTombstones)
e.Object.SetDefaults(0);
if (e.Info == 75)
if (Config.DisableClownBombs)
e.Object.SetDefaults(0);
if (e.Info == 109)
if (Config.DisableSnowBalls)
e.Object.SetDefaults(0);
}
/// <summary>NetHooks_SendData - Fired when the server sends data.</summary>
/// <param name="e">e - The SendDataEventArgs object.</param>
private void NetHooks_SendData(SendDataEventArgs e)
{
/*if (e.MsgId == PacketTypes.Disconnect)
{
Action<RemoteClient, string> disconnect = (client, str) =>
{
if (client == null || !client.IsActive || client.Socket.IsConnected())
return;
using (var ms = new MemoryStream())
{
var msg = new DisconnectMsg() { Reason = str };
msg.PackFull(ms);
client.Socket.AsyncSend(ms.ToArray(), 0, (int)ms.Length, client.ServerWriteCallBack);
client.Socket.Close();
}
};
if (e.remoteClient != -1)
{
disconnect(Netplay.Clients[e.remoteClient], e.text);
}
else
{
for (int i = 0; i < Netplay.Clients.Length; i++)
{
if (e.ignoreClient != -1 && e.ignoreClient == i)
continue;
disconnect(Netplay.Clients[i], e.text);
}
}
e.Handled = true;
return;
}
else */if (e.MsgId == PacketTypes.WorldInfo)
{
using (var ms = new MemoryStream())
{
var msg = new WorldInfoMsg
{
Time = (int)Main.time,
DayTime = Main.dayTime,
MoonPhase = (byte)Main.moonPhase,
BloodMoon = Main.bloodMoon,
Eclipse = Main.eclipse,
MaxTilesX = (short)Main.maxTilesX,
MaxTilesY = (short)Main.maxTilesY,
SpawnX = (short)Main.spawnTileX,
SpawnY = (short)Main.spawnTileY,
WorldSurface = (short)Main.worldSurface,
RockLayer = (short)Main.rockLayer,
//Sending a fake world id causes the client to not be able to find a stored spawnx/y.
//This fixes the bed spawn point bug. With a fake world id it wont be able to find the bed spawn.
WorldID = Main.worldID,
MoonType = (byte)Main.moonType,
TreeX0 = Main.treeX[0],
TreeX1 = Main.treeX[1],
TreeX2 = Main.treeX[2],
TreeStyle0 = (byte)Main.treeStyle[0],
TreeStyle1 = (byte)Main.treeStyle[1],
TreeStyle2 = (byte)Main.treeStyle[2],
TreeStyle3 = (byte)Main.treeStyle[3],
CaveBackX0 = Main.caveBackX[0],
CaveBackX1 = Main.caveBackX[1],
CaveBackX2 = Main.caveBackX[2],
CaveBackStyle0 = (byte)Main.caveBackStyle[0],
CaveBackStyle1 = (byte)Main.caveBackStyle[1],
CaveBackStyle2 = (byte)Main.caveBackStyle[2],
CaveBackStyle3 = (byte)Main.caveBackStyle[3],
SetBG0 = (byte)WorldGen.treeBG,
SetBG1 = (byte)WorldGen.corruptBG,
SetBG2 = (byte)WorldGen.jungleBG,
SetBG3 = (byte)WorldGen.snowBG,
SetBG4 = (byte)WorldGen.hallowBG,
SetBG5 = (byte)WorldGen.crimsonBG,
SetBG6 = (byte)WorldGen.desertBG,
SetBG7 = (byte)WorldGen.oceanBG,
IceBackStyle = (byte)Main.iceBackStyle,
JungleBackStyle = (byte)Main.jungleBackStyle,
HellBackStyle = (byte)Main.hellBackStyle,
WindSpeed = Main.windSpeed,
NumberOfClouds = (byte)Main.numClouds,
BossFlags = (WorldGen.shadowOrbSmashed ? BossFlags.OrbSmashed : BossFlags.None) |
(NPC.downedBoss1 ? BossFlags.DownedBoss1 : BossFlags.None) |
(NPC.downedBoss2 ? BossFlags.DownedBoss2 : BossFlags.None) |
(NPC.downedBoss3 ? BossFlags.DownedBoss3 : BossFlags.None) |
(Main.hardMode ? BossFlags.HardMode : BossFlags.None) |
(NPC.downedClown ? BossFlags.DownedClown : BossFlags.None) |
(Main.ServerSideCharacter ? BossFlags.ServerSideCharacter : BossFlags.None) |
(NPC.downedPlantBoss ? BossFlags.DownedPlantBoss : BossFlags.None),
BossFlags2 = (NPC.downedMechBoss1 ? BossFlags2.DownedMechBoss1 : BossFlags2.None) |
(NPC.downedMechBoss2 ? BossFlags2.DownedMechBoss2 : BossFlags2.None) |
(NPC.downedMechBoss3 ? BossFlags2.DownedMechBoss3 : BossFlags2.None) |
(NPC.downedMechBossAny ? BossFlags2.DownedMechBossAny : BossFlags2.None) |
(Main.cloudBGActive >= 1f ? BossFlags2.CloudBg : BossFlags2.None) |
(WorldGen.crimson ? BossFlags2.Crimson : BossFlags2.None) |
(Main.pumpkinMoon ? BossFlags2.PumpkinMoon : BossFlags2.None) |
(Main.snowMoon ? BossFlags2.SnowMoon : BossFlags2.None),
BossFlags3 = (Main.expertMode ? BossFlags3.ExpertMode : BossFlags3.None) |
(Main.fastForwardTime ? BossFlags3.FastForwardTime : BossFlags3.None) |
(Main.slimeRain ? BossFlags3.SlimeRain : BossFlags3.None) |
(NPC.downedSlimeKing ? BossFlags3.DownedKingSlime : BossFlags3.None) |
(NPC.downedQueenBee ? BossFlags3.DownedQueenBee : BossFlags3.None) |
(NPC.downedFishron ? BossFlags3.DownedFishron : BossFlags3.None) |
(NPC.downedMartians ? BossFlags3.DownedMartians : BossFlags3.None) |
(NPC.downedAncientCultist ? BossFlags3.DownedAncientCultist : BossFlags3.None),
BossFlags4 = (NPC.downedMoonlord ? BossFlags4.DownedMoonLord : BossFlags4.None) |
(NPC.downedHalloweenKing ? BossFlags4.DownedHalloweenKing : BossFlags4.None) |
(NPC.downedHalloweenTree ? BossFlags4.DownedHalloweenTree : BossFlags4.None) |
(NPC.downedChristmasIceQueen ? BossFlags4.DownedChristmasIceQueen : BossFlags4.None) |
(NPC.downedChristmasSantank ? BossFlags4.DownedChristmasSantank : BossFlags4.None) |
(NPC.downedChristmasTree ? BossFlags4.DownedChristmasTree : BossFlags4.None),
Rain = Main.maxRaining,
WorldName = TShock.Config.UseServerName ? TShock.Config.ServerName : Main.worldName
};
msg.PackFull(ms);
if (e.remoteClient == -1)
{
TSPlayer.All.SendRawData(ms.ToArray());
}
else
{
var player = Players[e.remoteClient];
if (player == null) return;
player.SendRawData(ms.ToArray());
}
}
e.Handled = true;
return;
}
else if (e.MsgId == PacketTypes.PlayerHp)
{
if (Main.player[(byte)e.number].statLife <= 0)
{
e.Handled = true;
return;
}
}
}
/// <summary>OnStartHardMode - Fired when hard mode is started.</summary>
/// <param name="e">e - The HandledEventArgs object.</param>
private void OnStartHardMode(HandledEventArgs e)
{
if (Config.DisableHardmode)
e.Handled = true;
}
/// <summary>StartInvasion - Starts an invasion on the server.</summary>
/// <param name="type">type - The invasion type id.</param>
//TODO: Why is this in TShock's main class?
public static void StartInvasion(int type)
{
Main.invasionType = type;
if (Config.InfiniteInvasion)
{
Main.invasionSize = 20000000;
}
else
{
Main.invasionSize = 100 + (Config.InvasionMultiplier * Utils.ActivePlayers());
}
Main.invasionWarn = 0;
if (new Random().Next(2) == 0)
{
Main.invasionX = 0.0;
}
else
{
Main.invasionX = Main.maxTilesX;
}
}
/// <summary>CheckProjectilePermission - Checks if a projectile is banned.</summary>
/// <param name="player">player - The TSPlayer object that created the projectile.</param>
/// <param name="index">index - The projectile index.</param>
/// <param name="type">type - The projectile type.</param>
/// <returns>bool - True if the player does not have permission to use a projectile.</returns>
public static bool CheckProjectilePermission(TSPlayer player, int index, int type)
{
if (type == 43)
{
return true;
}
if (type == 17 && Itembans.ItemIsBanned("Dirt Rod", player))
//Dirt Rod Projectile
{
return true;
}
if ((type == 42 || type == 65 || type == 68) && Itembans.ItemIsBanned("Sandgun", player)) //Sandgun Projectiles
{
return true;
}
Projectile proj = new Projectile();
proj.SetDefaults(type);
if (Main.projHostile[type])
{
//player.SendMessage( proj.name, Color.Yellow);
return true;
}
return false;
}
/// <summary>CheckRangePermission - Checks if a player has permission to modify a tile dependent on range checks.</summary>
/// <param name="player">player - The TSPlayer object.</param>
/// <param name="x">x - The x coordinate of the tile.</param>
/// <param name="y">y - The y coordinate of the tile.</param>
/// <param name="range">range - The range to check for.</param>
/// <returns>bool - True if the player should not be able to place the tile. False if they can, or if range checks are off.</returns>
public static bool CheckRangePermission(TSPlayer player, int x, int y, int range = 32)
{
if (Config.RangeChecks && ((Math.Abs(player.TileX - x) > range) || (Math.Abs(player.TileY - y) > range)))
{
return true;
}
return false;
}
/// <summary>CheckTilePermission - Checks to see if a player has permission to modify a tile in general.</summary>
/// <param name="player">player - The TSPlayer object.</param>
/// <param name="tileX">tileX - The x coordinate of the tile.</param>
/// <param name="tileY">tileY - The y coordinate of the tile.</param>
/// <param name="tileType">tileType - The tile type.</param>
/// <param name="actionType">actionType - The type of edit that took place.</param>
/// <returns>bool - True if the player should not be able to modify a tile.</returns>
public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY, short tileType, GetDataHandlers.EditAction actionType)
{
if (!player.Group.HasPermission(Permissions.canbuild))
{
if (TShock.Config.AllowIce && actionType != GetDataHandlers.EditAction.PlaceTile)
{
foreach (Point p in player.IceTiles)
{
if (p.X == tileX && p.Y == tileY && (Main.tile[p.X, p.Y].type == 0 || Main.tile[p.X, p.Y].type == 127))
{
player.IceTiles.Remove(p);
return false;
}
}
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000)
{
player.SendErrorMessage("You do not have permission to build!");
player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
if (TShock.Config.AllowIce && actionType == GetDataHandlers.EditAction.PlaceTile && tileType == 127)
{
player.IceTiles.Add(new Point(tileX, tileY));
return false;
}
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000)
{
player.SendErrorMessage("You do not have permission to build!");
player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
if (!player.Group.HasPermission(Permissions.editregion) && !Regions.CanBuild(tileX, tileY, player) &&
Regions.InArea(tileX, tileY))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000)
{
player.SendErrorMessage("This region is protected from changes.");
player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
if (Config.DisableBuild)
{
if (!player.Group.HasPermission(Permissions.antibuild))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000)
{
player.SendErrorMessage("The world is protected from changes.");
player.WPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
}
if (Config.SpawnProtection)
{
if (!player.Group.HasPermission(Permissions.editspawn))
{
if (CheckSpawn(tileX, tileY))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 2000)
{
player.SendErrorMessage("Spawn is protected from changes.");
player.SPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
}
}
return false;
}
/// <summary>CheckTilePermission - Checks to see if a player has the ability to modify a tile at a given position.</summary>
/// <param name="player">player - The TSPlayer object.</param>
/// <param name="tileX">tileX - The x coordinate of the tile.</param>
/// <param name="tileY">tileY - The y coordinate of the tile.</param>
/// <param name="paint">paint - Whether or not the tile is paint.</param>
/// <returns>bool - True if the player should not be able to modify a the tile.</returns>
public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY, bool paint = false)
{
if ((!paint && !player.Group.HasPermission(Permissions.canbuild)) ||
(paint && !player.Group.HasPermission(Permissions.canpaint)))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000)
{
player.SendErrorMessage("You do not have permission to build!");
player.BPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
if (!player.Group.HasPermission(Permissions.editregion) && !Regions.CanBuild(tileX, tileY, player) &&
Regions.InArea(tileX, tileY))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000)
{
player.SendErrorMessage("This region is protected from changes.");
player.RPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
if (Config.DisableBuild)
{
if (!player.Group.HasPermission(Permissions.antibuild))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000)
{
player.SendErrorMessage("The world is protected from changes.");
player.WPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
}
if (Config.SpawnProtection)
{
if (!player.Group.HasPermission(Permissions.editspawn))
{
if (CheckSpawn(tileX, tileY))
{
if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 1000)
{
player.SendErrorMessage("Spawn is protected from changes.");
player.SPm = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
return true;
}
}
}
return false;
}
/// <summary>CheckSpawn - Checks to see if a location is inside the spawn protection zone.</summary>
/// <param name="x">x - The x coordinate to check.</param>
/// <param name="y">y - The y coordinate to check.</param>
/// <returns>bool - True if the location is inside the spawn protection zone.</returns>
public static bool CheckSpawn(int x, int y)
{
Vector2 tile = new Vector2(x, y);
Vector2 spawn = new Vector2(Main.spawnTileX, Main.spawnTileY);
return Distance(spawn, tile) <= Config.SpawnProtectionRadius;
}
/// <summary>Distance - Determines the distance between two vectors.</summary>
/// <param name="value1">value1 - The first vector location.</param>
/// <param name="value2">value2 - The second vector location.</param>
/// <returns>float - The distance between the two vectors.</returns>
public static float Distance(Vector2 value1, Vector2 value2)
{
float num2 = value1.X - value2.X;
float num = value1.Y - value2.Y;
float num3 = (num2 * num2) + (num * num);
return (float)Math.Sqrt(num3);
}
/// <summary>HackedInventory - Checks to see if a user has a hacked inventory. In addition, messages players the result.</summary>
/// <param name="player">player - The TSPlayer object.</param>
/// <returns>bool - True if the player has a hacked inventory.</returns>
public static bool HackedInventory(TSPlayer player)
{
bool check = false;
Item[] inventory = player.TPlayer.inventory;
Item[] armor = player.TPlayer.armor;
Item[] dye = player.TPlayer.dye;
Item[] miscEquips = player.TPlayer.miscEquips;
Item[] miscDyes = player.TPlayer.miscDyes;
for (int i = 0; i < NetItem.MaxInventory; i++)
{
if (i < NetItem.InventorySlots)
{
//0-58
Item item = new Item();
if (inventory[i] != null && inventory[i].netID != 0)
{
item.netDefaults(inventory[i].netID);
item.Prefix(inventory[i].prefix);
item.AffixName();
if (inventory[i].stack > item.maxStack)
{
check = true;
player.SendMessage(
String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.name, inventory[i].stack),
Color.Cyan);
}
}
}
else if (i < NetItem.InventorySlots
+ NetItem.DyeSlots + NetItem.MiscDyeSlots + NetItem.MiscEquipSlots)
{
//59-78
Item item = new Item();
var index = i - NetItem.InventorySlots;
if (armor[index] != null && armor[index].netID != 0)
{
item.netDefaults(armor[index].netID);
item.Prefix(armor[index].prefix);
item.AffixName();
if (armor[index].stack > item.maxStack)
{
check = true;
player.SendMessage(
String.Format("Stack cheat detected. Remove armor {0} ({1}) and then rejoin", item.name, armor[i - 48].stack),
Color.Cyan);
}
}
}
else if (i < NetItem.MaxInventory - NetItem.MiscEquipSlots - NetItem.MiscDyeSlots)
{
//79-88
Item item = new Item();
var index = i - (NetItem.MaxInventory -
(NetItem.DyeSlots + NetItem.MiscDyeSlots + NetItem.MiscEquipSlots));
if (dye[index] != null && dye[index].netID != 0)
{
item.netDefaults(dye[index].netID);
item.Prefix(dye[index].prefix);
item.AffixName();
if (dye[index].stack > item.maxStack)
{
check = true;
player.SendMessage(
String.Format("Stack cheat detected. Remove dye {0} ({1}) and then rejoin", item.name, dye[index].stack),
Color.Cyan);
}
}
}
else if (i < NetItem.MaxInventory - NetItem.MiscEquipSlots)
{
//89-93
Item item = new Item();
var index = i - (NetItem.MaxInventory - (NetItem.MiscDyeSlots + NetItem.MiscEquipSlots));
if (miscEquips[index] != null && miscEquips[index].netID != 0)
{
item.netDefaults(miscEquips[index].netID);
item.Prefix(miscEquips[index].prefix);
item.AffixName();
if (miscEquips[index].stack > item.maxStack)
{
check = true;
player.SendMessage(
String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.name, dye[index].stack),
Color.Cyan);
}
}
}
else
{
//93-98
Item item = new Item();
var index = i - (NetItem.MaxInventory - NetItem.MiscDyeSlots);
if (miscDyes[index] != null && miscDyes[index].netID != 0)
{
item.netDefaults(miscDyes[index].netID);
item.Prefix(miscDyes[index].prefix);
item.AffixName();
if (miscDyes[index].stack > item.maxStack)
{
check = true;
player.SendMessage(
String.Format("Stack cheat detected. Remove item dye {0} ({1}) and then rejoin", item.name, dye[index].stack),
Color.Cyan);
}
}
}
}
return check;
}
/// <summary>CheckIgnores - Checks a players ignores...?</summary>
/// <param name="player">player - The TSPlayer object.</param>
/// <returns>bool - True if any ignore is not none, false, or login state differs from the required state.</returns>
public static bool CheckIgnores(TSPlayer player)
{
return player.IgnoreActionsForInventory != "none" || player.IgnoreActionsForCheating != "none" || player.IgnoreActionsForDisabledArmor != "none" || player.IgnoreActionsForClearingTrashCan || !player.IsLoggedIn && Config.RequireLogin;
}
/// <summary>OnConfigRead - Fired when the config file has been read.</summary>
/// <param name="file">file - The config file object.</param>
public void OnConfigRead(ConfigFile file)
{
NPC.defaultMaxSpawns = file.DefaultMaximumSpawns;
NPC.defaultSpawnRate = file.DefaultSpawnRate;
Main.autoSave = file.AutoSave;
if (Backups != null)
{
Backups.KeepFor = file.BackupKeepFor;
Backups.Interval = file.BackupInterval;
}
if (!OverridePort)
{
Netplay.ListenPort = file.ServerPort;
}
if (file.MaxSlots > 235)
file.MaxSlots = 235;
Main.maxNetPlayers = file.MaxSlots + 20;
Netplay.ServerPassword = "";
Netplay.spamCheck = false;
}
}
}