using Barotrauma.LuaCs; using Barotrauma.LuaCs.Compatibility; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using LightInject; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using AssemblyLoader = Barotrauma.LuaCs.AssemblyLoader; [assembly: InternalsVisibleTo("ImpromptuInterfaceDynamicAssembly")] [assembly: InternalsVisibleTo("Dynamitey")] namespace Barotrauma { internal delegate void LuaCsMessageLogger(string message); internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, IEventReloadAllPackages { public LuaCsSetup() { // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); _servicesProvider.GetService(); SubscribeToLuaCsEvents(); } private void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in EventService.Subscribe(this); EventService.Subscribe(this); } #region CONST_DEF #if SERVER public const bool IsServer = true; #else public const bool IsServer = false; #endif public const bool IsClient = !IsServer; #endregion #region Services_CVars /* * === Singleton Services */ private readonly IServicesProvider _servicesProvider; public PerformanceCounterService PerformanceCounter => _servicesProvider.GetService(); public ILoggerService Logger => _servicesProvider.GetService(); public IConfigService ConfigService => _servicesProvider.GetService(); public IPackageManagementService PackageManagementService => _servicesProvider.GetService(); public IPluginManagementService PluginManagementService => _servicesProvider.GetService(); public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.GetService(); public INetworkingService NetworkingService => _servicesProvider.GetService(); public IEventService EventService => _servicesProvider.GetService(); public LuaGame Game => _servicesProvider.GetService(); internal IStorageService StorageService => _servicesProvider.GetService(); /// /// Whether C# plugin code is enabled. /// public bool IsCsEnabled { get => _isCsEnabled?.Value ?? false; internal set => _isCsEnabled?.TrySetValue(value); } private ISettingBase _isCsEnabled; /// /// Whether the popup error GUI should be hidden/suppressed. /// public bool DisableErrorGUIOverlay { get => _disableErrorGUIOverlay?.Value ?? false; internal set => _disableErrorGUIOverlay?.TrySetValue(value); } private ISettingBase _disableErrorGUIOverlay; /// /// Whether usernames are anonymized or show in logs. /// public bool HideUserNamesInLogs { get => _hideUserNamesInLogs?.Value ?? false; internal set => _hideUserNamesInLogs?.TrySetValue(value); } private ISettingBase _hideUserNamesInLogs; /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// public ulong LuaForBarotraumaSteamId { get => _luaForBarotraumaSteamId?.Value ?? 0; internal set => _luaForBarotraumaSteamId?.TrySetValue(value); } private ISettingBase _luaForBarotraumaSteamId; /// /// Whether the maximum message size over the network should be restricted. /// public bool RestrictMessageSize { get => _restrictMessageSize?.Value ?? false; internal set => _restrictMessageSize?.TrySetValue(value); } private ISettingBase _restrictMessageSize; /// /// The local save path for all local data storage for mods. /// public string LocalDataSavePath { get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods"); internal set => _localDataSavePath?.TrySetValue(value); } private ISettingBase _localDataSavePath; void LoadLuaCsConfig() { var luaCsPackage = ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma"), null) ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma")) ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma")); _isCsEnabled = ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) ? val1 : null; _disableErrorGUIOverlay = ConfigService.TryGetConfig>(luaCsPackage, "DisableErrorGUIOverlay", out var val3) ? val3 : null; _hideUserNamesInLogs = ConfigService.TryGetConfig>(luaCsPackage, "HideUserNamesInLogs", out var val4) ? val4 : null; _luaForBarotraumaSteamId = ConfigService.TryGetConfig>(luaCsPackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : null; _restrictMessageSize = ConfigService.TryGetConfig>(luaCsPackage, "RestrictMessageSize", out var val7) ? val7 : null; _localDataSavePath = ConfigService.TryGetConfig>(luaCsPackage, "LocalDataSavePath", out var val8) ? val8 : null; } private IServicesProvider SetupServicesProvider() { var servicesProvider = new ServicesProvider(); // Base Service servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaCsHook); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); // All Lua Extras servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // service config data servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); #if CLIENT SetupServicesProviderClient(servicesProvider); #endif // gen IL servicesProvider.CompileAndRun(); return servicesProvider; } #endregion #region StateMachine private RunState _runState; /// /// The current run state of all services managed by LuaCs. /// public RunState CurrentRunState { get => _runState; private set => _runState = value; } private readonly StateMachine _runStateMachine; public void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages) { ProcessEnabledPackageChanges(new []{ package }.Concat(regularPackages).ToImmutableArray()); } public void OnReloadAllPackages() { if (CurrentRunState <= RunState.Unloaded) { return; } CoroutineManager.Invoke(() => { var state = CurrentRunState; SetRunState(RunState.Unloaded); SetRunState(state); }); } private void ProcessEnabledPackageChanges(ImmutableArray packages) { if (CurrentRunState < RunState.LoadedNoExec) { return; } var state = CurrentRunState; if (CurrentRunState > RunState.LoadedNoExec) { SetRunState(RunState.LoadedNoExec); } this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(packages)); SetRunState(state); // restore } private void SetRunState(RunState targetRunState) { if (CurrentRunState == targetRunState) { return; } _runStateMachine.GotoState(targetRunState); } private ImmutableArray GetEnabledPackagesList() { var enabledRegular = ContentPackageManager.EnabledPackages.Regular.ToImmutableArray(); if (!enabledRegular.Any( p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) || p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase))) { var luaCs = ContentPackageManager.AllPackages.FirstOrDefault( p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) || p.Name.Equals("Lua For Barotrauma", StringComparison.InvariantCultureIgnoreCase)); if (luaCs is null) { DebugConsole.ThrowError($"The 'LuaCsForBarotrauma' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!", new NullReferenceException($"The 'LuaCsForBarotrauma' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!"), createMessageBox: true); return enabledRegular; } enabledRegular = new[] { luaCs }.Concat(enabledRegular).ToImmutableArray(); } return enabledRegular; } private StateMachine SetupStateMachine() { return new StateMachine(false, RunState.Unloaded, onEnter: RunStateUnloaded_OnEnter, null) .AddState(RunState.LoadedNoExec, onEnter: RunStateLoadedNoExec_OnEnter, null) .AddState(RunState.Running, onEnter: RunStateRunning_OnEnter, RunStateRunning_OnExit); // ReSharper disable InconsistentNaming void RunStateUnloaded_OnEnter(State currentState) { if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } if (PackageManagementService.IsAnyPackageLoaded()) { DisposeLuaCsConfig(); Logger.LogResults(PackageManagementService.UnloadAllPackages()); } EventService.Reset(); ConfigService.Reset(); LuaScriptManagementService.Reset(); PackageManagementService.Reset(); NetworkingService.Reset(); SubscribeToLuaCsEvents(); CurrentRunState = RunState.Unloaded; } void RunStateLoadedNoExec_OnEnter(State currentState) { if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } if (!PackageManagementService.IsAnyPackageLoaded()) { foreach (var registrationProvider in _servicesProvider.GetAllServices()) { registrationProvider.RegisterTypeProviders(ConfigService, null); } Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList())); Logger.LogResults(ConfigService.LoadSavedConfigsValues()); LoadLuaCsConfig(); } CurrentRunState = RunState.LoadedNoExec; } void RunStateRunning_OnEnter(State currentState) { if (!PackageManagementService.IsAnyPackageLoaded()) { foreach (var registrationProvider in _servicesProvider.GetAllServices()) { registrationProvider.RegisterTypeProviders(ConfigService, null); } Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList())); Logger.LogResults(ConfigService.LoadSavedConfigsValues()); LoadLuaCsConfig(); } if (!PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(GetEnabledPackagesList(), IsCsEnabled)); } #if CLIENT // Technically not very accurate, but we want to call after we run mods anyway if (GameMain.Client != null) { EventService.PublishEvent(static p => p.OnServerConnected()); } #endif CurrentRunState = RunState.Running; } void RunStateRunning_OnExit(State currentState) { Logger.LogResults(PackageManagementService.StopRunningPackages()); } // ReSharper restore InconsistentNaming } #endregion #region LegacyRedirects public ILuaCsHook Hook => this.EventService; public INetworkingService Networking => this.NetworkingService; #endregion public void Dispose() { try { SetRunState(RunState.Unloaded); } catch (Exception e) { Logger.LogError(e.Message); } try { DisposeLuaCsConfig(); PluginManagementService.Dispose(); LuaScriptManagementService.Dispose(); ConfigService.Dispose(); PackageManagementService.Dispose(); // TODO: Add all missing services. //NetworkingService.Dispose(); EventService.Dispose(); _servicesProvider.DisposeAndReset(); } catch (Exception e) { Console.WriteLine(e); throw; } GC.SuppressFinalize(this); } /// /// Handles changes in game states tracked by screen changes. /// /// The new game screen. public partial void OnScreenSelected(Screen screen); void DisposeLuaCsConfig() { _isCsEnabled = null; _disableErrorGUIOverlay = null; _hideUserNamesInLogs = null; _luaForBarotraumaSteamId = null; _restrictMessageSize = null; } } /// /// Specifies the current run state of the LuaCs Modding System. /// [Important]Enum State values ordering must be in the form of (lower state) === (higher state) /// public enum RunState : byte { /// /// No assets are loaded, code execution suspended. /// Unloaded = 0, /// /// Loaded mod configs, settings and assets. No code execution. /// LoadedNoExec = 1, /// /// All assets loaded, code execution is active. /// Running = 2 } }