/// <summary> /// Raises the <see cref="ValueChanged"/> event. /// </summary> /// <param name="oldVal">The old value as a string.</param> /// <param name="newVal">The new value as a string.</param> internal void OnConVarChanged(string oldVal, string newVal) { if (this.ValueChanged != null) { var args = new ConVarChangedEventArgs(this, oldVal, newVal); foreach (EventHandler <ConVarChangedEventArgs> d in this.ValueChanged.GetInvocationList()) { try { d.Invoke(this, args); } catch (HaltPluginException) { } catch (Exception e) { if (d.Target is IPlugin p) { p.Container.SetFailState(e); } else { SMLog.Error(e); } } } } }
/// <summary> /// Checks the status of this query and publishes the result if it has completed. /// </summary> /// <returns><c>true</c> if this query has completed; otherwise <c>false</c>.</returns> internal bool CheckStatus() { if (this.status == Status.Completed) { if (this.QueryCompleted != null) { var args = new QueryCompletedEventArgs(this.database, this.affectedRows, this.results, this.data, this.exception); foreach (EventHandler <QueryCompletedEventArgs> d in this.QueryCompleted.GetInvocationList()) { try { d.Invoke(this, args); } catch (HaltPluginException) { } catch (Exception e) { if (d.Target is IPlugin p) { p.Container.SetFailState(e); } else { SMLog.Error(e); } } } } this.status = Status.Published; } return(this.status == Status.Published); }
/// <summary> /// Sets the plugin to an error state. /// </summary> /// <param name="error">The error message.</param> public void SetFailState(string error) { this.LoadStatus = Status.Error; this.Error = error; this.Plugin = null; SMLog.Error(error, this.File); }
/// <summary> /// Sets the plugin to an error state. /// </summary> /// <param name="e">The exception causing the error state.</param> internal void SetFailState(Exception e) { this.LoadStatus = Status.Error; this.Error = e.Message; this.Plugin = null; SMLog.Error(e, this.File); }
/// <summary> /// Raises the <see cref="Ended"/> event. /// </summary> /// <param name="counts">The count of votes for each answer option.</param> /// <param name="percents">The percentage of eligible voters that voted for each answer option.</param> private void OnVoteEnded(int[] counts, float[] percents) { if (this.Ended != null) { var args = new VoteEndedEventArgs(this, this.voteOptions, counts, percents, this.data); foreach (EventHandler <VoteEndedEventArgs> d in this.Ended.GetInvocationList()) { try { d.Invoke(this, args); } catch (HaltPluginException) { } catch (Exception e) { if (d.Target is IPlugin p) { p.Container.SetFailState(e); } else { SMLog.Error(e); } } } } }
/// <summary> /// Raises the <see cref="Cancelled"/> event. /// </summary> private void OnVoteCancelled() { if (this.Cancelled != null) { foreach (EventHandler d in this.Cancelled.GetInvocationList()) { try { d.Invoke(this, EventArgs.Empty); } catch (HaltPluginException) { } catch (Exception e) { if (d.Target is IPlugin p) { p.Container.SetFailState(e); } else { SMLog.Error(e); } } } } }
/// <summary> /// Executes the admin command. /// </summary> /// <param name="arguments">The list of arguments for the command.</param> /// <param name="client">The <see cref="ClientInfo"/> object representing the client that is executing the command.</param> internal void OnExecute(List <string> arguments, ClientInfo client) { if (this.Executed != null) { var args = new AdminCommandEventArgs(this, arguments, client); foreach (EventHandler <AdminCommandEventArgs> d in this.Executed.GetInvocationList()) { try { d.Invoke(this, args); } catch (HaltPluginException) { } catch (Exception e) { if (d.Target is IPlugin p) { p.Container.SetFailState(e); } else { SMLog.Error(e); } } } } }
/// <summary> /// Processes a chat message. /// </summary> /// <param name="client">The <see cref="ClientInfo"/> object representing the client that sent the message.</param> /// <param name="type">The type of chat message.</param> /// <param name="message">The message text.</param> /// <param name="recipientEntityIds">The list of entity IDs receiving the message.</param> /// <returns><c>true</c> to allow the message to continue propagating; <c>false</c> to consume the message.</returns> internal static bool HookChatMessage(ClientInfo client, EChatType type, string message, List <int> recipientEntityIds) { if (client == null) { return(true); } if (ChatMessage != null) { var args = new ChatMessageEventArgs(client, type, message, recipientEntityIds); foreach (EventHandler <ChatMessageEventArgs> d in ChatMessage.GetInvocationList()) { try { d.Invoke(null, args); if (args.Handled) { return(false); } } catch (HaltPluginException) { args.Handled = false; } catch (Exception e) { args.Handled = false; if (d.Target is IPlugin p) { p.Container.SetFailState(e); } else { SMLog.Error(e); } } } } if (message.StartsWith(publicChatTrigger.AsString) || message.StartsWith(silentChatTrigger.AsString)) { replyToChat.Add(client.entityId); ConnectionManager.Instance.ServerConsoleCommand(client, $"sm {message.Substring(1)}"); replyToChat.Remove(client.entityId); return(message.StartsWith(publicChatTrigger.AsString)); } return(true); }
/// <summary> /// Executes a single automatically executed configuration file. /// </summary> /// <param name="config">The <see cref="ConfigInfo"/> object containing the configuration file metadata.</param> private static void ExecuteConfig(ConfigInfo config) { var path = $"{SMPath.Config}{config.Name}.xml"; if (!File.Exists(path)) { if (!config.AutoCreate) { return; } GenerateConfig(config); } var xml = new XmlDocument(); try { xml.Load(path); } catch (XmlException e) { SMLog.Error($"Failed reading configuration from {path}: {e.Message}"); return; } foreach (XmlElement element in xml.GetElementsByTagName("property")) { if (element.HasAttribute("name") && element.HasAttribute("value")) { if (conVars.TryGetValue(element.GetAttribute("name").Trim().ToLower(), out var conVar)) { conVar.Value.Value = element.GetAttribute("value"); } } } if (watcher == null) { watcher = new FileSystemWatcher(SMPath.Config, "*.xml") { NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName, }; watcher.Changed += OnConfigFileChanged; watcher.Deleted += OnConfigFileChanged; watcher.Renamed += OnConfigFileChanged; watcher.EnableRaisingEvents = true; } }
/// <summary> /// Loads the files for a set of phrases. /// </summary> /// <param name="file">The name of the translation file.</param> private static void LoadPhrases(string file) { var xml = new XmlDocument(); foreach (var path in Directory.GetFiles(SMPath.Translations, $"{file}.xml", SearchOption.AllDirectories)) { try { xml.Load(path); } catch (XmlException e) { SMLog.Error($"Failed reading translation file from {path}: {e.Message}"); continue; } foreach (XmlElement phraseElement in xml.GetElementsByTagName("Phrase")) { if (!phraseElement.HasAttribute("Name")) { continue; } var key = phraseElement.GetAttribute("Name"); if (phrases.ContainsKey(key)) { SMLog.Error($"Phrase \"{key}\" in file {file}.xml conflicts with file {phrases[key].File}.xml"); continue; } var pd = new PhraseDictionary(file, key); foreach (XmlElement formatElement in phraseElement.GetElementsByTagName("Format")) { var mc = Regex.Matches(formatElement.InnerText, @"\{([1-9][0-9]*)\:([0-9a-zA-Z]+)\}"); if (mc.Count == 0) { throw new Exception($"Invalid format string for translation file {file}: {formatElement.InnerText}"); } foreach (Match m in mc) { pd.FormatArgs[int.Parse(m.Groups[1].Value)] = m.Groups[2].Value; } break; } foreach (var langElement in phraseElement.ChildNodes) { if (langElement is XmlElement) { var lang = ((XmlElement)langElement).Name; var phraseString = ((XmlElement)langElement).InnerText; if (lang.Equals("Format")) { continue; } pd[lang] = phraseString; } } phrases[key] = pd; } } }
/// <summary> /// Loads a plugin. /// </summary> /// <param name="key">The dictionary key of the plugin.</param> /// <param name="refreshing">A value indicating whether the plugin list is being refreshed.</param> private static void Load(string key, bool refreshing) { if (IsLocked) { throw new NotSupportedException(); } if (Plugins.ContainsKey(key) && Plugins[key].LoadStatus == PluginContainer.Status.Loaded) { return; } var file = $"{SMPath.Plugins}{key}.dll"; if (!File.Exists(file)) { return; } try { var dll = Assembly.Load(File.ReadAllBytes(file)); var type = dll.GetType($"SevenMod.Plugin.{key}.{key}", true, true); var container = new PluginContainer(Path.GetFileName(file)); if (type.IsSubclassOf(PluginParentType)) { try { var plugin = Activator.CreateInstance(type) as PluginAbstract; plugin.Container = container; plugin.OnLoadPlugin(); if (API.IsGameAwake) { plugin.OnGameAwake(); } if (API.IsGameStartDone) { plugin.OnGameStartDone(); } if (ConVarManager.ConfigsLoaded) { ConVarManager.ExecuteConfigs(plugin); plugin.OnConfigsExecuted(); } if (!refreshing) { AdminManager.ReloadAdmins(); } container.Plugin = plugin; container.PluginInfo = plugin.Info; container.LoadStatus = PluginContainer.Status.Loaded; } catch (HaltPluginException) { throw; } catch (Exception e) { container.LoadStatus = PluginContainer.Status.Error; container.Error = e.Message; throw; } Plugins[key] = container; } else { throw new Exception($"{type.Name} does not inherit from {PluginParentType.Name}"); } } catch (Exception e) { SMLog.Error($"Failed loading plugin {key}.dll: {e.Message}"); SMLog.Error(e); } }
/// <summary> /// Logs a message to the SevenMod error logs. /// </summary> /// <param name="message">The error message to log.</param> protected void LogError(string message) { SMLog.Error(message, this.Container.File); }
/// <summary> /// Parses the database configuration file and loads the list of connection configurations. /// </summary> private static void ParseConfig() { connections.Clear(); if (!File.Exists(ConfigPath)) { CreateConfig(); } var xml = new XmlDocument(); try { xml.Load(ConfigPath); } catch (XmlException e) { SMLog.Error($"Failed reading database configuration from {ConfigPath}: {e.Message}"); return; } var defaultDriver = DatabaseDriver.SQLite; var defaultDriverElements = xml.GetElementsByTagName("DefaultDriver"); if ((defaultDriverElements.Count > 0) && "mysql".EqualsCaseInsensitive(defaultDriverElements[0].InnerText)) { defaultDriver = DatabaseDriver.MySQL; } foreach (XmlElement element in xml.GetElementsByTagName("Connection")) { if (!element.HasAttribute("Name")) { continue; } var name = element.GetAttribute("Name"); if (connections.ContainsKey(name)) { continue; } var driver = defaultDriver; var driverElements = element.GetElementsByTagName("Driver"); if (driverElements.Count > 0) { if ("sqlite".EqualsCaseInsensitive(driverElements[0].InnerText)) { driver = DatabaseDriver.SQLite; } else if ("mysql".EqualsCaseInsensitive(driverElements[0].InnerText)) { driver = DatabaseDriver.MySQL; } else { continue; } } var databaseElements = element.GetElementsByTagName("Database"); if (databaseElements.Count == 0) { continue; } var database = databaseElements[0].InnerText; if (driver == DatabaseDriver.MySQL) { var hostElements = element.GetElementsByTagName("Host"); if (hostElements.Count == 0) { continue; } var host = hostElements[0].InnerText; var userElements = element.GetElementsByTagName("User"); if (userElements.Count == 0) { continue; } var user = userElements[0].InnerText; var pass = string.Empty; var passElements = element.GetElementsByTagName("Pass"); if (passElements.Count > 0) { pass = passElements[0].InnerText; } var port = 3306u; var portElements = element.GetElementsByTagName("Port"); if (passElements.Count > 0) { uint.TryParse(portElements[0].InnerText, out port); } var connection = new ConnectionInfo(DatabaseDriver.MySQL, database, host, user, pass, port); connections.Add(name, connection); } else { var connection = new ConnectionInfo(DatabaseDriver.SQLite, database); connections.Add(name, connection); } } if (watcher == null) { watcher = new FileSystemWatcher(Path.GetDirectoryName(ConfigPath), Path.GetFileName(ConfigPath)) { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite, }; watcher.Changed += OnConfigFileChanged; watcher.Deleted += OnConfigFileChanged; watcher.Renamed += OnConfigFileChanged; watcher.EnableRaisingEvents = true; } }