Compare commits
19 Commits
main
...
resource-b
Author | SHA1 | Date | |
---|---|---|---|
|
4e6bab1b53 | ||
|
64692bc161 | ||
|
8d27c072ca | ||
|
d7c0abdfc2 | ||
|
d0b6bbaa3a | ||
|
411b199a98 | ||
|
b48b15b652 | ||
|
e83480b7f9 | ||
|
d1b4f23992 | ||
|
42c0ef2159 | ||
|
8701ac703e | ||
|
fe53e01a4a | ||
|
617dbc9812 | ||
|
1606d25fed | ||
|
5585876005 | ||
|
54c7f4ce92 | ||
|
a062322dda | ||
|
4599c558e4 | ||
|
71f7f4b257 |
4
.github/workflows/build-test.yaml
vendored
4
.github/workflows/build-test.yaml
vendored
@ -31,9 +31,9 @@ jobs:
|
||||
- name: Restore nuget packages
|
||||
run: nuget restore
|
||||
- name: Build client and installer
|
||||
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
|
||||
run: dotnet build Client/Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client
|
||||
- name: Build server win-x64
|
||||
run: dotnet build RageCoop.Server/RageCoop.Server.csproj -o bin/Release/Server
|
||||
run: dotnet build Server/RageCoop.Server.csproj -o bin/Release/Server
|
||||
- name: Upload server
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -11,7 +12,6 @@ using System.Windows.Input;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||
using Path = System.IO.Path;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client.Installer
|
||||
{
|
||||
@ -58,64 +58,53 @@ namespace RageCoop.Client.Installer
|
||||
{
|
||||
UpdateStatus("Checking requirements");
|
||||
var shvPath = Path.Combine(root, "ScriptHookV.dll");
|
||||
var shvdnPath = Path.Combine(root, "ScriptHookVDotNet3.dll");
|
||||
var scriptsPath = Path.Combine(root, "Scripts");
|
||||
var lemonPath = Path.Combine(scriptsPath, "LemonUI.SHVDN3.dll");
|
||||
var installPath = Path.Combine(scriptsPath, "RageCoop");
|
||||
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName == installPath)
|
||||
var installPath = Path.Combine(root, "RageCoop", "Scripts");
|
||||
var legacyPath = Path.Combine(scriptsPath, "RageCoop");
|
||||
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName.StartsWith(installPath))
|
||||
{
|
||||
throw new InvalidOperationException("The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
|
||||
}
|
||||
Directory.CreateDirectory(installPath);
|
||||
if (!File.Exists(shvPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookV first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
if (!File.Exists(shvdnPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookVDotNet first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
var shvdnVer = GetVer(shvdnPath);
|
||||
if (shvdnVer < new Version(3, 5, 1))
|
||||
{
|
||||
MessageBox.Show("Please update ScriptHookVDotNet to latest version!" +
|
||||
$"\nCurrent version is {shvdnVer}, 3.5.1 or higher is required");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
if (File.Exists(lemonPath))
|
||||
{
|
||||
var lemonVer = GetVer(lemonPath);
|
||||
if (lemonVer < new Version(1, 7))
|
||||
{
|
||||
UpdateStatus("Updating LemonUI");
|
||||
File.WriteAllBytes(lemonPath, getLemon());
|
||||
}
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(installPath);
|
||||
|
||||
File.Copy("ScriptHookVDotNet.dll", Path.Combine(root, "ScriptHookVDotNet.asi"), true);
|
||||
File.Copy("ScriptHookVDotNet3.dll", Path.Combine(root, "ScriptHookVDotNet3.dll"), true);
|
||||
|
||||
|
||||
UpdateStatus("Removing old versions");
|
||||
|
||||
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (f.EndsWith("RageCoop.Client.Settings.xml")) { continue; }
|
||||
File.Delete(f);
|
||||
}
|
||||
|
||||
// 1.5 installation check
|
||||
if (Directory.Exists(legacyPath))
|
||||
{
|
||||
Directory.Delete(legacyPath, true);
|
||||
}
|
||||
|
||||
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(f);
|
||||
}
|
||||
|
||||
if (File.Exists("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll"))
|
||||
if (File.Exists("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll") && File.Exists("RageCoop.Client.Loader.dll"))
|
||||
{
|
||||
UpdateStatus("Installing...");
|
||||
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath));
|
||||
File.Copy("RageCoop.Client.Loader.dll", Path.Combine(scriptsPath, "RageCoop.Client.Loader.dll"), true);
|
||||
Finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Required files are missing, please re-download the zip from official website");
|
||||
throw new Exception("Required files are missing, please re-download the installer from official website");
|
||||
}
|
||||
|
||||
void Finish()
|
||||
@ -124,7 +113,7 @@ namespace RageCoop.Client.Installer
|
||||
checkKeys:
|
||||
UpdateStatus("Checking conflicts");
|
||||
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
|
||||
var settingsPath = Path.Combine(installPath, @"Data\RageCoop.Client.Settings.xml");
|
||||
var settingsPath = Path.Combine(root, Util.SettingsPath);
|
||||
Settings settings = null;
|
||||
try
|
||||
{
|
||||
@ -199,15 +188,5 @@ namespace RageCoop.Client.Installer
|
||||
Dispatcher.BeginInvoke(new Action(() => Status.Content = status));
|
||||
}
|
||||
|
||||
private Version GetVer(string location)
|
||||
{
|
||||
return Version.Parse(FileVersionInfo.GetVersionInfo(location).FileVersion);
|
||||
}
|
||||
|
||||
private byte[] getLemon()
|
||||
{
|
||||
return (byte[])Resource.ResourceManager.GetObject("LemonUI_SHVDN3");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
54
Client/Installer/RageCoop.Client.Installer.csproj
Normal file
54
Client/Installer/RageCoop.Client.Installer.csproj
Normal file
@ -0,0 +1,54 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="bg.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<OutDir>..\..\bin\Debug\Client</OutDir>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<OutDir>..\..\bin\Release\Client</OutDir>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
|
||||
<ProjectReference Include="..\Loader\RageCoop.Client.Loader.csproj" />
|
||||
<ProjectReference Include="..\Scripts\RageCoop.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="bg.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
Client/Installer/RageCoop.Client.Installer.csproj.user
Normal file
9
Client/Installer/RageCoop.Client.Installer.csproj.user
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -59,15 +59,5 @@ namespace RageCoop.Client.Installer {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] LemonUI_SHVDN3 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("LemonUI_SHVDN3", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -118,7 +118,4 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="LemonUI_SHVDN3" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Resources\LemonUI.SHVDN3.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
205
Client/Loader/LoaderContext.cs
Normal file
205
Client/Loader/LoaderContext.cs
Normal file
@ -0,0 +1,205 @@
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using Console = GTA.Console;
|
||||
|
||||
namespace RageCoop.Client.Loader
|
||||
{
|
||||
|
||||
public class LoaderContext : MarshalByRefObject, IDisposable
|
||||
{
|
||||
#region PRIMARY-LOADING-LOGIC
|
||||
public static ConcurrentDictionary<string, LoaderContext> LoadedDomains => new ConcurrentDictionary<string, LoaderContext>(_loadedDomains);
|
||||
|
||||
private static readonly ConcurrentDictionary<string, LoaderContext> _loadedDomains = new ConcurrentDictionary<string, LoaderContext>();
|
||||
|
||||
public bool UnloadRequested;
|
||||
public string BaseDirectory => AppDomain.CurrentDomain.BaseDirectory;
|
||||
private ScriptDomain CurrentDomain => ScriptDomain.CurrentDomain;
|
||||
public static void CheckForUnloadRequest()
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
foreach (var p in _loadedDomains.Values)
|
||||
{
|
||||
if (p.UnloadRequested)
|
||||
{
|
||||
Unload(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsLoaded(string dir)
|
||||
{
|
||||
return _loadedDomains.ContainsKey(Path.GetFullPath(dir).ToLower());
|
||||
}
|
||||
public static LoaderContext Load(string dir)
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
dir = Path.GetFullPath(dir).ToLower();
|
||||
if (IsLoaded(dir))
|
||||
{
|
||||
throw new Exception("Already loaded");
|
||||
}
|
||||
ScriptDomain newDomain = null;
|
||||
try
|
||||
{
|
||||
dir = Path.GetFullPath(dir).ToLower();
|
||||
Directory.CreateDirectory(dir);
|
||||
Exception e = null;
|
||||
// Load domain in main thread
|
||||
Main.QueueToMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
/*
|
||||
var assemblies = new List<string>();
|
||||
assemblies.Add(typeof(DomainLoader).Assembly.Location);
|
||||
assemblies.AddRange(typeof(DomainLoader).Assembly.GetReferencedAssemblies()
|
||||
.Select(x => Assembly.Load(x.FullName).Location)
|
||||
.Where(x => !string.IsNullOrEmpty(x)));
|
||||
*/
|
||||
|
||||
// Delete API assemblies
|
||||
Directory.GetFiles(dir, "ScriptHookVDotNet*", SearchOption.AllDirectories).ToList().ForEach(x => File.Delete(x));
|
||||
var ctxAsm = Path.Combine(dir, "RageCoop.Client.Loader.dll");
|
||||
if (File.Exists(ctxAsm)) { File.Delete(ctxAsm); }
|
||||
|
||||
newDomain = ScriptDomain.Load(
|
||||
Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName, dir);
|
||||
newDomain.AppDomain.SetData("Primary", ScriptDomain.CurrentDomain);
|
||||
newDomain.AppDomain.SetData("Console", ScriptDomain.CurrentDomain.AppDomain.GetData("Console"));
|
||||
var context = (LoaderContext)newDomain.AppDomain.CreateInstanceFromAndUnwrap(
|
||||
typeof(LoaderContext).Assembly.Location,
|
||||
typeof(LoaderContext).FullName, ignoreCase: false,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
null,
|
||||
new object[] { }
|
||||
, null, null);
|
||||
newDomain.AppDomain.SetData("RageCoop.Client.LoaderContext", context);
|
||||
newDomain.Start();
|
||||
_loadedDomains.TryAdd(dir, context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
e = ex;
|
||||
}
|
||||
});
|
||||
// Wait till next tick
|
||||
GTA.Script.Yield();
|
||||
if (e != null) { throw e; }
|
||||
return _loadedDomains[dir];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GTA.UI.Notification.Show(ex.ToString());
|
||||
Console.Error(ex);
|
||||
if (newDomain != null)
|
||||
{
|
||||
ScriptDomain.Unload(newDomain);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unload(LoaderContext domain)
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
Exception ex = null;
|
||||
var name = domain.CurrentDomain.Name;
|
||||
Console.Info("Unloading domain: " + name);
|
||||
Main.QueueToMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_loadedDomains.TryRemove(domain.BaseDirectory.ToLower(), out _))
|
||||
{
|
||||
throw new Exception("Failed to remove domain from list");
|
||||
}
|
||||
domain.Dispose();
|
||||
ScriptDomain.Unload(domain.CurrentDomain);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ex = e;
|
||||
GTA.UI.Notification.Show(ex.ToString());
|
||||
}
|
||||
});
|
||||
GTA.Script.Yield();
|
||||
if (ex != null)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
Console.Info("Unloaded domain: " + name);
|
||||
}
|
||||
}
|
||||
public static void Unload(string dir)
|
||||
{
|
||||
Unload(_loadedDomains[Path.GetFullPath(dir).ToLower()]);
|
||||
}
|
||||
public static void UnloadAll()
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
foreach (var d in _loadedDomains.Values.ToArray())
|
||||
{
|
||||
Unload(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LOAD-CONTEXT
|
||||
|
||||
|
||||
private LoaderContext()
|
||||
{
|
||||
AppDomain.CurrentDomain.DomainUnload += (s, e) => Dispose();
|
||||
PrimaryDomain.Tick += Tick;
|
||||
PrimaryDomain.KeyEvent += KeyEvent;
|
||||
Console.Info($"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
|
||||
}
|
||||
public static ScriptDomain PrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain;
|
||||
public static LoaderContext CurrentContext => AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext") as LoaderContext;
|
||||
/// <summary>
|
||||
/// Request the current domain to be unloaded
|
||||
/// </summary>
|
||||
public static void RequestUnload()
|
||||
{
|
||||
if (PrimaryDomain == null)
|
||||
{
|
||||
throw new NotSupportedException("Current domain not loaded by the loader therfore cannot be unloaded automatically");
|
||||
}
|
||||
CurrentContext.UnloadRequested = true;
|
||||
}
|
||||
private void Tick()
|
||||
{
|
||||
CurrentDomain.DoTick();
|
||||
}
|
||||
|
||||
private void KeyEvent(Keys keys, bool status)
|
||||
{
|
||||
CurrentDomain.DoKeyEvent(keys, status);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (PrimaryDomain == null) { return; }
|
||||
PrimaryDomain.Tick -= Tick;
|
||||
PrimaryDomain.KeyEvent -= KeyEvent;
|
||||
AppDomain.CurrentDomain.SetData("Primary", null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
69
Client/Loader/Main.cs
Normal file
69
Client/Loader/Main.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Console = GTA.Console;
|
||||
namespace RageCoop.Client.Loader
|
||||
{
|
||||
public class Main : GTA.Script
|
||||
{
|
||||
private static readonly string GameDir = Directory.GetParent(typeof(SHVDN.ScriptDomain).Assembly.Location).FullName;
|
||||
private static readonly string ScriptsLocation = Path.Combine(GameDir, "RageCoop", "Scripts");
|
||||
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
|
||||
private static int MainThreadID;
|
||||
public Main()
|
||||
{
|
||||
if (LoaderContext.PrimaryDomain != null) { throw new InvalidOperationException("Improperly placed loader assembly, please re-install to fix this issue"); }
|
||||
Tick += OnTick;
|
||||
SHVDN.ScriptDomain.CurrentDomain.Tick += DomainTick;
|
||||
Aborted += (s, e) => LoaderContext.UnloadAll();
|
||||
}
|
||||
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
while (Game.IsLoading)
|
||||
{
|
||||
GTA.Script.Yield();
|
||||
}
|
||||
LoaderContext.CheckForUnloadRequest();
|
||||
if (!LoaderContext.IsLoaded(ScriptsLocation))
|
||||
{
|
||||
if (!File.Exists(Path.Combine(ScriptsLocation, "RageCoop.Client.dll")))
|
||||
{
|
||||
GTA.UI.Notification.Show("~r~Main assembly is missing, please re-install the client");
|
||||
Abort();
|
||||
}
|
||||
LoaderContext.Load(ScriptsLocation);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void QueueToMainThread(Action task)
|
||||
{
|
||||
if (Thread.CurrentThread.ManagedThreadId != MainThreadID)
|
||||
{
|
||||
TaskQueue.Enqueue(task);
|
||||
}
|
||||
else
|
||||
{
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DomainTick()
|
||||
{
|
||||
if (MainThreadID == default) { MainThreadID = Thread.CurrentThread.ManagedThreadId; }
|
||||
while (TaskQueue.TryDequeue(out var task))
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
Client/Loader/Properties/AssemblyInfo.cs
Normal file
35
Client/Loader/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
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("RageCoop.Client.Loader")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client.Loader")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[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("fc8cbdbb-6dc3-43af-b34d-092e476410a5")]
|
||||
|
||||
// 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")]
|
58
Client/Loader/RageCoop.Client.Loader.csproj
Normal file
58
Client/Loader/RageCoop.Client.Loader.csproj
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>RageCoop.Client.Loader</RootNamespace>
|
||||
<AssemblyName>RageCoop.Client.Loader</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutPutPath>..\..\bin\Debug\Client.Loader</OutPutPath>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutPutPath>..\..\bin\Release\Client.Loader</OutPutPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="ScriptHookVDotNet">
|
||||
<HintPath>..\..\libs\ScriptHookVDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ScriptHookVDotNet3">
|
||||
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="LoaderContext.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -7,6 +7,7 @@ using System.Windows.Forms;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
internal class DevTool : Script
|
||||
{
|
||||
public static Vehicle ToMark;
|
||||
@ -14,10 +15,14 @@ namespace RageCoop.Client
|
||||
public static int Current = 0;
|
||||
public static int Secondary = 0;
|
||||
public static MuzzleDir Direction = MuzzleDir.Forward;
|
||||
public static Script Instance;
|
||||
public DevTool()
|
||||
{
|
||||
Util.StartUpCheck();
|
||||
Instance = this;
|
||||
Tick += OnTick;
|
||||
KeyDown += OnKeyDown;
|
||||
Pause();
|
||||
}
|
||||
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
@ -73,7 +78,7 @@ namespace RageCoop.Client
|
||||
}
|
||||
DevToolMenu.secondaryBoneIndexItem.AltTitle = Secondary.ToString();
|
||||
}
|
||||
private static void OnTick(object sender, EventArgs e)
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if (ToMark == null || !ToMark.Exists()) { return; }
|
||||
Update();
|
@ -2,24 +2,28 @@
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Console = GTA.Console;
|
||||
using Script = GTA.Script;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
internal class Main : Script
|
||||
{
|
||||
private bool _gameLoaded = false;
|
||||
private static bool _gameLoaded = false;
|
||||
internal static Version Version = typeof(Main).Assembly.GetName().Version;
|
||||
|
||||
internal static int LocalPlayerID = 0;
|
||||
@ -27,48 +31,79 @@ namespace RageCoop.Client
|
||||
internal static RelationshipGroup SyncedPedsGroup;
|
||||
|
||||
internal static new Settings Settings = null;
|
||||
internal static Scripting.BaseScript BaseScript = new Scripting.BaseScript();
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
internal static Chat MainChat = null;
|
||||
internal static Stopwatch Counter = new Stopwatch();
|
||||
internal static Logger Logger = null;
|
||||
|
||||
internal static ulong Ticked = 0;
|
||||
internal static Vector3 PlayerPosition;
|
||||
internal static Scripting.Resources Resources = null;
|
||||
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
public static Worker Worker;
|
||||
|
||||
internal static Resources Resources = null;
|
||||
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
|
||||
public static string LogPath => $"{Settings.DataDirectory}\\RageCoop.Client.log";
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public Main()
|
||||
{
|
||||
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
|
||||
Util.StartUpCheck();
|
||||
Console.Info($"Starting {typeof(Main).FullName}, domain: {AppDomain.CurrentDomain.Id} {AppDomain.CurrentDomain.FriendlyName}");
|
||||
try
|
||||
{
|
||||
Settings = Util.ReadSettings();
|
||||
if (Settings.DataDirectory.StartsWith("Scripts"))
|
||||
{
|
||||
var defaultDir = new Settings().DataDirectory;
|
||||
Console.Warning("Data directory must be outside scripts folder, migrating to default direcoty: " + defaultDir);
|
||||
if (Directory.Exists(Settings.DataDirectory))
|
||||
{
|
||||
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Settings.DataDirectory), new DirectoryInfo(defaultDir));
|
||||
Directory.Delete(Settings.DataDirectory, true);
|
||||
}
|
||||
Settings.DataDirectory = defaultDir;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
|
||||
// GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
|
||||
Settings = new Settings();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
Directory.CreateDirectory(Settings.DataDirectory);
|
||||
Logger = new Logger()
|
||||
{
|
||||
LogPath = $"{Settings.DataDirectory}\\RageCoop.Client.log",
|
||||
UseConsole = false,
|
||||
Writers = new List<StreamWriter> { CoreUtils.OpenWriter(LogPath) },
|
||||
#if DEBUG
|
||||
LogLevel = 0,
|
||||
#else
|
||||
LogLevel=Settings.LogLevel,
|
||||
LogLevel = Settings.LogLevel,
|
||||
#endif
|
||||
};
|
||||
Resources = new Scripting.Resources();
|
||||
Logger.OnFlush += (line, formatted) =>
|
||||
{
|
||||
switch (line.LogLevel)
|
||||
{
|
||||
#if DEBUG
|
||||
// case LogLevel.Trace:
|
||||
case LogLevel.Debug:
|
||||
Console.Info(line.Message);
|
||||
break;
|
||||
#endif
|
||||
case LogLevel.Info:
|
||||
Console.Info(line.Message);
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
Console.Warning(line.Message);
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
Console.Error(line.Message);
|
||||
break;
|
||||
}
|
||||
};
|
||||
ScriptDomain.CurrentDomain.Tick += DomainTick;
|
||||
Resources = new Resources();
|
||||
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
|
||||
{
|
||||
Tick += (object sender, EventArgs e) =>
|
||||
@ -91,21 +126,74 @@ namespace RageCoop.Client
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
MainChat = new Chat();
|
||||
Aborted += OnAborted;
|
||||
Tick += OnTick;
|
||||
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
|
||||
KeyDown += OnKeyDown;
|
||||
KeyDown += (s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
|
||||
KeyUp += (s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
|
||||
Aborted += (object sender, EventArgs e) => Disconnected("Abort");
|
||||
|
||||
Util.NativeMemory();
|
||||
Counter.Restart();
|
||||
|
||||
}
|
||||
|
||||
private static void OnAborted(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
WorldThread.Instance?.Abort();
|
||||
DevTool.Instance?.Abort();
|
||||
ScriptDomain.CurrentDomain.Tick -= DomainTick;
|
||||
Disconnected("Abort");
|
||||
WorldThread.DoQueuedActions();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DomainTick()
|
||||
{
|
||||
while (TaskQueue.TryDequeue(out var task))
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
try
|
||||
{
|
||||
EntityPool.DoSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to main thread and wait for execution to complete, must be called from script thread.
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
internal static void QueueToMainThreadAndWait(Action task)
|
||||
{
|
||||
Exception e = null;
|
||||
TaskQueue.Enqueue(() => { try { task(); } catch (Exception ex) { e = ex; } });
|
||||
Yield();
|
||||
if (e != null) { throw e; }
|
||||
}
|
||||
|
||||
public static Ped P;
|
||||
public static float FPS;
|
||||
private bool _lastDead;
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
private static bool _lastDead;
|
||||
private static void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
P = Game.Player.Character;
|
||||
PlayerPosition = P.ReadPosition();
|
||||
@ -126,7 +214,6 @@ namespace RageCoop.Client
|
||||
#endif
|
||||
|
||||
|
||||
DoQueuedActions();
|
||||
if (!Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
@ -135,14 +222,6 @@ namespace RageCoop.Client
|
||||
{
|
||||
Game.TimeScale = 1;
|
||||
}
|
||||
try
|
||||
{
|
||||
EntityPool.DoSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
}
|
||||
|
||||
if (Networking.ShowNetworkInfo)
|
||||
{
|
||||
@ -153,7 +232,7 @@ namespace RageCoop.Client
|
||||
|
||||
MainChat.Tick();
|
||||
PlayerList.Tick();
|
||||
if (!Scripting.API.Config.EnableAutoRespawn)
|
||||
if (!API.Config.EnableAutoRespawn)
|
||||
{
|
||||
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
|
||||
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
|
||||
@ -167,8 +246,8 @@ namespace RageCoop.Client
|
||||
{
|
||||
P.Health = 1;
|
||||
Game.Player.WantedLevel = 0;
|
||||
Main.Logger.Debug("Player died.");
|
||||
Scripting.API.Events.InvokePlayerDied();
|
||||
Logger.Debug("Player died.");
|
||||
API.Events.InvokePlayerDied();
|
||||
}
|
||||
GTA.UI.Screen.StopEffects();
|
||||
}
|
||||
@ -179,13 +258,13 @@ namespace RageCoop.Client
|
||||
}
|
||||
else if (P.IsDead && !_lastDead)
|
||||
{
|
||||
Scripting.API.Events.InvokePlayerDied();
|
||||
API.Events.InvokePlayerDied();
|
||||
}
|
||||
|
||||
_lastDead = P.IsDead;
|
||||
Ticked++;
|
||||
}
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
private static void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
@ -296,7 +375,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
Voice.Init();
|
||||
}
|
||||
QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
WorldThread.Traffic(!Settings.DisableTraffic);
|
||||
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
|
||||
@ -312,7 +391,7 @@ namespace RageCoop.Client
|
||||
|
||||
|
||||
Logger.Info($">> Disconnected << reason: {reason}");
|
||||
QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
@ -326,68 +405,13 @@ namespace RageCoop.Client
|
||||
CoopMenu.DisconnectedMenuSetting();
|
||||
GTA.UI.Notification.Show("~r~Disconnected: " + reason);
|
||||
LocalPlayerID = default;
|
||||
Resources.Unload();
|
||||
});
|
||||
Memory.RestorePatches();
|
||||
DownloadManager.Cleanup();
|
||||
Voice.ClearAll();
|
||||
Resources.Unload();
|
||||
}
|
||||
private static void DoQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
foreach (var action in QueuedActions.ToArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (action())
|
||||
{
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
|
||||
internal static void QueueAction(Func<bool> a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(a);
|
||||
}
|
||||
}
|
||||
internal static void QueueAction(Action a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(() => { a(); return true; });
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears all queued actions
|
||||
/// </summary>
|
||||
internal static void ClearQueuedActions()
|
||||
{
|
||||
lock (QueuedActions) { QueuedActions.Clear(); }
|
||||
}
|
||||
|
||||
public static void Delay(Action a, int time)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
QueueAction(a);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ namespace RageCoop.Client
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
public static NativeItem ReloadItem = new NativeItem("Reload", "Reload RAGECOOP and associated scripts");
|
||||
public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0");
|
||||
public static NativeCheckboxItem ShowOwnerItem = new NativeCheckboxItem("Show entity owner", "Show the owner name of the entity you're aiming at", false);
|
||||
private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
|
||||
@ -46,13 +47,18 @@ namespace RageCoop.Client
|
||||
};
|
||||
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
|
||||
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
|
||||
ReloadItem.Activated += ReloadDomain;
|
||||
Menu.Add(SimulatedLatencyItem);
|
||||
Menu.Add(ShowNetworkInfoItem);
|
||||
Menu.Add(ShowOwnerItem);
|
||||
Menu.Add(ReloadItem);
|
||||
Menu.AddSubMenu(DiagnosticMenu);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void ReloadDomain(object sender, EventArgs e)
|
||||
{
|
||||
Loader.LoaderContext.RequestUnload();
|
||||
}
|
||||
}
|
||||
}
|
@ -73,10 +73,12 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (enableItem.Checked)
|
||||
{
|
||||
DevTool.Instance.Resume();
|
||||
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevTool.Instance.Pause();
|
||||
DevTool.ToMark = null;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -60,7 +61,7 @@ namespace RageCoop.Client.Menus
|
||||
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
|
||||
|
||||
// Need to be processed in main thread
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (serverList == null)
|
||||
{
|
||||
@ -127,7 +128,7 @@ namespace RageCoop.Client.Menus
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
ResultItem.Title = "Download failed!";
|
||||
ResultItem.Description = ex.Message;
|
@ -1,5 +1,7 @@
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
@ -30,6 +32,11 @@ namespace RageCoop.Client.Menus
|
||||
|
||||
private static void StartUpdate(object sender, EventArgs e)
|
||||
{
|
||||
if (CoreUtils.GetLatestVersion() < Main.Version)
|
||||
{
|
||||
GTA.UI.Notification.Show("Local version is newer than remote version, update can't continue");
|
||||
return;
|
||||
}
|
||||
IsUpdating = true;
|
||||
Menu.Clear();
|
||||
Menu.Add(_updatingItem);
|
||||
@ -45,7 +52,7 @@ namespace RageCoop.Client.Menus
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
|
||||
|
||||
client.DownloadProgressChanged += (s, e1) => { Main.QueueAction(() => { _updatingItem.AltTitle = $"{e1.ProgressPercentage}%"; }); };
|
||||
client.DownloadProgressChanged += (s, e1) => { API.QueueAction(() => { _updatingItem.AltTitle = $"{e1.ProgressPercentage}%"; }); };
|
||||
client.DownloadFileCompleted += (s, e2) => { Install(); };
|
||||
client.DownloadFileAsync(new Uri("https://github.com/RAGECOOP/RAGECOOP-V/releases/download/nightly/RageCoop.Client.zip"), _downloadPath);
|
||||
}
|
||||
@ -60,24 +67,22 @@ namespace RageCoop.Client.Menus
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
_updatingItem.AltTitle = "Installing...";
|
||||
});
|
||||
Directory.CreateDirectory(@"Scripts\RageCoop");
|
||||
foreach (var f in Directory.GetFiles(@"Scripts\RageCoop", "*.dll", SearchOption.AllDirectories))
|
||||
var insatllPath = @"RageCoop\Scripts";
|
||||
Directory.CreateDirectory(insatllPath);
|
||||
foreach (var f in Directory.GetFiles(insatllPath, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
try { File.Delete(f); }
|
||||
catch { }
|
||||
}
|
||||
new FastZip().ExtractZip(_downloadPath, "Scripts", FastZip.Overwrite.Always, null, null, null, true);
|
||||
new FastZip().ExtractZip(_downloadPath, insatllPath, FastZip.Overwrite.Always, null, null, null, true);
|
||||
try { File.Delete(_downloadPath); } catch { }
|
||||
try { File.Delete(Path.Combine("Scripts", "RageCoop.Client.Installer.exe")); } catch { }
|
||||
Main.QueueAction(() =>
|
||||
{
|
||||
Util.Reload();
|
||||
IsUpdating = false;
|
||||
});
|
||||
try { File.Delete(Path.Combine(insatllPath, "RageCoop.Client.Installer.exe")); } catch { }
|
||||
Loader.LoaderContext.RequestUnload();
|
||||
IsUpdating = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
@ -49,7 +49,7 @@ namespace RageCoop.Client
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error("Error occurred when loading server resource:");
|
||||
Main.Logger.Error("Error occurred when loading server resource");
|
||||
Main.Logger.Error(ex);
|
||||
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -25,6 +26,7 @@ namespace RageCoop.Client
|
||||
static Networking()
|
||||
{
|
||||
Security = new Security(Main.Logger);
|
||||
Packets.CustomEvent.ResolveHandle = _resolveHandle;
|
||||
}
|
||||
|
||||
public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null)
|
||||
@ -98,7 +100,7 @@ namespace RageCoop.Client
|
||||
try { ProcessMessage(m); }
|
||||
catch (Exception ex) { Main.Logger.Error(ex); }
|
||||
};
|
||||
Main.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
|
||||
API.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
|
||||
Menus.CoopMenu._serverConnectItem.Enabled = false;
|
||||
Security.Regen();
|
||||
if (publicKey == null)
|
||||
@ -133,13 +135,13 @@ namespace RageCoop.Client
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error("Cannot connect to server: ", ex);
|
||||
Main.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
|
||||
API.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
|
||||
}
|
||||
IsConnecting = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
public static bool IsOnServer { get => ServerConnection?.Status == NetConnectionStatus.Connected; }
|
||||
public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
|
||||
|
||||
#region -- PLAYER --
|
||||
private static void PlayerConnect(Packets.PlayerConnect packet)
|
||||
@ -152,7 +154,7 @@ namespace RageCoop.Client
|
||||
PlayerList.SetPlayer(packet.PedID, packet.Username);
|
||||
|
||||
Main.Logger.Debug($"player connected:{p.Username}");
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected."));
|
||||
}
|
||||
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
|
||||
@ -160,7 +162,7 @@ namespace RageCoop.Client
|
||||
var player = PlayerList.GetPlayer(packet.PedID);
|
||||
if (player == null) { return; }
|
||||
PlayerList.RemovePlayer(packet.PedID);
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
EntityPool.RemoveAllFromPlayer(packet.PedID);
|
||||
GTA.UI.Notification.Show($"~h~{player.Username}~h~ left.");
|
@ -1,7 +1,9 @@
|
||||
using GTA;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
@ -149,7 +151,7 @@ namespace RageCoop.Client
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
|
||||
return true;
|
||||
@ -236,7 +238,7 @@ namespace RageCoop.Client
|
||||
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
|
||||
packet.Deserialize(msg);
|
||||
|
||||
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
|
||||
API.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
|
||||
}
|
||||
break;
|
||||
|
||||
@ -259,22 +261,22 @@ namespace RageCoop.Client
|
||||
|
||||
case PacketType.CustomEvent:
|
||||
{
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.CustomEventQueued:
|
||||
{
|
||||
recycle = false;
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
|
||||
Main.QueueAction(() =>
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent();
|
||||
if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
|
||||
{
|
||||
recycle = false;
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
Peer.Recycle(msg);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
Peer.Recycle(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -291,7 +293,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
recycle = false;
|
||||
// Dispatch to script thread
|
||||
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
|
||||
API.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
|
||||
}
|
||||
break;
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -90,9 +91,9 @@ namespace RageCoop.Client
|
||||
Blip b;
|
||||
if (sp.IsPlayer)
|
||||
{
|
||||
p.BlipColor = Scripting.API.Config.BlipColor;
|
||||
p.BlipSprite = Scripting.API.Config.BlipSprite;
|
||||
p.BlipScale = Scripting.API.Config.BlipScale;
|
||||
p.BlipColor = API.Config.BlipColor;
|
||||
p.BlipSprite = API.Config.BlipSprite;
|
||||
p.BlipScale = API.Config.BlipScale;
|
||||
}
|
||||
else if ((b = ped.AttachedBlip) != null)
|
||||
{
|
@ -2,6 +2,7 @@
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -83,7 +84,7 @@ namespace RageCoop.Client
|
||||
p._latencyToServer = packet.Latency;
|
||||
p.Position = packet.Position;
|
||||
p.IsHost = packet.IsHost;
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (p.FakeBlip?.Exists() != true)
|
||||
{
|
||||
@ -95,9 +96,9 @@ namespace RageCoop.Client
|
||||
}
|
||||
else
|
||||
{
|
||||
p.FakeBlip.Color = Scripting.API.Config.BlipColor;
|
||||
p.FakeBlip.Scale = Scripting.API.Config.BlipScale;
|
||||
p.FakeBlip.Sprite = Scripting.API.Config.BlipSprite;
|
||||
p.FakeBlip.Color = API.Config.BlipColor;
|
||||
p.FakeBlip.Scale = API.Config.BlipScale;
|
||||
p.FakeBlip.Sprite = API.Config.BlipSprite;
|
||||
p.FakeBlip.DisplayType = BlipDisplayType.Default;
|
||||
p.FakeBlip.Position = p.Position;
|
||||
}
|
||||
@ -124,7 +125,7 @@ namespace RageCoop.Client
|
||||
if (Players.TryGetValue(id, out var player))
|
||||
{
|
||||
Players.Remove(id);
|
||||
Main.QueueAction(() => player.FakeBlip?.Delete());
|
||||
API.QueueAction(() => player.FakeBlip?.Delete());
|
||||
}
|
||||
}
|
||||
public static void Cleanup()
|
@ -16,7 +16,7 @@ using System.Resources;
|
||||
|
||||
|
||||
// Version informationr(
|
||||
[assembly: AssemblyVersion("1.5.4.3")]
|
||||
[assembly: AssemblyFileVersion("1.5.4.3")]
|
||||
[assembly: AssemblyVersion("1.5.6.1")]
|
||||
[assembly: AssemblyFileVersion("1.5.6.1")]
|
||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||
|
157
Client/Scripts/RageCoop.Client.csproj
Normal file
157
Client/Scripts/RageCoop.Client.csproj
Normal file
@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>RageCoop.Client</RootNamespace>
|
||||
<AssemblyName>RageCoop.Client</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<OutPutPath>..\..\bin\Debug\Client</OutPutPath>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<OutPutPath>..\..\bin\Release\Client</OutPutPath>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Debug.cs" />
|
||||
<Compile Include="DevTools\DevTool.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Menus\CoopMenu.cs" />
|
||||
<Compile Include="Menus\Sub\DebugMenu.cs" />
|
||||
<Compile Include="Menus\Sub\DevToolMenu.cs" />
|
||||
<Compile Include="Menus\Sub\ServersMenu.cs" />
|
||||
<Compile Include="Menus\Sub\SettingsMenu.cs" />
|
||||
<Compile Include="Menus\Sub\UpdateMenu.cs" />
|
||||
<Compile Include="Networking\Chat.cs" />
|
||||
<Compile Include="Networking\DownloadManager.cs" />
|
||||
<Compile Include="Networking\HolePunch.cs" />
|
||||
<Compile Include="Networking\Networking.cs" />
|
||||
<Compile Include="Networking\Receive.cs" />
|
||||
<Compile Include="Networking\Send.cs" />
|
||||
<Compile Include="Networking\Statistics.cs" />
|
||||
<Compile Include="PlayerList.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>AssemblyInfo.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Scripting\API.cs" />
|
||||
<Compile Include="Scripting\BaseScript.cs" />
|
||||
<Compile Include="Scripting\ClientScript.cs" />
|
||||
<Compile Include="Scripting\Resources.cs" />
|
||||
<Compile Include="Security.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
<Compile Include="Sync\Entities\Ped\SyncedPed.Members.cs" />
|
||||
<Compile Include="Sync\Entities\Ped\SyncedPed.Animations.cs" />
|
||||
<Compile Include="Sync\Entities\SyncedEntity.cs" />
|
||||
<Compile Include="Sync\Entities\Ped\SyncedPed.cs" />
|
||||
<Compile Include="Sync\Entities\SyncedProjectile.cs" />
|
||||
<Compile Include="Sync\Entities\SyncedProp.cs" />
|
||||
<Compile Include="Sync\Entities\Vehicle\SyncedVehicle.cs" />
|
||||
<Compile Include="Sync\Entities\Vehicle\SyncedVehicle.Members.cs" />
|
||||
<Compile Include="Sync\EntityPool.cs" />
|
||||
<Compile Include="Sync\SyncEvents.cs" />
|
||||
<Compile Include="Sync\Voice.cs" />
|
||||
<Compile Include="Util\AddOnDataProvider.cs" />
|
||||
<Compile Include="Util\Memory.cs" />
|
||||
<Compile Include="Util\NativeCaller.cs" />
|
||||
<Compile Include="Util\PedConfigFlags.cs" />
|
||||
<Compile Include="Util\PedExtensions.cs" />
|
||||
<Compile Include="Util\TaskType.cs" />
|
||||
<Compile Include="Util\Util.cs" />
|
||||
<Compile Include="Util\VehicleExtensions.cs" />
|
||||
<Compile Include="Util\WeaponUtil.cs" />
|
||||
<Compile Include="WorldThread.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=1.4.0.12, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\SharpZipLib.1.4.0\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="LemonUI.SHVDN3">
|
||||
<HintPath>..\..\libs\LemonUI.SHVDN3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lidgren.Network">
|
||||
<HintPath>..\..\libs\Lidgren.Network.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Microsoft.Extensions.ObjectPool, Version=6.0.8.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.ObjectPool.6.0.8\lib\net461\Microsoft.Extensions.ObjectPool.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.2.1.0\lib\net472\NAudio.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Asio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.Asio.2.1.0\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.Core.2.1.0\lib\netstandard2.0\NAudio.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Midi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.Midi.2.1.0\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.Wasapi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.Wasapi.2.1.0\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.WinForms, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.WinForms.2.1.0\lib\net472\NAudio.WinForms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NAudio.WinMM, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\NAudio.WinMM.2.1.0\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ScriptHookVDotNet">
|
||||
<HintPath>..\..\libs\ScriptHookVDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ScriptHookVDotNet3">
|
||||
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Properties\AssemblyInfo.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj">
|
||||
<Project>{cc2e8102-e568-4524-aa1f-f8e0f1cfe58a}</Project>
|
||||
<Name>RageCoop.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Loader\RageCoop.Client.Loader.csproj">
|
||||
<Project>{fc8cbdbb-6dc3-43af-b34d-092e476410a5}</Project>
|
||||
<Name>RageCoop.Client.Loader</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup Condition=" '$(DevEnvDir)' != '*Undefined*'">
|
||||
<PostBuildEvent>"$(DevEnvDir)TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt"</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -2,8 +2,11 @@
|
||||
using GTA;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
@ -25,7 +28,7 @@ namespace RageCoop.Client.Scripting
|
||||
/// <summary>
|
||||
/// Provides vital functionality to interact with RAGECOOP
|
||||
/// </summary>
|
||||
public class API
|
||||
public static class API
|
||||
{
|
||||
#region INTERNAL
|
||||
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
|
||||
@ -116,16 +119,22 @@ namespace RageCoop.Client.Scripting
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="GTA.Script.Tick"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.Tick"/> instead.</remarks>
|
||||
[Obsolete]
|
||||
public static event EmptyEvent OnTick;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="Script.KeyDown"/>
|
||||
/// </summary>
|
||||
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyDown"/> instead.</remarks>
|
||||
[Obsolete]
|
||||
public static KeyEventHandler OnKeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// This is equivalent of <see cref="Script.KeyUp"/>
|
||||
/// </summary>
|
||||
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyUp"/> instead.</remarks>
|
||||
[Obsolete]
|
||||
public static KeyEventHandler OnKeyUp;
|
||||
|
||||
#region INVOKE
|
||||
@ -257,30 +266,12 @@ namespace RageCoop.Client.Scripting
|
||||
Networking.SendChatMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
public static void QueueAction(Action a)
|
||||
{
|
||||
Main.QueueAction(a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a"> An <see cref="Func{T, TResult}"/> to be executed with a return value indicating whether it can be removed after execution.</param>
|
||||
public static void QueueAction(Func<bool> a)
|
||||
{
|
||||
Main.QueueAction(a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an event and data to the server.
|
||||
/// </summary>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
|
||||
public static void SendCustomEvent(int eventHash, params object[] args)
|
||||
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent()
|
||||
@ -289,13 +280,27 @@ namespace RageCoop.Client.Scripting
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
/// <summary>
|
||||
/// Send an event and data to the server
|
||||
/// </summary>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
|
||||
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
|
||||
{
|
||||
Args = args,
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from backgound thread, use <see cref="QueueAction(Action)"/> in the handler to dispatch code to script thread.
|
||||
/// </summary>
|
||||
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
|
||||
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
|
||||
public static void RegisterCustomEventHandler(int hash, Action<CustomEventReceivedArgs> handler)
|
||||
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
|
||||
{
|
||||
lock (CustomEventHandlers)
|
||||
{
|
||||
@ -335,5 +340,41 @@ namespace RageCoop.Client.Scripting
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
public static void QueueAction(Action a)
|
||||
{
|
||||
WorldThread.QueueAction(a);
|
||||
}
|
||||
public static void QueueActionAndWait(Action a, int timeout = 15000)
|
||||
{
|
||||
var done = new AutoResetEvent(false);
|
||||
Exception e = null;
|
||||
QueueAction(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
a();
|
||||
done.Set();
|
||||
}
|
||||
catch (Exception ex) { e = ex; }
|
||||
});
|
||||
if (e != null) { throw e; }
|
||||
else if (!done.WaitOne(timeout)) { throw new TimeoutException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a"> An <see cref="Func{T, TResult}"/> to be executed with a return value indicating whether it can be removed after execution.</param>
|
||||
public static void QueueAction(Func<bool> a)
|
||||
{
|
||||
WorldThread.QueueAction(a);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal class BaseScript : ClientScript
|
||||
internal static class BaseScript
|
||||
{
|
||||
private bool _isHost = false;
|
||||
public override void OnStart()
|
||||
private static bool _isHost = false;
|
||||
public static void OnStart()
|
||||
{
|
||||
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
|
||||
API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
|
||||
@ -57,19 +57,19 @@ namespace RageCoop.Client.Scripting
|
||||
});
|
||||
}
|
||||
|
||||
private void WeatherTimeSync(CustomEventReceivedArgs e)
|
||||
private static void WeatherTimeSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
|
||||
Function.Call(Hash._SET_WEATHER_TYPE_TRANSITION, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
|
||||
}
|
||||
|
||||
private void SetDisplayNameTag(CustomEventReceivedArgs e)
|
||||
private static void SetDisplayNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; }
|
||||
}
|
||||
|
||||
private void UpdatePedBlip(CustomEventReceivedArgs e)
|
||||
private static void UpdatePedBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = Entity.FromHandle((int)e.Args[0]);
|
||||
if (p == null) { return; }
|
||||
@ -89,14 +89,13 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateVehicle(CustomEventReceivedArgs e)
|
||||
private static void CreateVehicle(CustomEventReceivedArgs e)
|
||||
{
|
||||
var vehicleModel = (Model)e.Args[1];
|
||||
vehicleModel.Request(1000);
|
||||
Vehicle veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
|
||||
while (veh == null)
|
||||
Vehicle veh;
|
||||
while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
|
||||
{
|
||||
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
veh.CanPretendOccupants = false;
|
||||
@ -109,7 +108,7 @@ namespace RageCoop.Client.Scripting
|
||||
EntityPool.Add(v);
|
||||
}
|
||||
|
||||
private void DeleteServerBlip(CustomEventReceivedArgs e)
|
||||
private static void DeleteServerBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
|
||||
{
|
||||
@ -118,7 +117,7 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
}
|
||||
|
||||
private void ServerBlipSync(CustomEventReceivedArgs obj)
|
||||
private static void ServerBlipSync(CustomEventReceivedArgs obj)
|
||||
{
|
||||
int id = (int)obj.Args[0];
|
||||
var sprite = (BlipSprite)(ushort)obj.Args[1];
|
||||
@ -140,15 +139,12 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
|
||||
|
||||
private void DeleteEntity(CustomEventReceivedArgs e)
|
||||
private static void DeleteEntity(CustomEventReceivedArgs e)
|
||||
{
|
||||
Entity.FromHandle((int)e.Args[0])?.Delete();
|
||||
}
|
||||
|
||||
public override void OnStop()
|
||||
{
|
||||
}
|
||||
private void SetNameTag(CustomEventReceivedArgs e)
|
||||
private static void SetNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null)
|
||||
@ -156,11 +152,11 @@ namespace RageCoop.Client.Scripting
|
||||
p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
}
|
||||
private void SetAutoRespawn(CustomEventReceivedArgs args)
|
||||
private static void SetAutoRespawn(CustomEventReceivedArgs args)
|
||||
{
|
||||
API.Config.EnableAutoRespawn = (bool)args.Args[0];
|
||||
}
|
||||
private void DeleteServerProp(CustomEventReceivedArgs e)
|
||||
private static void DeleteServerProp(CustomEventReceivedArgs e)
|
||||
{
|
||||
var id = (int)e.Args[0];
|
||||
if (EntityPool.ServerProps.TryGetValue(id, out var prop))
|
||||
@ -170,7 +166,7 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
|
||||
}
|
||||
private void ServerObjectSync(CustomEventReceivedArgs e)
|
||||
private static void ServerObjectSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
SyncedProp prop;
|
||||
var id = (int)e.Args[0];
|
||||
@ -185,9 +181,10 @@ namespace RageCoop.Client.Scripting
|
||||
prop.Model = (Model)e.Args[1];
|
||||
prop.Position = (Vector3)e.Args[2];
|
||||
prop.Rotation = (Vector3)e.Args[3];
|
||||
prop.Model.Request(1000);
|
||||
prop.Update();
|
||||
}
|
||||
private void NativeCall(CustomEventReceivedArgs e)
|
||||
private static void NativeCall(CustomEventReceivedArgs e)
|
||||
{
|
||||
List<InputArgument> arguments = new List<InputArgument>();
|
||||
int i;
|
||||
@ -271,7 +268,7 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
|
||||
}
|
||||
private InputArgument GetInputArgument(object obj)
|
||||
private static InputArgument GetInputArgument(object obj)
|
||||
{
|
||||
// Implicit conversion
|
||||
switch (obj)
|
@ -5,15 +5,16 @@ namespace RageCoop.Client.Scripting
|
||||
/// <summary>
|
||||
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
|
||||
/// </summary>
|
||||
public abstract class ClientScript
|
||||
public abstract class ClientScript : GTA.Script
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This method would be called from background thread, call <see cref="API.QueueAction(System.Action)"/> to dispatch it to main thread.
|
||||
/// This method would be called from main thread, right after all script constructors are invoked.
|
||||
/// </summary>
|
||||
public abstract void OnStart();
|
||||
|
||||
/// <summary>
|
||||
/// This method would be called from background thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method.
|
||||
/// This method would be called from main thread right before the whole <see cref="System.AppDomain"/> is unloded but prior to <see cref="GTA.Script.Aborted"/>.
|
||||
/// </summary>
|
||||
public abstract void OnStop();
|
||||
|
190
Client/Scripts/Scripting/Resources.cs
Normal file
190
Client/Scripts/Scripting/Resources.cs
Normal file
@ -0,0 +1,190 @@
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ClientResource
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the resource
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
/// <summary>
|
||||
/// Directory where the scripts is loaded from
|
||||
/// </summary>
|
||||
public string ScriptsDirectory { get; internal set; }
|
||||
/// <summary>
|
||||
/// A resource-specific folder that can be used to store your files.
|
||||
/// </summary>
|
||||
public string DataFolder { get; internal set; }
|
||||
/// <summary>
|
||||
/// Get all <see cref="ClientScript"/> instance in this resource.
|
||||
/// </summary>
|
||||
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
|
||||
/// <summary>
|
||||
/// Get the <see cref="ResourceFile"/> where this script is loaded from.
|
||||
/// </summary>
|
||||
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Core.Logger"/> instance that can be used to debug your resource.
|
||||
/// </summary>
|
||||
public Logger Logger { get; internal set; }
|
||||
}
|
||||
internal class Resources
|
||||
{
|
||||
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new ConcurrentDictionary<string, ClientResource>();
|
||||
private Logger Logger { get; set; }
|
||||
public Resources()
|
||||
{
|
||||
Logger = Main.Logger;
|
||||
}
|
||||
public void Load(string path, string[] zips)
|
||||
{
|
||||
LoadedResources.Clear();
|
||||
foreach (var zip in zips)
|
||||
{
|
||||
var zipPath = Path.Combine(path, zip);
|
||||
Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
|
||||
Unpack(zipPath, Path.Combine(path, "Data"));
|
||||
}
|
||||
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored()).ForEach(x => File.Delete(x));
|
||||
|
||||
// Load it in main thread
|
||||
API.QueueActionAndWait(() =>
|
||||
{
|
||||
Main.QueueToMainThreadAndWait(() =>
|
||||
{
|
||||
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).ForEach(x => ScriptDomain.CurrentDomain.StartScripts(x));
|
||||
SetupScripts();
|
||||
});
|
||||
});
|
||||
}
|
||||
public void Unload()
|
||||
{
|
||||
|
||||
StopScripts();
|
||||
LoadedResources.Clear();
|
||||
Loader.LoaderContext.RequestUnload();
|
||||
}
|
||||
|
||||
private void Unpack(string zipPath, string dataFolderRoot)
|
||||
{
|
||||
var r = new ClientResource()
|
||||
{
|
||||
Logger = API.Logger,
|
||||
Scripts = new List<ClientScript>(),
|
||||
Name = Path.GetFileNameWithoutExtension(zipPath),
|
||||
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
|
||||
ScriptsDirectory = Path.Combine(Directory.GetParent(zipPath).FullName, Path.GetFileNameWithoutExtension(zipPath))
|
||||
};
|
||||
Directory.CreateDirectory(r.DataFolder);
|
||||
var scriptsDir = r.ScriptsDirectory;
|
||||
if (Directory.Exists(scriptsDir)) { Directory.Delete(scriptsDir, true); }
|
||||
else if (File.Exists(scriptsDir)) { File.Delete(scriptsDir); }
|
||||
Directory.CreateDirectory(scriptsDir);
|
||||
|
||||
new FastZip().ExtractZip(zipPath, scriptsDir, null);
|
||||
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
r.Files.Add(dir, new ResourceFile()
|
||||
{
|
||||
IsDirectory = true,
|
||||
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
|
||||
});
|
||||
}
|
||||
var assemblies = new Dictionary<ResourceFile, Assembly>();
|
||||
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (Path.GetFileName(file).CanBeIgnored())
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.Logger.Warning($"Failed to delete API assembly: {file}. This may or may cause some unexpected behaviours.\n{ex}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
|
||||
var rfile = new ResourceFile()
|
||||
{
|
||||
GetStream = () => { return new FileStream(file, FileMode.Open, FileAccess.Read); },
|
||||
IsDirectory = false,
|
||||
Name = relativeName
|
||||
};
|
||||
r.Files.Add(relativeName, rfile);
|
||||
}
|
||||
|
||||
LoadedResources.TryAdd(r.Name, r);
|
||||
}
|
||||
|
||||
private void SetupScripts()
|
||||
{
|
||||
foreach (var s in GetClientScripts())
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
API.Logger.Debug("Starting script: " + s.GetType().FullName);
|
||||
var script = (ClientScript)s;
|
||||
if (LoadedResources.TryGetValue(Directory.GetParent(script.Filename).Name, out var r))
|
||||
{
|
||||
script.CurrentResource = r;
|
||||
}
|
||||
else
|
||||
{
|
||||
API.Logger.Warning("Failed to locate resource for script: " + script.Filename);
|
||||
}
|
||||
var res = script.CurrentResource;
|
||||
script.CurrentFile = res?.Files.Values.Where(x => x.Name.ToLower() == script.Filename.Substring(res.ScriptsDirectory.Length + 1).Replace('\\', '/')).FirstOrDefault();
|
||||
res?.Scripts.Add(script);
|
||||
script.OnStart();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.Logger.Error($"Failed to start {s.GetType().FullName}", ex);
|
||||
}
|
||||
|
||||
API.Logger.Debug("Started script: " + s.GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopScripts()
|
||||
{
|
||||
foreach (var s in GetClientScripts())
|
||||
{
|
||||
try
|
||||
{
|
||||
API.Logger.Debug("Stopping script: " + s.GetType().FullName);
|
||||
((ClientScript)s).OnStop();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.Logger.Error($"Failed to stop {s.GetType().FullName}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static object[] GetClientScripts()
|
||||
{
|
||||
return ScriptDomain.CurrentDomain.RunningScripts.Where(x =>
|
||||
x.ScriptInstance.GetType().IsScript(typeof(ClientScript))).Select(x => x.ScriptInstance).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -36,7 +36,7 @@ namespace RageCoop.Client
|
||||
/// LogLevel for RageCoop.
|
||||
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
|
||||
/// </summary>
|
||||
public int LogLevel = 00;
|
||||
public int LogLevel = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The key to open menu
|
||||
@ -70,7 +70,7 @@ namespace RageCoop.Client
|
||||
/// <summary>
|
||||
/// The directory where log and resources downloaded from server will be placed.
|
||||
/// </summary>
|
||||
public string DataDirectory { get; set; } = "Scripts\\RageCoop\\Data";
|
||||
public string DataDirectory { get; set; } = "RageCoop\\Data";
|
||||
|
||||
/// <summary>
|
||||
/// Show the owner name of the entity you're aiming at
|
@ -40,8 +40,10 @@ namespace RageCoop.Client
|
||||
var flag = AnimationFlags.Loop;
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
|
||||
{
|
||||
if (LoadAnim(animDict) == null) { return; }
|
||||
|
||||
MainPed.Task.ClearAll();
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim(animDict), ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,18 +145,11 @@ namespace RageCoop.Client
|
||||
|
||||
private string LoadAnim(string anim)
|
||||
{
|
||||
ulong startTime = Util.GetTickCount64();
|
||||
|
||||
while (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
|
||||
if (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
|
||||
{
|
||||
Script.Yield();
|
||||
Function.Call(Hash.REQUEST_ANIM_DICT, anim);
|
||||
if (Util.GetTickCount64() - startTime >= 1000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
}
|
@ -283,7 +283,10 @@ namespace RageCoop.Client
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
|
||||
{
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, LoadAnim("skydive@base"), "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
// Skip update if animation is not loaded
|
||||
var dict = LoadAnim("skydive@base");
|
||||
if (dict == null) { return; }
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -312,7 +315,9 @@ namespace RageCoop.Client
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3))
|
||||
{
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim("skydive@parachute@first_person"), "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
var dict = LoadAnim("skydive@parachute@first_person");
|
||||
if (dict == null) { return; }
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
|
||||
return;
|
@ -27,6 +27,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
|
||||
if (!NeedUpdate) { return; }
|
||||
if (!Model.IsLoaded) { Model.Request(); return; }
|
||||
if (MainProp == null || !MainProp.Exists())
|
||||
{
|
||||
MainProp = World.CreateProp(Model, Position, Rotation, false, false);
|
@ -113,7 +113,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (MainVehicle.IsDead)
|
||||
{
|
||||
Main.Delay(() =>
|
||||
WorldThread.Delay(() =>
|
||||
{
|
||||
if (MainVehicle.IsDead && !IsDead)
|
||||
{
|
@ -8,6 +8,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
internal static class SyncEvents
|
||||
{
|
||||
|
||||
#region TRIGGER
|
||||
public static void TriggerPedKilled(SyncedPed victim)
|
||||
{
|
||||
@ -256,7 +257,7 @@ namespace RageCoop.Client
|
||||
|
||||
if (!getBulletImpact())
|
||||
{
|
||||
Main.QueueAction(getBulletImpact);
|
||||
Scripting.API.QueueAction(getBulletImpact);
|
||||
}
|
||||
}
|
||||
else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default)
|
@ -1,9 +1,4 @@
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
@ -1,21 +1,32 @@
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Util
|
||||
{
|
||||
public static void StartUpCheck()
|
||||
{
|
||||
if (AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext") == null)
|
||||
{
|
||||
var error = $"Client not loaded with loader, please re-install using the installer to fix this issue";
|
||||
try
|
||||
{
|
||||
GTA.UI.Notification.Show("~r~" + error);
|
||||
}
|
||||
catch { }
|
||||
throw new Exception(error);
|
||||
}
|
||||
}
|
||||
public static SizeF ResolutionMaintainRatio
|
||||
{
|
||||
get
|
||||
@ -110,28 +121,21 @@ namespace RageCoop.Client
|
||||
|
||||
|
||||
#endregion
|
||||
public static string SettingsPath = "Scripts\\RageCoop\\Data\\RageCoop.Client.Settings.xml";
|
||||
public static string SettingsPath = "RageCoop\\Settings.json";
|
||||
public static Settings ReadSettings(string path = null)
|
||||
{
|
||||
path = path ?? SettingsPath;
|
||||
XmlSerializer ser = new XmlSerializer(typeof(Settings));
|
||||
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
Settings settings = null;
|
||||
|
||||
if (File.Exists(path))
|
||||
Settings settings;
|
||||
try
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(path))
|
||||
{
|
||||
settings = (Settings)ser.Deserialize(stream);
|
||||
}
|
||||
settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
using (FileStream stream = File.OpenWrite(path))
|
||||
{
|
||||
ser.Serialize(stream, settings = new Settings());
|
||||
}
|
||||
Main.Logger?.Error(ex);
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented));
|
||||
}
|
||||
|
||||
return settings;
|
||||
@ -144,17 +148,13 @@ namespace RageCoop.Client
|
||||
settings = settings ?? Main.Settings;
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
|
||||
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
XmlSerializer ser = new XmlSerializer(typeof(Settings));
|
||||
ser.Serialize(stream, settings);
|
||||
}
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger?.Error(ex);
|
||||
return false;
|
||||
// GTA.UI.Notification.Show("Error saving player settings: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,63 +214,8 @@ namespace RageCoop.Client
|
||||
Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index);
|
||||
}
|
||||
|
||||
#region WIN32
|
||||
|
||||
private const UInt32 WM_KEYDOWN = 0x0100;
|
||||
public static void Reload()
|
||||
{
|
||||
string reloadKey = "None";
|
||||
var lines = File.ReadAllLines("ScriptHookVDotNet.ini");
|
||||
foreach (var l in lines)
|
||||
{
|
||||
var ss = l.Split('=');
|
||||
if (ss.Length > 0 && ss[0] == "ReloadKey")
|
||||
{
|
||||
reloadKey = ss[1];
|
||||
}
|
||||
}
|
||||
var lineList = lines.ToList();
|
||||
if (reloadKey == "None")
|
||||
{
|
||||
foreach (var l in lines)
|
||||
{
|
||||
var ss = l.Split('=');
|
||||
ss.ForEach(s => s.Replace(" ", ""));
|
||||
if (ss.Length > 0 && ss[0] == "ReloadKey")
|
||||
{
|
||||
reloadKey = ss[1];
|
||||
lineList.Remove(l);
|
||||
}
|
||||
}
|
||||
lineList.Add("ReloadKey=Insert");
|
||||
File.WriteAllLines("ScriptHookVDotNet.ini", lineList.ToArray());
|
||||
GTA.UI.Notification.Show("Reload cannot be performed automatically, please type \"Reload()\" manually in the SHVDN console.");
|
||||
}
|
||||
Keys key = (Keys)Enum.Parse(typeof(Keys), reloadKey, true);
|
||||
|
||||
// Move log file so it doesn't get deleted
|
||||
Main.Logger.Dispose();
|
||||
|
||||
var path = Main.Logger.LogPath + ".last.log";
|
||||
try
|
||||
{
|
||||
if (File.Exists(path)) { File.Delete(path); }
|
||||
if (File.Exists(Main.Logger.LogPath)) { File.Move(Main.Logger.LogPath, path); }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GTA.UI.Notification.Show(ex.Message);
|
||||
}
|
||||
|
||||
PostMessage(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, WM_KEYDOWN, (int)key, 0);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
|
||||
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern ulong GetTickCount64();
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,20 +1,27 @@
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public class WorldThread : Script
|
||||
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
internal class WorldThread : Script
|
||||
{
|
||||
|
||||
public static Script Instance;
|
||||
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public WorldThread()
|
||||
{
|
||||
Util.StartUpCheck();
|
||||
Instance = this;
|
||||
Tick += OnTick;
|
||||
Aborted += (sender, e) =>
|
||||
{
|
||||
@ -25,6 +32,7 @@ namespace RageCoop.Client
|
||||
private static bool _trafficEnabled;
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
DoQueuedActions();
|
||||
if (Game.IsLoading || !Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
@ -144,5 +152,61 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Delay(Action a, int time)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
QueueAction(a);
|
||||
});
|
||||
}
|
||||
internal static void DoQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
foreach (var action in QueuedActions.ToArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (action())
|
||||
{
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
|
||||
internal static void QueueAction(Func<bool> a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(a);
|
||||
}
|
||||
}
|
||||
internal static void QueueAction(Action a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(() => { a(); return true; });
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears all queued actions
|
||||
/// </summary>
|
||||
internal static void ClearQueuedActions()
|
||||
{
|
||||
lock (QueuedActions) { QueuedActions.Clear(); }
|
||||
}
|
||||
}
|
||||
}
|
11
Client/Scripts/app.config
Normal file
11
Client/Scripts/app.config
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
@ -1,21 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Costura.Fody" version="5.7.0" targetFramework="net48" developmentDependency="true" />
|
||||
<package id="Fody" version="6.6.3" targetFramework="net48" developmentDependency="true" />
|
||||
<package id="Microsoft.Extensions.ObjectPool" version="6.0.8" targetFramework="net48" />
|
||||
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net48" />
|
||||
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" />
|
||||
<package id="NAudio" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio.Asio" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio.Core" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio.Midi" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net481" />
|
||||
<package id="NAudio" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NAudio.Asio" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NAudio.Core" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NAudio.Midi" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net48" />
|
||||
<package id="NETStandard.Library" version="1.6.1" targetFramework="net48" />
|
||||
<package id="SharpZipLib" version="1.3.3" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
|
||||
<package id="SharpZipLib" version="1.4.0" targetFramework="net48" />
|
||||
<package id="System.AppContext" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Console" version="4.3.0" targetFramework="net48" />
|
||||
@ -32,15 +32,18 @@
|
||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Memory" version="4.5.4" targetFramework="net48" />
|
||||
<package id="System.Net.Http" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Net.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
|
||||
<package id="System.ObjectModel" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Reflection" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net48" />
|
||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.Handles" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net48" />
|
||||
@ -57,6 +60,7 @@
|
||||
<package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Threading" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.5.2" targetFramework="net48" />
|
||||
<package id="System.Threading.Timer" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" />
|
@ -1,6 +1,7 @@
|
||||
using GTA.Math;
|
||||
using Lidgren.Network;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -9,6 +10,7 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
@ -24,15 +26,49 @@ namespace RageCoop.Core
|
||||
{
|
||||
private static readonly HashSet<string> ToIgnore = new HashSet<string>()
|
||||
{
|
||||
"RageCoop.Client.dll",
|
||||
"RageCoop.Core.dll",
|
||||
"RageCoop.Server.dll",
|
||||
"ScriptHookVDotNet3.dll",
|
||||
"ScriptHookVDotNet.dll"
|
||||
"RageCoop.Client",
|
||||
"RageCoop.Client.Loader",
|
||||
"RageCoop.Client.Installer",
|
||||
"RageCoop.Core",
|
||||
"RageCoop.Server",
|
||||
"ScriptHookVDotNet2",
|
||||
"ScriptHookVDotNet3",
|
||||
"ScriptHookVDotNet"
|
||||
};
|
||||
public static void GetDependencies(Assembly assembly, ref HashSet<string> existing)
|
||||
{
|
||||
if (assembly.FullName.StartsWith("System")) { return; }
|
||||
foreach(var name in assembly.GetReferencedAssemblies())
|
||||
{
|
||||
if (name.FullName.StartsWith("System")) { continue; }
|
||||
try
|
||||
{
|
||||
var asm = Assembly.Load(name);
|
||||
GetDependencies(asm,ref existing);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
if (!existing.Contains(assembly.FullName))
|
||||
{
|
||||
Console.WriteLine(assembly.FullName);
|
||||
existing.Add(assembly.FullName);
|
||||
}
|
||||
}
|
||||
public static Version GetLatestVersion(string branch = "dev-nightly")
|
||||
{
|
||||
var url = $"https://raw.githubusercontent.com/RAGECOOP/RAGECOOP-V/{branch}/RageCoop.Server/Properties/AssemblyInfo.cs";
|
||||
var versionLine = HttpHelper.DownloadString(url).Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.Contains("[assembly: AssemblyVersion(")).First();
|
||||
var start = versionLine.IndexOf('\"') + 1;
|
||||
var end = versionLine.LastIndexOf('\"');
|
||||
return Version.Parse(versionLine.Substring(start, end - start));
|
||||
}
|
||||
public static bool CanBeIgnored(this string name)
|
||||
{
|
||||
return ToIgnore.Contains(name);
|
||||
return ToIgnore.Contains(Path.GetFileNameWithoutExtension(name));
|
||||
}
|
||||
public static string ToFullPath(this string path)
|
||||
{
|
||||
return Path.GetFullPath(path);
|
||||
}
|
||||
public static void GetBytesFromObject(object obj, NetOutgoingMessage m)
|
||||
{
|
||||
@ -225,6 +261,11 @@ namespace RageCoop.Core
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public static StreamWriter OpenWriter(string path, FileMode mode = FileMode.Create, FileAccess access = FileAccess.Write, FileShare share = FileShare.ReadWrite)
|
||||
{
|
||||
return new StreamWriter(File.Open(path, mode, access, share));
|
||||
}
|
||||
}
|
||||
internal class IpInfo
|
||||
{
|
||||
@ -272,22 +313,27 @@ namespace RageCoop.Core
|
||||
p.Deserialize(msg);
|
||||
return p;
|
||||
}
|
||||
public static bool HasPedFlag(this PedDataFlags flagToCheck, PedDataFlags flag)
|
||||
public static bool HasPedFlag(this PedDataFlags flags, PedDataFlags flag)
|
||||
{
|
||||
return (flagToCheck & flag) != 0;
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
public static bool HasProjDataFlag(this ProjectileDataFlags flagToCheck, ProjectileDataFlags flag)
|
||||
public static bool HasProjDataFlag(this ProjectileDataFlags flags, ProjectileDataFlags flag)
|
||||
{
|
||||
return (flagToCheck & flag) != 0;
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
public static bool HasVehFlag(this VehicleDataFlags flagToCheck, VehicleDataFlags flag)
|
||||
public static bool HasVehFlag(this VehicleDataFlags flags, VehicleDataFlags flag)
|
||||
{
|
||||
return (flagToCheck & flag) != 0;
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
public static bool HasConfigFlag(this PlayerConfigFlags flagToCheck, PlayerConfigFlags flag)
|
||||
public static bool HasConfigFlag(this PlayerConfigFlags flags, PlayerConfigFlags flag)
|
||||
{
|
||||
return (flagToCheck & flag) != 0;
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
public static bool HasEventFlag(this CustomEventFlags flags, CustomEventFlags flag)
|
||||
{
|
||||
return (flags & flag) != 0;
|
||||
|
||||
}
|
||||
public static Type GetActualType(this TypeCode code)
|
||||
{
|
||||
@ -401,13 +447,11 @@ namespace RageCoop.Core
|
||||
return output;
|
||||
}
|
||||
|
||||
public static bool IsSubclassOf(this Type type, string baseTypeName)
|
||||
public static bool IsScript(this Type type, Type scriptType)
|
||||
{
|
||||
for (Type t = type.BaseType; t != null; t = t.BaseType)
|
||||
if (t.FullName == baseTypeName)
|
||||
return true;
|
||||
return false;
|
||||
return !type.IsAbstract && type.IsSubclassOf(scriptType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
176
Core/Logger.cs
Normal file
176
Core/Logger.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace RageCoop.Core
|
||||
{
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warning = 3,
|
||||
Error = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Logger : MarshalByRefObject, IDisposable
|
||||
{
|
||||
public class LogLine
|
||||
{
|
||||
internal LogLine() { }
|
||||
public DateTime TimeStamp;
|
||||
public LogLevel LogLevel;
|
||||
public string Message;
|
||||
}
|
||||
/// <summary>
|
||||
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
|
||||
/// </summary>
|
||||
public int LogLevel = 0;
|
||||
/// <summary>
|
||||
/// Name of this logger
|
||||
/// </summary>
|
||||
public string Name = "Logger";
|
||||
public readonly string DateTimeFormat = "HH:mm:ss";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use UTC time for timestamping the log
|
||||
/// </summary>
|
||||
public readonly bool UseUtc = false;
|
||||
public List<StreamWriter> Writers = new List<StreamWriter> { new StreamWriter(Console.OpenStandardOutput()) };
|
||||
public int FlushInterval = 1000;
|
||||
public event FlushDelegate OnFlush;
|
||||
public bool FlushImmediately = false;
|
||||
public delegate void FlushDelegate(LogLine line, string fomatted);
|
||||
|
||||
private readonly Thread LoggerThread;
|
||||
private bool Stopping = false;
|
||||
private readonly ConcurrentQueue<LogLine> _queuedLines = new ConcurrentQueue<LogLine>();
|
||||
internal Logger()
|
||||
{
|
||||
Name = Process.GetCurrentProcess().Id.ToString();
|
||||
if (!FlushImmediately)
|
||||
{
|
||||
LoggerThread = new Thread(() =>
|
||||
{
|
||||
while (!Stopping)
|
||||
{
|
||||
Flush();
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
Flush();
|
||||
});
|
||||
LoggerThread.Start();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void Info(string message)
|
||||
{
|
||||
Enqueue(2, message);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void Warning(string message)
|
||||
{
|
||||
Enqueue(3, message);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void Error(string message)
|
||||
{
|
||||
Enqueue(4, message);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="error"></param>
|
||||
public void Error(string message, Exception error)
|
||||
{
|
||||
Enqueue(4, $"{message}:\n {error}");
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ex"></param>
|
||||
public void Error(Exception ex)
|
||||
{
|
||||
Enqueue(4, ex.ToString());
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void Debug(string message)
|
||||
{
|
||||
Enqueue(1, message);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void Trace(string message)
|
||||
{
|
||||
Enqueue(0, message);
|
||||
}
|
||||
public void Enqueue(int level, string message)
|
||||
{
|
||||
if (level < LogLevel) { return; }
|
||||
_queuedLines.Enqueue(new LogLine()
|
||||
{
|
||||
Message = message,
|
||||
TimeStamp = UseUtc ? DateTime.UtcNow : DateTime.Now,
|
||||
LogLevel = (LogLevel)level
|
||||
});
|
||||
if (FlushImmediately)
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private string Format(LogLine line)
|
||||
{
|
||||
return string.Format("[{0}][{2}] [{3}] {1}", line.TimeStamp.ToString(DateTimeFormat), line.Message, Name, line.LogLevel.ToString());
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void Flush()
|
||||
{
|
||||
lock (_queuedLines)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_queuedLines.TryDequeue(out var line))
|
||||
{
|
||||
var formatted = Format(line);
|
||||
Writers.ForEach(x => { x.WriteLine(formatted); x.Flush(); });
|
||||
OnFlush?.Invoke(line, formatted);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Stop backdround thread and flush all pending messages.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Stopping = true;
|
||||
LoggerThread?.Join();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
namespace RageCoop.Core
|
||||
using System;
|
||||
|
||||
namespace RageCoop.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A json object representing a server's information as annouced to master server.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ServerInfo
|
||||
{
|
||||
#pragma warning disable 1591
|
@ -1,8 +1,5 @@
|
||||
using GTA.Math;
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace RageCoop.Core
|
||||
{
|
@ -1,4 +1,5 @@
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
namespace RageCoop.Core
|
||||
{
|
||||
@ -7,21 +8,20 @@ namespace RageCoop.Core
|
||||
|
||||
internal class CustomEvent : Packet
|
||||
{
|
||||
public override PacketType Type => (_queued ? PacketType.CustomEventQueued : PacketType.CustomEvent);
|
||||
public CustomEvent(Func<byte, NetIncomingMessage, object> onResolve = null, bool queued = false)
|
||||
public static Func<byte, NetIncomingMessage, object> ResolveHandle = null;
|
||||
public CustomEventFlags Flags;
|
||||
public override PacketType Type => PacketType.CustomEvent;
|
||||
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
|
||||
{
|
||||
_resolve = onResolve;
|
||||
_queued = queued;
|
||||
Flags = flags;
|
||||
}
|
||||
private readonly bool _queued;
|
||||
private Func<byte, NetIncomingMessage, object> _resolve { get; set; }
|
||||
public int Hash { get; set; }
|
||||
public object[] Args { get; set; }
|
||||
|
||||
protected override void Serialize(NetOutgoingMessage m)
|
||||
{
|
||||
Args = Args ?? new object[] { };
|
||||
|
||||
m.Write((byte)Flags);
|
||||
m.Write(Hash);
|
||||
m.Write(Args.Length);
|
||||
foreach (var arg in Args)
|
||||
@ -33,11 +33,10 @@ namespace RageCoop.Core
|
||||
public override void Deserialize(NetIncomingMessage m)
|
||||
{
|
||||
|
||||
|
||||
Flags = (CustomEventFlags)m.ReadByte();
|
||||
Hash = m.ReadInt32();
|
||||
var len = m.ReadInt32();
|
||||
Args = new object[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
Args = new object[m.ReadInt32()];
|
||||
for (int i = 0; i < Args.Length; i++)
|
||||
{
|
||||
byte type = m.ReadByte();
|
||||
switch (type)
|
||||
@ -73,13 +72,13 @@ namespace RageCoop.Core
|
||||
case 0x15:
|
||||
Args[i] = m.ReadByteArray(); break;
|
||||
default:
|
||||
if (_resolve == null)
|
||||
if (ResolveHandle == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Unexpected type: {type}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Args[i] = _resolve(type, m); break;
|
||||
Args[i] = ResolveHandle(type, m); break;
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ namespace RageCoop.Core
|
||||
AllResourcesSent = 15,
|
||||
|
||||
CustomEvent = 16,
|
||||
CustomEventQueued = 17,
|
||||
|
||||
ConnectionRequest = 18,
|
||||
P2PConnect = 19,
|
@ -24,6 +24,14 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<NoWarn>1701;1702;1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
|
||||
<NoWarn>1701;1702;1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Costura.Fody" Version="5.7.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@ -34,7 +42,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.0" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -42,9 +51,6 @@
|
||||
<Reference Include="Lidgren.Network">
|
||||
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ScriptHookVDotNet3">
|
||||
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
|
||||
</Reference>
|
124
Core/Scripting/CustomEvents.cs
Normal file
124
Core/Scripting/CustomEvents.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace RageCoop.Core.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes how the event should be sent or processed
|
||||
/// </summary>
|
||||
public enum CustomEventFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Data will be encrypted and decrypted on target client
|
||||
/// </summary>
|
||||
Encrypted = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Event will be queued and fired in script thread, specify this flag if your handler will call native functions.
|
||||
/// </summary>
|
||||
Queued = 2,
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct to identify different event using hash
|
||||
/// </summary>
|
||||
public struct CustomEventHash
|
||||
{
|
||||
private static readonly MD5 Hasher = MD5.Create();
|
||||
private static readonly Dictionary<int, string> Hashed = new Dictionary<int, string>();
|
||||
/// <summary>
|
||||
/// Hash value
|
||||
/// </summary>
|
||||
public int Hash;
|
||||
/// <summary>
|
||||
/// Create from hash
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
public static implicit operator CustomEventHash(int hash)
|
||||
{
|
||||
return new CustomEventHash() { Hash = hash };
|
||||
}
|
||||
/// <summary>
|
||||
/// Create from string
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
public static implicit operator CustomEventHash(string name)
|
||||
{
|
||||
return new CustomEventHash() { Hash = FromString(name) };
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Get a Int32 hash of a string.
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException">The exception is thrown when the name did not match a previously computed one and the hash was the same.</exception>
|
||||
public static int FromString(string s)
|
||||
{
|
||||
var hash = BitConverter.ToInt32(Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)), 0);
|
||||
lock (Hashed)
|
||||
{
|
||||
if (Hashed.TryGetValue(hash, out string name))
|
||||
{
|
||||
if (name != s)
|
||||
{
|
||||
throw new ArgumentException($"Hashed value has collision with another name:{name}, hashed value:{hash}");
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
Hashed.Add(hash, s);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// To int
|
||||
/// </summary>
|
||||
/// <param name="h"></param>
|
||||
public static implicit operator int(CustomEventHash h)
|
||||
{
|
||||
return h.Hash;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class CustomEvents
|
||||
{
|
||||
internal static readonly CustomEventHash OnPlayerDied = "RageCoop.OnPlayerDied";
|
||||
internal static readonly CustomEventHash SetWeather = "RageCoop.SetWeather";
|
||||
internal static readonly CustomEventHash OnPedDeleted = "RageCoop.OnPedDeleted";
|
||||
internal static readonly CustomEventHash OnVehicleDeleted = "RageCoop.OnVehicleDeleted";
|
||||
internal static readonly CustomEventHash SetAutoRespawn = "RageCoop.SetAutoRespawn";
|
||||
internal static readonly CustomEventHash SetDisplayNameTag = "RageCoop.SetDisplayNameTag";
|
||||
internal static readonly CustomEventHash NativeCall = "RageCoop.NativeCall";
|
||||
internal static readonly CustomEventHash NativeResponse = "RageCoop.NativeResponse";
|
||||
internal static readonly CustomEventHash AllResourcesSent = "RageCoop.AllResourcesSent";
|
||||
internal static readonly CustomEventHash ServerPropSync = "RageCoop.ServerPropSync";
|
||||
internal static readonly CustomEventHash ServerBlipSync = "RageCoop.ServerBlipSync";
|
||||
internal static readonly CustomEventHash SetEntity = "RageCoop.SetEntity";
|
||||
internal static readonly CustomEventHash DeleteServerProp = "RageCoop.DeleteServerProp";
|
||||
internal static readonly CustomEventHash UpdatePedBlip = "RageCoop.UpdatePedBlip";
|
||||
internal static readonly CustomEventHash DeleteEntity = "RageCoop.DeleteEntity";
|
||||
internal static readonly CustomEventHash DeleteServerBlip = "RageCoop.DeleteServerBlip";
|
||||
internal static readonly CustomEventHash CreateVehicle = "RageCoop.CreateVehicle";
|
||||
internal static readonly CustomEventHash WeatherTimeSync = "RageCoop.WeatherTimeSync";
|
||||
internal static readonly CustomEventHash IsHost = "RageCoop.IsHost";
|
||||
/// <summary>
|
||||
/// Get event hash from string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>This method is obsoete, you should use implicit operator or <see cref="CustomEventHash.FromString(string)"/></remarks>
|
||||
[Obsolete]
|
||||
public static int Hash(string s)
|
||||
{
|
||||
return CustomEventHash.FromString(s);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,13 +3,17 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31919.166
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "RageCoop.Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "RageCoop.Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "RageCoop.Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "Client\Scripts\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "RageCoop.Client.Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "Client\Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client.Loader", "Client\Loader\RageCoop.Client.Loader.csproj", "{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{531656CF-7269-488D-B042-741BC96C3941}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -51,10 +55,23 @@ Global
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681} = {531656CF-7269-488D-B042-741BC96C3941}
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B} = {531656CF-7269-488D-B042-741BC96C3941}
|
||||
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5} = {531656CF-7269-488D-B042-741BC96C3941}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
|
||||
EndGlobalSection
|
||||
|
@ -1,36 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="bg.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RageCoop.Client\RageCoop.Client.csproj" />
|
||||
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="bg.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user