diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c422163 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +Build +*.sln +.vs +obj/ +bin/ \ No newline at end of file diff --git a/README.md b/README.md index 8c7be72..d2d11f0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # Vina-Framework -Vina Framework is developed to help creating high quality C# FiveM resources. The framework handle & abstract basic stuff that most resource need. +Vina Framework is developed to help creating high quality C# FiveM resources. +The framework handle & abstract basic stuff that most resource need. diff --git a/VinaFrameworkClient/Core/BaseClient.cs b/VinaFrameworkClient/Core/BaseClient.cs new file mode 100644 index 0000000..fee1df0 --- /dev/null +++ b/VinaFrameworkClient/Core/BaseClient.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using CitizenFX.Core; +using CitizenFX.Core.Native; + +namespace VinaFrameworkClient.Core +{ + /// + /// Extend your Client script with this class and add modules to your constructor + /// + public abstract class BaseClient : BaseScript + { + /// + /// This current client resource name + /// + public static string Name { get; private set; } + + /// + /// This current client resource name + /// + public string ResourceName { get { return Name; } } + + private List modules; + + /// + /// Extend your Client script with this class and add modules to your constructor + /// + public BaseClient() + { + Name = API.GetCurrentResourceName(); + modules = new List(); + + Debug.WriteLine("============================================================"); + Log($"Initializing..."); + + Tick += initialize; + } + + #region Base Events + + private async Task initialize() + { + Tick -= initialize; + + await Delay(0); + + Log($"Initialized!"); + TriggerServerEvent($"internal:{Name}:onPlayerClientInitialized"); + } + + #endregion + + #region Methods + + /// + /// Verify if the module has been loaded. + /// + /// The module class type. Ex: typeof(MyModule) + /// + public bool HasModule(Type moduleType) + { + foreach (Module module in modules) + { + if (module.GetType() == moduleType) + { + return true; + } + } + + return false; + } + + /// + /// Add module by referencing the type of your module class. + /// + /// The module class type. Ex: typeof(MyModule) + public void AddModule(Type moduleType) + { + bool error = false; + foreach (Module _module in modules) + { + if (_module.GetType() == moduleType) + { + error = true; + LogError(new Exception($"Trying to add existing module {moduleType.Name}!")); + } + } + + if (error) return; + + try + { + if (!moduleType.IsSubclassOf(typeof(Module))) + throw new Exception($"Trying to add class {moduleType.Name} that is not a subclass of Module!"); + + Module module = (Module)Activator.CreateInstance(moduleType, this); + modules.Add(module); + } + catch (Exception exception) + { + LogError(exception, $" in {moduleType.Name} > Constructor"); + } + } + + /// + /// Get a module instance from anywhere, cannot be called inside a module constructor. + /// + /// The module class type. + /// Return the module or throw an exception if the module is not found. + public T GetModule() + { + foreach (Module module in modules) + { + if (module.GetType() == typeof(T)) + { + return (T)Convert.ChangeType(module, typeof(T)); + } + } + + throw new Exception($"Module {typeof(T)} doesn't exist!"); + } + + /// + /// Register an event. + /// + /// The event name to register. + /// The event delegate to register. + public void RegisterEvent(string eventName, Delegate action) + { + EventHandlers[eventName] += action; + Log($"Event {eventName} registered!"); + } + + /// + /// Un-register an event. + /// + /// The event name to un-register. + /// The event delegate to un-register. + public void UnregisterEvent(string eventName, Delegate action) + { + EventHandlers[eventName] -= action; + Log($"Event {eventName} deregistered!"); + } + + /// + /// Add a tick to the resource. + /// + /// The tick delegate to add. + public void AddTick(Func action) + { + Tick += action; + Log($"{action.Method.DeclaringType.Name} added Tick {action.Method.Name}!"); + } + + /// + /// Remove a tick from the resource. + /// + /// The tick delegate to remove. + public void RemoveTick(Func action) + { + Tick -= action; + Log($"{action.Method.DeclaringType.Name} removed Tick {action.Method.Name}!"); + } + + /// + /// Un-register an event. + /// + /// The event name to un-register. + /// The event delegate to un-register. + internal void AddInternalTick(Func action) + { + Tick += action; + } + + /// + /// Remove a tick from the resource. + /// + /// The tick delegate to remove. + internal void RemoveInternalTick(Func action) + { + Tick -= action; + } + + /// + /// Get the ExportDictionary. + /// + /// Return the ExportDictionary. + public ExportDictionary GetExports() + { + return Exports; + } + + /// + /// Get a specific Export from a resource. + /// + /// + /// Return the dynamic export. + public dynamic GetExport(string resourceName) + { + return Exports[resourceName]; + } + + /// + /// Set an Export from this resource. + /// + /// The name of the Exported method. + /// The delegate of the Exported method. + public void SetExport(string name, Delegate method) + { + Exports.Add(name, method); + } + + /// + /// Log a message. + /// + /// The message to log. + public void Log(string message) + { + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [INFO] {ResourceName.ToUpper()}: {message}"); + } + + /// + /// Log an exception. + /// + /// The Exception to log. + /// Some text to add before the log message. + public void LogError(Exception exception, string prefix = "") + { + string pre = (prefix != "") ? $" {prefix}" : ""; + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [ERROR] {ResourceName.ToUpper()}{pre}: {exception.Message}\n{exception.StackTrace}"); + } + + #endregion + } +} diff --git a/VinaFrameworkClient/Core/Module.cs b/VinaFrameworkClient/Core/Module.cs new file mode 100644 index 0000000..a2163a6 --- /dev/null +++ b/VinaFrameworkClient/Core/Module.cs @@ -0,0 +1,90 @@ +using System; +using System.Threading.Tasks; + +using CitizenFX.Core; + +namespace VinaFrameworkClient.Core +{ + /// + /// Extend your module class with this class and add it to your client constructor. + /// + public abstract class Module + { + /// + /// This current module name. + /// + protected string Name { get; } + + /// + /// Read-only reference to the client instance. + /// + protected BaseClient client { get; } + + /// + /// Extend your module class with this class and add it to your client constructor. + /// + /// + public Module(BaseClient client) + { + Name = this.GetType().Name; + this.client = client; + client.AddInternalTick(initialize); + Log($"Instance created!"); + } + + private async Task initialize() + { + Log($"Initializing..."); + + client.RemoveInternalTick(initialize); + + await BaseClient.Delay(0); + + try + { + OnModuleInitialized(); + } + catch (Exception exception) + { + LogError(exception, " in OnModuleInitialized"); + } + } + + /// + /// Overridable method that run on first tick only. You can get other module from here. + /// + protected virtual void OnModuleInitialized() + { + Log($"Initialized!"); + } + + /// + /// + /// + /// + public static Task Delay(int msecs) + { + return BaseClient.Delay(msecs); + } + + /// + /// Log a message from this module. + /// + /// The message to log. + protected void Log(string message) + { + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [INFO] {client.ResourceName.ToUpper()} > {Name.ToUpper()}: {message}"); + } + + /// + /// Log an exception from this module. + /// + /// The Exception to log. + /// Some text to add before the log message. + protected void LogError(Exception exception, string prefix = "") + { + string pre = (prefix != "") ? $" {prefix}" : ""; + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [ERROR] {client.ResourceName.ToUpper()} > {Name.ToUpper()}{pre}: {exception.Message}\n{exception.StackTrace}"); + } + } +} diff --git a/VinaFrameworkClient/Properties/AssemblyInfo.cs b/VinaFrameworkClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..48080bb --- /dev/null +++ b/VinaFrameworkClient/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("VinaFramework")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("VinaFrameworkClient")] +[assembly: AssemblyCopyright("Copyright © VinaStar 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6b2efafa-b162-4b97-b837-6d8a8e5162cd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/VinaFrameworkClient/VinaFrameworkClient.csproj b/VinaFrameworkClient/VinaFrameworkClient.csproj new file mode 100644 index 0000000..46e39eb --- /dev/null +++ b/VinaFrameworkClient/VinaFrameworkClient.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {6B2EFAFA-B162-4B97-B837-6D8A8E5162CD} + Library + Properties + VinaFrameworkClient + VinaFrameworkClient + v4.5.2 + 512 + true + + + false + none + false + ..\Build\ + DEBUG;TRACE + prompt + 4 + Off + ..\Build\VinaFrameworkClient.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\..\FiveM.app\citizen\clr2\lib\mono\4.5\CitizenFX.Core.dll + False + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VinaFrameworkServer/Core/BaseServer.cs b/VinaFrameworkServer/Core/BaseServer.cs new file mode 100644 index 0000000..80cbbec --- /dev/null +++ b/VinaFrameworkServer/Core/BaseServer.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using CitizenFX.Core; +using CitizenFX.Core.Native; + +namespace VinaFrameworkServer.Core +{ + /// + /// Extend your Server script with this class and add modules to your constructor + /// + public abstract class BaseServer : BaseScript + { + /// + /// This current client resource name + /// + public static string Name { get; private set; } + + /// + /// This current client resource name + /// + public string ResourceName { get { return Name; } } + + private List modules; + + /// + /// Extend your Server script with this class and add modules to your constructor + /// + public BaseServer() + { + Name = API.GetCurrentResourceName(); + modules = new List(); + EventHandlers["playerConnecting"] += new Action(onPlayerConnecting); + EventHandlers["playerDropped"] += new Action(onPlayerDropped); + EventHandlers[$"internal:{Name}:onPlayerClientInitialized"] += new Action(onPlayerClientInitialized); + + Debug.WriteLine("============================================================"); + Log($"Instanciating..."); + } + + #region Base Events + + private void onPlayerConnecting([FromSource] Player player) + { + Log($"PlayerConnecting {player.Name}"); + + foreach (Module module in modules) + { + try + { + module.onPlayerConnecting(player); + } + catch (Exception exception) + { + LogError(exception, $" in {module.GetType().Name} > OnPlayerConnecting"); + } + } + } + private void onPlayerDropped([FromSource] Player player, string reason) + { + Log($"PlayerDropped {player.Name}"); + + foreach (Module module in modules) + { + try + { + module.onPlayerDropped(player, reason); + } + catch (Exception exception) + { + LogError(exception, $" in {module.GetType().Name} > OnPlayerDropped"); + } + } + } + private void onPlayerClientInitialized([FromSource] Player player) + { + Log($"PlayerClientInitialized {player.Name}"); + + foreach (Module module in modules) + { + try + { + module.onPlayerClientInitialized(player); + } + catch (Exception exception) + { + LogError(exception, $" in {module.GetType().Name} > OnPlayerClientInitialized"); + } + } + } + + #endregion + + #region Methods + + /// + /// Verify if the module has been loaded. + /// + /// The module class type. Ex: typeof(MyModule) + /// + public bool HasModule(Type moduleType) + { + foreach (Module module in modules) + { + if (module.GetType() == moduleType) + { + return true; + } + } + + return false; + } + + /// + /// Add module by referencing the type of your module class. + /// + /// The module class type. Ex: typeof(MyModule) + public void AddModule(Type moduleType) + { + bool error = false; + foreach(Module _module in modules) + { + if (_module.GetType() == moduleType) + { + error = true; + LogError(new Exception($"Trying to add existing module {moduleType.Name}!")); + } + } + + if (error) return; + + try + { + if (!moduleType.IsSubclassOf(typeof(Module))) + throw new Exception($"Trying to add class {moduleType.Name} that is not a subclass of Module!"); + + Module module = (Module)Activator.CreateInstance(moduleType, this); + modules.Add(module); + } + catch (Exception exception) + { + LogError(exception, $" in {moduleType.Name} > Constructor"); + } + } + + /// + /// Get a module instance from anywhere, cannot be called inside a module constructor. + /// + /// The module class type. + /// Return the module or throw an exception if the module is not found. + public T GetModule() + { + foreach (Module module in modules) + { + if (module.GetType() == typeof(T)) + { + return (T)Convert.ChangeType(module, typeof(T)); + } + } + + throw new Exception($"Module {typeof(T)} doesn't exist!"); + } + + /// + /// Register an event. + /// + /// The event name to register. + /// The event delegate to register. + public void RegisterEvent(string eventName, Delegate action) + { + EventHandlers[eventName] += action; + Log($"Event {eventName} registered!"); + } + + /// + /// Un-register an event. + /// + /// The event name to un-register. + /// The event delegate to un-register. + public void UnregisterEvent(string eventName, Delegate action) + { + EventHandlers[eventName] -= action; + Log($"Event {eventName} deregistered!"); + } + + /// + /// Add a tick to the resource. + /// + /// The tick delegate to add. + public void AddTick(Func action) + { + Tick += action; + Log($"{action.Method.DeclaringType.Name} added Tick {action.Method.Name}!"); + } + + /// + /// Remove a tick from the resource. + /// + /// The tick delegate to remove. + public void RemoveTick(Func action) + { + Tick -= action; + Log($"{action.Method.DeclaringType.Name} removed Tick {action.Method.Name}!"); + } + + /// + /// Un-register an event. + /// + /// The event name to un-register. + /// The event delegate to un-register. + internal void AddInternalTick(Func action) + { + Tick += action; + } + + /// + /// Remove a tick from the resource. + /// + /// The tick delegate to remove. + internal void RemoveInternalTick(Func action) + { + Tick -= action; + } + + /// + /// Get the ExportDictionary. + /// + /// Return the ExportDictionary. + public ExportDictionary GetExports() + { + return Exports; + } + + /// + /// Get a specific Export from a resource. + /// + /// + /// Return the dynamic export. + public dynamic GetExport(string resourceName) + { + return Exports[resourceName]; + } + + /// + /// Set an Export from this resource. + /// + /// The name of the Exported method. + /// The delegate of the Exported method. + public void SetExport(string name, Delegate method) + { + Exports.Add(name, method); + } + + /// + /// Log a message. + /// + /// The message to log. + public void Log(string message) + { + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [INFO] {ResourceName.ToUpper()}: {message}"); + } + + /// + /// Log an exception. + /// + /// The Exception to log. + /// Some text to add before the log message. + public void LogError(Exception exception, string prefix = "") + { + string pre = (prefix != "") ? $" {prefix}" : ""; + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [ERROR] {ResourceName.ToUpper()}{pre}: {exception.Message}\n{exception.StackTrace}"); + } + + #endregion + } +} diff --git a/VinaFrameworkServer/Core/Module.cs b/VinaFrameworkServer/Core/Module.cs new file mode 100644 index 0000000..07846b2 --- /dev/null +++ b/VinaFrameworkServer/Core/Module.cs @@ -0,0 +1,124 @@ +using System; +using System.Threading.Tasks; + +using CitizenFX.Core; + +namespace VinaFrameworkServer.Core +{ + /// + /// + /// + public abstract class Module + { + /// + /// This current module name. + /// + protected string Name { get; } + + /// + /// Read-only reference to the server instance. + /// + protected BaseServer server { get; } + + /// + /// Extend your module class with this class and add it to your server constructor. + /// + /// + public Module(BaseServer server) + { + Name = this.GetType().Name; + this.server = server; + server.AddInternalTick(initialize); + Log($"Instance created!"); + } + + private async Task initialize() + { + Log($"Initializing..."); + + server.RemoveInternalTick(initialize); + + await BaseServer.Delay(0); + + try + { + OnModuleInitialized(); + } + catch (Exception exception) + { + LogError(exception, " in OnModuleInitialized"); + } + } + + /// + /// Overridable method that run on first tick only. You can get other module from here. + /// + protected virtual void OnModuleInitialized() + { + Log($"Initialized!"); + } + + internal void onPlayerConnecting(Player player) + { + OnPlayerConnecting(player); + } + + /// + /// Overridable method that run when a player is connecting to the server. + /// + /// The player that is connecting. + protected abstract void OnPlayerConnecting(Player player); + + internal void onPlayerDropped(Player player, string reason) + { + OnPlayerDropped(player, reason); + } + + /// + /// Overridable method that run when a player has left the server. + /// + /// The player that left. + /// The reason that player left. + protected abstract void OnPlayerDropped(Player player, string reason); + + internal void onPlayerClientInitialized(Player player) + { + OnPlayerClientInitialized(player); + } + + /// + /// + /// + /// + protected abstract void OnPlayerClientInitialized(Player player); + + /// + /// + /// + /// + public static Task Delay(int msecs) + { + return BaseServer.Delay(msecs); + } + + /// + /// Log a message from this module. + /// + /// The message to log. + protected void Log(string message) + { + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [INFO] {server.ResourceName.ToUpper()} > {Name.ToUpper()}: {message}"); + } + + /// + /// Log an exception from this module. + /// + /// The Exception to log. + /// Some text to add before the log message. + protected void LogError(Exception exception, string prefix = "") + { + string pre = (prefix != "") ? $" {prefix}" : ""; + Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} [ERROR] {server.ResourceName.ToUpper()} > {Name.ToUpper()}{pre}: {exception.Message}\n{exception.StackTrace}"); + } + } +} diff --git a/VinaFrameworkServer/Properties/AssemblyInfo.cs b/VinaFrameworkServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..82ce568 --- /dev/null +++ b/VinaFrameworkServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("VinaFramework")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("VinaFrameworkServer")] +[assembly: AssemblyCopyright("Copyright © VinaStar 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("326213ad-a8f1-4590-aa24-16e1b54f63b8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/VinaFrameworkServer/VinaFrameworkServer.csproj b/VinaFrameworkServer/VinaFrameworkServer.csproj new file mode 100644 index 0000000..66855b7 --- /dev/null +++ b/VinaFrameworkServer/VinaFrameworkServer.csproj @@ -0,0 +1,56 @@ + + + + + Debug + AnyCPU + {326213AD-A8F1-4590-AA24-16E1B54F63B8} + Library + Properties + VinaFrameworkServer + VinaFrameworkServer + v4.5.2 + 512 + true + + + false + none + false + ..\Build\ + DEBUG;TRACE + prompt + 4 + Off + ..\Build\VinaFrameworkServer.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\fivem_server\citizen\clr2\lib\mono\4.5\CitizenFX.Core.dll + False + + + + + + + + + + + + + + + + + + \ No newline at end of file