using Barotrauma.LuaCs; using Barotrauma.LuaCs.Compatibility; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using LightInject; using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; 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 const string PackageId = "LuaCsForBarotrauma"; private static LuaCsSetup _luaCsSetup; public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup(); private LuaCsSetup() { if (_luaCsSetup != null) { throw new Exception("Tried to create another LuaCsSetup instance"); } // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); 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; private PerformanceCounterService _performanceCounterService; public PerformanceCounterService PerformanceCounterService => _performanceCounterService ??= _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(); // hotpath performance ref cache private IEventService _eventService = null; public IEventService EventService => _eventService ??= _servicesProvider.GetService(); // hotpath performance ref cache private LuaGame _game; public LuaGame Game => _game ??= _servicesProvider.GetService(); /// /// Whether C# plugin code is enabled. /// public bool IsCsEnabled { get => _isCsEnabled?.Value ?? false; internal set => _isCsEnabled?.TrySetValue(value); } private ISettingBase _isCsEnabled; /// /// Whether usernames are anonymized or show in logs. /// public bool HideUserNamesInLogs { get => _hideUserNamesInLogs?.Value ?? false; internal set => _hideUserNamesInLogs?.TrySetValue(value); } private ISettingBase _hideUserNamesInLogs; public bool UseCaching { get => _useCaching?.Value ?? true; } private ISettingBase _useCaching; public static ContentPackage GetLuaCsPackage() { return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)) ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)); } void LoadLuaCsConfig() { var luaCsPackage = GetLuaCsPackage(); _isCsEnabled = ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) ? val1 : null; _hideUserNamesInLogs = ConfigService.TryGetConfig>(luaCsPackage, "HideUserNamesInLogs", out var val4) ? val4 : null; _useCaching = ConfigService.TryGetConfig>(luaCsPackage, "UseCaching", out var val5) ? val5 : 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); 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(GetLuaCsEnabledPackagesList(packages))); SetRunState(state); // restore } private void SetRunState(RunState targetRunState) { if (CurrentRunState == targetRunState) { return; } _runStateMachine.GotoState(targetRunState); } private ImmutableArray GetEnabledPackagesList() => GetLuaCsEnabledPackagesList(ContentPackageManager.EnabledPackages.Regular .ToImmutableArray()); private ImmutableArray GetLuaCsEnabledPackagesList(ImmutableArray enabledRegular) { 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) { Logger.LogMessage("LuaCs unloaded state entered"); 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(); Game.Reset(); _servicesProvider.GetService().Reset(); Logger.LogMessage("Services have been reset"); SubscribeToLuaCsEvents(); CurrentRunState = RunState.Unloaded; } void RunStateLoadedNoExec_OnEnter(State currentState) { Logger.LogMessage("LuaCs no execution state entered"); 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) { string csEnabled = IsCsEnabled ? "enabled" : "disabled"; Logger.LogMessage($"LuaCs running state entered. Running under commit {AssemblyInfo.GitRevision}, CSharp is {csEnabled}"); 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) { EventService.Call("stop"); Logger.LogResults(PackageManagementService.StopRunningPackages()); Logger.LogMessage("LuaCs running state exited"); } // ReSharper restore InconsistentNaming } #endregion #region LegacyRedirects // --- Compatibility /// /// [Obsolete] Legacy support only. /// [Obsolete] public LuaCsPerformanceCounter PerformanceCounter { get; private set; } = new LuaCsPerformanceCounter(); /// /// [Obsolete] Use instead. /// [Obsolete($"Use {nameof(PluginManagementService)} instead.")] public IPluginManagementService PluginPackageManager => this.PluginManagementService; public ILuaCsHook Hook => this.EventService; public INetworkingService Networking => this.NetworkingService; public ILuaCsTimer Timer => _servicesProvider.GetService(); public DynValue CallLuaFunction(object function, params object[] args) => LuaScriptManagementService.CallFunctionSafe(function, args); #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(); _eventService = null; _game = null; PerformanceCounter = null; _servicesProvider.DisposeAndReset(); } catch (Exception e) { Console.WriteLine(e); throw; } _luaCsSetup = null; 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; _hideUserNamesInLogs = 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 } }