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 PackageName = "LuaCsForBarotrauma";
private static LuaCsSetup _luaCsSetup;
public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup();
///
/// The index of the last Vanilla command.
///
public static int DebugConsoleCommandVanillaIndex { get; private set; }
private LuaCsSetup()
{
if (_luaCsSetup != null)
{
throw new Exception("Tried to create another LuaCsSetup instance");
}
DebugConsoleCommandVanillaIndex = DebugConsole.Commands.Count;
// == 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();
public Script Lua => LuaScriptManagementService.InternalScript;
private ISettingBase _isCsEnabledForSession;
public bool IsCsEnabledForSession
{
get => _isCsEnabledForSession?.Value ?? false;
internal set
{
_isCsEnabledForSession?.TrySetValue(value);
if (_isCsEnabledForSession != null)
{
if (_isCsEnabledForSession.GetConfigInfo() == null)
{
Logger.LogError($"Config info was nil while trying to save {IsCsEnabledForSession}");
return;
}
ConfigService.SaveConfigValue(_isCsEnabledForSession);
}
}
}
///
/// Whether C# plugin code is enabled.
///
public bool IsCsEnabled
{
#if CLIENT
get => _csRunPolicy?.Value == "Enabled" || IsCsEnabledForSession;
#elif SERVER
// cs settings cannot be changed on the server after launch
get => _csRunPolicy?.Value is "Enabled" or "Prompt";
#endif
}
private ISettingList _csRunPolicy;
public string CsRunPolicyValue => _csRunPolicy?.Value ?? "Prompt";
///
/// 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(PackageName), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageName))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageName));
}
void LoadLuaCsConfig()
{
var luaCsPackage = GetLuaCsPackage();
_csRunPolicy =
ConfigService.TryGetConfig>(luaCsPackage, "CsRunPolicy", 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;
_isCsEnabledForSession =
ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabledForSession", out var val6)
? val6
: null;
if (!ContentPackageManager.EnabledPackages.All.Contains(luaCsPackage))
{
// sorry perfidius (not sorry)
luaCsPackage.UnloadFilesOfType();
luaCsPackage.LoadFilesOfType();
}
}
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);
servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaConfigService);
// 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()
{
CoroutineManager.Invoke(() =>
{
SetRunState(RunState.Unloaded);
CoroutineManager.Invoke(() =>
{
SetRunState(RunState.Running);
},0.25f);
});
}
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)));
ConfigService.LoadSavedConfigsValues();
SetRunState(state); // restore
}
public 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(PackageName, StringComparison.InvariantCultureIgnoreCase)))
{
var luaCs = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase));
if (luaCs is null)
{
DebugConsole.ThrowError($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!",
new NullReferenceException($"The '{PackageName}' 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)
{
if (!PackageManagementService.IsAnyPackageLoaded())
{
foreach (var registrationProvider in _servicesProvider.GetAllServices())
{
registrationProvider.RegisterTypeProviders(ConfigService, null);
}
Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList()));
Logger.LogResults(ConfigService.LoadSavedConfigsValues());
LoadLuaCsConfig();
}
string csEnabled = IsCsEnabled ? "enabled" : "disabled";
Logger.LogMessage($"LuaCs running state entered. Running under commit {AssemblyInfo.GitRevision}, CSharp is {csEnabled}");
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
#if SERVER
GameMain.Server.ServerSettings.LoadClientPermissions();
#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
///
/// Checks for Cs Execution Policy (ie. prompting the user) and then calls the delegate once completed.
///
///
partial void CheckReadyToRun(Action onReadyToRun);
#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()
{
_csRunPolicy = 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
}
}