reworked app startup
This commit is contained in:
		@@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml;
 | 
			
		||||
using StarsAssistant.ViewModels;
 | 
			
		||||
using StarsAssistant.Views;
 | 
			
		||||
using Splat;
 | 
			
		||||
using StarsAssistant.Services;
 | 
			
		||||
 | 
			
		||||
namespace StarsAssistant;
 | 
			
		||||
 | 
			
		||||
@@ -18,15 +19,21 @@ public partial class App : Application
 | 
			
		||||
    {
 | 
			
		||||
        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
 | 
			
		||||
        {
 | 
			
		||||
            Game game = new(desktop.Args);
 | 
			
		||||
            game.RegisterServicesForGame();
 | 
			
		||||
 | 
			
		||||
            desktop.MainWindow = new MainWindow
 | 
			
		||||
            {
 | 
			
		||||
                DataContext = new MainWindowViewModel(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            base.OnFrameworkInitializationCompleted();
 | 
			
		||||
 | 
			
		||||
            game.StartBackgroundServices();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("This app supports only Desktop mode.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var csvloader = Locator.Current.GetService<Services.CSVDataLoader>()!;
 | 
			
		||||
        csvloader.StartPlanetCSVWatcher();
 | 
			
		||||
 | 
			
		||||
        base.OnFrameworkInitializationCompleted();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ public class Planet
 | 
			
		||||
    public string? Owner { get; set; }
 | 
			
		||||
    
 | 
			
		||||
    [Index(2)]
 | 
			
		||||
    public string? StarbaseType { get; set; }
 | 
			
		||||
    public string StarbaseType { get; set; } = String.Empty;
 | 
			
		||||
    
 | 
			
		||||
    [Index(3)]
 | 
			
		||||
    public int ReportAge { get; set; } = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ public class Planet
 | 
			
		||||
 | 
			
		||||
    public string? OwnerId { get; set; }
 | 
			
		||||
    
 | 
			
		||||
    public string? StarbaseType { get; set; }
 | 
			
		||||
    public string StarbaseType { get; set; } = String.Empty;
 | 
			
		||||
    
 | 
			
		||||
    public int ReportAge { get; set; } = 0;
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -25,37 +25,6 @@ sealed class Program
 | 
			
		||||
        var logger = new ConsoleLogger() { Level = LogLevel.Debug };
 | 
			
		||||
        Locator.CurrentMutable.RegisterConstant(logger, typeof(ILogger));
 | 
			
		||||
 | 
			
		||||
        bool newGame = false;
 | 
			
		||||
        string dbPath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/GOINGTH.sqlite";
 | 
			
		||||
        Services.Game gameSvc;
 | 
			
		||||
 | 
			
		||||
        using (StarsDatabase starsDB = new(dbPath))
 | 
			
		||||
        {
 | 
			
		||||
            if (Path.Exists(dbPath))
 | 
			
		||||
            {
 | 
			
		||||
                Model.Game dbGame = starsDB.Game.First();
 | 
			
		||||
                gameSvc = new Services.Game(dbGame);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                starsDB.Database.EnsureCreated();
 | 
			
		||||
                gameSvc = new()
 | 
			
		||||
                {
 | 
			
		||||
                    BaseName = "GOINGTH",
 | 
			
		||||
                    GamePath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/"
 | 
			
		||||
                };
 | 
			
		||||
                gameSvc.SaveToDatabase(starsDB);
 | 
			
		||||
                newGame = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Locator.CurrentMutable.RegisterConstant(gameSvc, typeof(Services.Game));
 | 
			
		||||
        Locator.CurrentMutable.RegisterConstant(new Services.CSVDataLoader(), typeof(Services.CSVDataLoader));
 | 
			
		||||
        Locator.CurrentMutable.Register(() => new StarsDatabase(gameSvc.DatabaseFileName), typeof(StarsDatabase));
 | 
			
		||||
            
 | 
			
		||||
        if (newGame)
 | 
			
		||||
            __createTestData();
 | 
			
		||||
 | 
			
		||||
        BuildAvaloniaApp()
 | 
			
		||||
            .StartWithClassicDesktopLifetime(args);
 | 
			
		||||
    }
 | 
			
		||||
@@ -68,93 +37,4 @@ sealed class Program
 | 
			
		||||
            .LogToTrace()
 | 
			
		||||
            .UseReactiveUI();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static void __createTestData ()
 | 
			
		||||
    {
 | 
			
		||||
        using var db = Locator.Current.GetService<StarsDatabase>()!;
 | 
			
		||||
 | 
			
		||||
        // Note: This sample requires the database to be created before running.
 | 
			
		||||
        // Console.WriteLine($"Database path: {db.DbPath}.");
 | 
			
		||||
 | 
			
		||||
        Race r = new()
 | 
			
		||||
        {
 | 
			
		||||
            Name = "Atlantis",
 | 
			
		||||
            PlayerRace = true,
 | 
			
		||||
            PlayerFileId = 1,
 | 
			
		||||
            ColonistsPerResource = 1000,
 | 
			
		||||
            GrowthRatePercent = 19,
 | 
			
		||||
            PRT = PRT.Other,
 | 
			
		||||
            HasOBRM = true,
 | 
			
		||||
            FactoryCost3 = false,
 | 
			
		||||
            FactoryNumberPer10k = 8,
 | 
			
		||||
            FactoryResCost = 8,
 | 
			
		||||
            FactoryResPer10 = 15,
 | 
			
		||||
            MineResCost = 3,
 | 
			
		||||
            MineMineralsPer10 = 10,
 | 
			
		||||
            MineNumberPer10k = 10
 | 
			
		||||
        };
 | 
			
		||||
        db.Add(r);
 | 
			
		||||
        db.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        var loader = new CSV.PlanetLoader();
 | 
			
		||||
        loader.ImportForRace(Services.Game.Player);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using CsvHelper.Configuration;
 | 
			
		||||
using CsvHelper;
 | 
			
		||||
using StarsAssistant.model;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
using var db = new StarsDatabase("stars.sqlite");
 | 
			
		||||
 | 
			
		||||
db.Database.EnsureDeleted();
 | 
			
		||||
db.Database.EnsureCreated();
 | 
			
		||||
 | 
			
		||||
// Note: This sample requires the database to be created before running.
 | 
			
		||||
Console.WriteLine($"Database path: {db.DbPath}.");
 | 
			
		||||
 | 
			
		||||
var config = CsvConfiguration.FromAttributes<Planet>(CultureInfo.InvariantCulture);
 | 
			
		||||
config.Delimiter = "\t";
 | 
			
		||||
config.Escape = '\0';
 | 
			
		||||
config.MissingFieldFound = null;
 | 
			
		||||
 | 
			
		||||
using (var reader = new StreamReader("/home/torben/goingth/GOINGTH.p1", Encoding.Latin1))
 | 
			
		||||
using (var csv = new CsvReader(reader, config))
 | 
			
		||||
{
 | 
			
		||||
    List<Planet> records = csv.GetRecords<Planet>().ToList();
 | 
			
		||||
    foreach (Planet p in records)
 | 
			
		||||
    {
 | 
			
		||||
        db.Add(p);
 | 
			
		||||
    }
 | 
			
		||||
    db.SaveChanges();
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
// Create
 | 
			
		||||
Console.WriteLine("Inserting a new blog");
 | 
			
		||||
db.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
 | 
			
		||||
db.SaveChanges();
 | 
			
		||||
 | 
			
		||||
// Read
 | 
			
		||||
Console.WriteLine("Querying for a blog");
 | 
			
		||||
var blog = db.Blogs
 | 
			
		||||
    .OrderBy(b => b.BlogId)
 | 
			
		||||
    .First();
 | 
			
		||||
 | 
			
		||||
// Update
 | 
			
		||||
Console.WriteLine("Updating the blog and adding a post");
 | 
			
		||||
blog.Url = "https://devblogs.microsoft.com/dotnet";
 | 
			
		||||
blog.Posts.Add(
 | 
			
		||||
    new Post { Title = "Hello World", Content = "I wrote an app using EF Core!" });
 | 
			
		||||
db.SaveChanges();
 | 
			
		||||
 | 
			
		||||
// Delete
 | 
			
		||||
// Console.WriteLine("Delete the blog");
 | 
			
		||||
// db.Remove(blog);
 | 
			
		||||
// db.SaveChanges();
 | 
			
		||||
*/
 | 
			
		||||
@@ -16,30 +16,58 @@ public partial class CSVDataLoader : IEnableLogger
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected Services.Game Game = Locator.Current.GetService<Services.Game>()!;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Regex to match fs watcher results to Stars file types.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>Precompiled RegEx</returns>
 | 
			
		||||
    [GeneratedRegex(@".*\.(?<type>[pf])(?<player>\d)+$")]
 | 
			
		||||
    private static partial Regex MyRegex();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// RegEx Instance to match Stars file types.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected Regex FileTypeRegEx = MyRegex();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Instance of the FSWatcher, to which we subscribe.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected IObservable<FileSystemEventArgs> Watcher;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Active subscription to FsWatcher.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected IDisposable? Subscription;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Construct the instance and prepare our FS Watcher.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public CSVDataLoader() 
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void StartPlanetCSVWatcher() 
 | 
			
		||||
    {
 | 
			
		||||
        // string[] filters = { "*.p*", "*.f*", "*.map" };
 | 
			
		||||
 | 
			
		||||
        var watcher = FsWatcher
 | 
			
		||||
        Watcher = FsWatcher
 | 
			
		||||
            .ObserveFileSystem(Game.GamePath, [ Game.PlanetFileSearchPattern ])
 | 
			
		||||
            .ThrottleAndDistinct(2, RxApp.TaskpoolScheduler)
 | 
			
		||||
            .Log(this, $"{DateTime.Now.ToLongTimeString()} FsEvent", fsEvent => $"{fsEvent.FullPath} {fsEvent.ChangeType}")
 | 
			
		||||
            .ObserveOn(RxApp.TaskpoolScheduler);
 | 
			
		||||
        
 | 
			
		||||
        watcher.Subscribe(fsEvent => this.LoadPlanetFile(fsEvent.FullPath));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void LoadPlanetFile(string fileName)
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Start the CSV watcher, and capture our disposable, so that we can shut 
 | 
			
		||||
    /// down the watcher if needed.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void StartCSVWatcher() 
 | 
			
		||||
    {
 | 
			
		||||
        if (Subscription != null)
 | 
			
		||||
            throw new InvalidOperationException("CSV Watcher is active, can't start it again.");
 | 
			
		||||
 | 
			
		||||
        Subscription = Watcher.Subscribe(fsEvent => this.LoadFile(fsEvent.FullPath));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Subscription to File Processing, called by our subscription to the watcher.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="fileName">The File to process</param>
 | 
			
		||||
    protected void LoadFile(string fileName)
 | 
			
		||||
    {
 | 
			
		||||
        Match m = FileTypeRegEx.Match(fileName);
 | 
			
		||||
        if (! m.Success)
 | 
			
		||||
@@ -50,9 +78,18 @@ public partial class CSVDataLoader : IEnableLogger
 | 
			
		||||
 | 
			
		||||
        string type = m.Groups["type"].Value;
 | 
			
		||||
        string player = m.Groups["player"].Value;
 | 
			
		||||
 | 
			
		||||
        this.Log().Debug($"Got file type {type} for player {player}");
 | 
			
		||||
        var loader = new CSV.PlanetLoader();
 | 
			
		||||
        loader.ImportForRace(Services.Game.Player);
 | 
			
		||||
 | 
			
		||||
        switch (type)
 | 
			
		||||
        {
 | 
			
		||||
            case "p":
 | 
			
		||||
                var loader = new CSV.PlanetLoader();
 | 
			
		||||
                loader.ImportForRace(Services.Game.Player);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                this.Log().Warn($"Planet loader got unknown file type ${type}. Ignoring file.");
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}   
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
using System.Reflection.PortableExecutable;
 | 
			
		||||
using Splat;
 | 
			
		||||
using StarsAssistant.Model;
 | 
			
		||||
 | 
			
		||||
namespace StarsAssistant.Services;
 | 
			
		||||
 | 
			
		||||
@@ -22,28 +23,28 @@ public class Game
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Save this record in the database. Uses the service locator to access the 
 | 
			
		||||
    /// database unless you specify an instance. This is needed during initial
 | 
			
		||||
    /// game creation, where the services are not yet established.
 | 
			
		||||
    /// Helper to construct the game from a command line.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="db">Optional DB instance if services are yet unavailable.</param>
 | 
			
		||||
    public void SaveToDatabase(Model.StarsDatabase? db = null) 
 | 
			
		||||
    /// <param name="args">The command line to parse.</param>
 | 
			
		||||
    public Game(string[]? args)
 | 
			
		||||
    {
 | 
			
		||||
        db ??= Locator.Current.GetService<Model.StarsDatabase>()!;
 | 
			
		||||
        
 | 
			
		||||
        Model.Game? dbGame = db.Game.FirstOrDefault();
 | 
			
		||||
        if (dbGame == null)
 | 
			
		||||
        {
 | 
			
		||||
            dbGame = new Model.Game();
 | 
			
		||||
            db.Add(dbGame);
 | 
			
		||||
            db.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        dbGame.GamePath = GamePath;
 | 
			
		||||
        dbGame.BaseName = BaseName; 
 | 
			
		||||
        db.Update(dbGame);
 | 
			
		||||
        db.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
        GamePath = "/home/torben/Nextcloud/Documents/Stars!/Games/goingth/";
 | 
			
		||||
        BaseName = "GOINGTH";
 | 
			
		||||
        string dbPath = DatabaseFileName;
 | 
			
		||||
 | 
			
		||||
        using StarsDatabase starsDB = new(dbPath);
 | 
			
		||||
        if (Path.Exists(dbPath))
 | 
			
		||||
        {
 | 
			
		||||
            Model.Game dbGame = starsDB.Game.First();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            starsDB.Database.EnsureCreated();
 | 
			
		||||
            SaveToDatabase(starsDB);
 | 
			
		||||
            __doCreateTestData = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// The base path in which all game files reside.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -68,9 +69,9 @@ public class Game
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Internal helper to lazily load the current player from the DB.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private readonly static Lazy<Model.Race>LazyPlayer = new ( () => {
 | 
			
		||||
        using var db = Locator.Current.GetService<Model.StarsDatabase>()!;
 | 
			
		||||
        Model.Race result = db.Race
 | 
			
		||||
    private readonly static Lazy<Race>LazyPlayer = new ( () => {
 | 
			
		||||
        using var db = Locator.Current.GetService<StarsDatabase>()!;
 | 
			
		||||
        Race result = db.Race
 | 
			
		||||
            .Where(r => r.PlayerRace == true)
 | 
			
		||||
            .First();
 | 
			
		||||
        return result;
 | 
			
		||||
@@ -79,13 +80,99 @@ public class Game
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Get the Race object for the current player, lazy initialized.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static Model.Race Player => LazyPlayer.Value;
 | 
			
		||||
    public static Race Player => LazyPlayer.Value;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Get the name of a planet file for a given race.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="r">The race to load.</param>
 | 
			
		||||
    /// <returns>Fully qualified file path.</returns>
 | 
			
		||||
    public string PlanetFileForRace (Model.Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}");
 | 
			
		||||
    public string PlanetFileForRace (Race r) => Path.Combine(GamePath, $"{BaseName}.p{r.PlayerFileId}");
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Save this record in the database. Uses the service locator to access the 
 | 
			
		||||
    /// database unless you specify an instance. This is needed during initial
 | 
			
		||||
    /// game creation, where the services are not yet established.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="db">Optional DB instance if services are yet unavailable.</param>
 | 
			
		||||
    public void SaveToDatabase(StarsDatabase? db = null) 
 | 
			
		||||
    {
 | 
			
		||||
        db ??= Locator.Current.GetService<StarsDatabase>()!;
 | 
			
		||||
        
 | 
			
		||||
        Model.Game? dbGame = db.Game.FirstOrDefault();
 | 
			
		||||
        if (dbGame == null)
 | 
			
		||||
        {
 | 
			
		||||
            dbGame = new Model.Game();
 | 
			
		||||
            db.Add(dbGame);
 | 
			
		||||
            db.SaveChanges();
 | 
			
		||||
        }
 | 
			
		||||
        dbGame.GamePath = GamePath;
 | 
			
		||||
        dbGame.BaseName = BaseName; 
 | 
			
		||||
        db.Update(dbGame);
 | 
			
		||||
        db.SaveChanges();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Registers all services depending on this Game, in the order required.
 | 
			
		||||
    /// This is called once the framework is initialized but before the main 
 | 
			
		||||
    /// window is active and has its models.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void RegisterServicesForGame()
 | 
			
		||||
    {
 | 
			
		||||
        Locator.CurrentMutable.RegisterConstant(this, typeof(Services.Game));
 | 
			
		||||
        Locator.CurrentMutable.RegisterConstant(new CSVDataLoader(), typeof(CSVDataLoader));
 | 
			
		||||
        Locator.CurrentMutable.Register(() => new StarsDatabase(DatabaseFileName), typeof(StarsDatabase));
 | 
			
		||||
 | 
			
		||||
        // TESTING HELPER
 | 
			
		||||
        if (__doCreateTestData)
 | 
			
		||||
            __createTestData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Starts all required background servies, so that we are in normal UI
 | 
			
		||||
    /// operation mode. This is called after the UI is up and running.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void StartBackgroundServices()
 | 
			
		||||
    {
 | 
			
		||||
        CSVDataLoader csvloader = Locator.Current.GetService<Services.CSVDataLoader>()!;
 | 
			
		||||
        csvloader.StartCSVWatcher();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private bool __doCreateTestData = false;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// TESTING HELPER
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private void __createTestData ()
 | 
			
		||||
    {
 | 
			
		||||
        using var db = Locator.Current.GetService<StarsDatabase>()!;
 | 
			
		||||
 | 
			
		||||
        // Note: This sample requires the database to be created before running.
 | 
			
		||||
        // Console.WriteLine($"Database path: {db.DbPath}.");
 | 
			
		||||
 | 
			
		||||
        Race r = new()
 | 
			
		||||
        {
 | 
			
		||||
            Name = "Atlantis",
 | 
			
		||||
            PlayerRace = true,
 | 
			
		||||
            PlayerFileId = 1,
 | 
			
		||||
            ColonistsPerResource = 1000,
 | 
			
		||||
            GrowthRatePercent = 19,
 | 
			
		||||
            PRT = PRT.Other,
 | 
			
		||||
            HasOBRM = true,
 | 
			
		||||
            FactoryCost3 = false,
 | 
			
		||||
            FactoryNumberPer10k = 8,
 | 
			
		||||
            FactoryResCost = 8,
 | 
			
		||||
            FactoryResPer10 = 15,
 | 
			
		||||
            MineResCost = 3,
 | 
			
		||||
            MineMineralsPer10 = 10,
 | 
			
		||||
            MineNumberPer10k = 10
 | 
			
		||||
        };
 | 
			
		||||
        db.Add(r);
 | 
			
		||||
        db.SaveChanges();
 | 
			
		||||
 | 
			
		||||
        var loader = new CSV.PlanetLoader();
 | 
			
		||||
        loader.ImportForRace(Services.Game.Player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user