19 Commits

Author SHA1 Message Date
4e6bab1b53 blah 2022-10-15 18:14:35 +08:00
64692bc161 Update 2022-10-15 17:16:47 +08:00
8d27c072ca Change package format to iso9660 2022-10-15 17:06:19 +08:00
d7c0abdfc2 Bump version 2022-10-15 15:54:46 +08:00
d0b6bbaa3a Update installer 2022-10-15 15:30:58 +08:00
411b199a98 Unload fix and small tweaks 2022-10-15 13:52:49 +08:00
b48b15b652 Tidy up 2022-10-15 12:15:19 +08:00
e83480b7f9 Update action 2022-10-15 11:57:26 +08:00
d1b4f23992 Restructure solution 2022-10-15 11:51:18 +08:00
42c0ef2159 Load in client in seperate domain
Rewrote logger for more flexible options
Complete client resource loading
2022-10-15 11:09:17 +08:00
8701ac703e blah 2022-10-10 16:45:58 +08:00
fe53e01a4a ClientScript loading logic 2022-10-09 23:35:30 +08:00
617dbc9812 Change settings and data directory 2022-10-09 22:07:52 +08:00
1606d25fed Queue action in WorldThread 2022-10-09 11:15:09 +08:00
5585876005 API remoting 2022-10-08 23:49:48 +08:00
54c7f4ce92 ResourceDomain 2022-10-08 12:43:24 +08:00
a062322dda Fix queued events 2022-10-05 18:10:56 +08:00
4599c558e4 Add back Hash method for compatabilit 2022-10-05 16:09:43 +08:00
71f7f4b257 Use implicit operator for CustomEventHash, add CustomEventFlags
Resource rebuild will be required, but no code change is needed
2022-10-05 15:53:57 +08:00
149 changed files with 42937 additions and 1763 deletions

View File

@ -31,9 +31,9 @@ jobs:
- name: Restore nuget packages - name: Restore nuget packages
run: nuget restore run: nuget restore
- name: Build client and installer - 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 - 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 - name: Upload server
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:

View File

@ -28,6 +28,8 @@ jobs:
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release
- name: Build server linux-x64 - name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release
- name: Build server linux-arm
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o bin/Release/Server/linux-arm -c Release
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: bin/Release/Client files: bin/Release/Client
@ -43,6 +45,11 @@ jobs:
files: bin/Release/Server/linux-x64 files: bin/Release/Server/linux-x64
dest: RageCoop.Server-linux-x64.zip dest: RageCoop.Server-linux-x64.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm
dest: RageCoop.Server-linux-arm.zip
- uses: WebFreak001/deploy-nightly@v1.1.0 - uses: WebFreak001/deploy-nightly@v1.1.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
@ -75,3 +82,17 @@ jobs:
asset_name: RageCoop.Server-linux-x64.zip asset_name: RageCoop.Server-linux-x64.zip
asset_content_type: application/zip asset_content_type: application/zip
max_releases: 7 max_releases: 7
- uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
release_id: 70603992
asset_path: RageCoop.Server-linux-arm.zip
asset_name: RageCoop.Server-linux-arm.zip
asset_content_type: application/zip
max_releases: 7
- uses: actions/checkout@v2

View File

@ -1,4 +1,5 @@
using System; using RageCoop.Core;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -11,7 +12,6 @@ using System.Windows.Input;
using MessageBox = System.Windows.MessageBox; using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog; using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using Path = System.IO.Path; using Path = System.IO.Path;
using RageCoop.Core;
namespace RageCoop.Client.Installer namespace RageCoop.Client.Installer
{ {
@ -58,64 +58,53 @@ namespace RageCoop.Client.Installer
{ {
UpdateStatus("Checking requirements"); UpdateStatus("Checking requirements");
var shvPath = Path.Combine(root, "ScriptHookV.dll"); var shvPath = Path.Combine(root, "ScriptHookV.dll");
var shvdnPath = Path.Combine(root, "ScriptHookVDotNet3.dll");
var scriptsPath = Path.Combine(root, "Scripts"); var scriptsPath = Path.Combine(root, "Scripts");
var lemonPath = Path.Combine(scriptsPath, "LemonUI.SHVDN3.dll"); var installPath = Path.Combine(root, "RageCoop", "Scripts");
var installPath = Path.Combine(scriptsPath, "RageCoop"); var legacyPath = Path.Combine(scriptsPath, "RageCoop");
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName == installPath) 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."); 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)) if (!File.Exists(shvPath))
{ {
MessageBox.Show("Please install ScriptHookV first!"); MessageBox.Show("Please install ScriptHookV first!");
Environment.Exit(1); Environment.Exit(1);
} }
if (!File.Exists(shvdnPath))
{ Directory.CreateDirectory(installPath);
MessageBox.Show("Please install ScriptHookVDotNet first!");
Environment.Exit(1); File.Copy("ScriptHookVDotNet.dll", Path.Combine(root, "ScriptHookVDotNet.asi"), true);
} File.Copy("ScriptHookVDotNet3.dll", Path.Combine(root, "ScriptHookVDotNet3.dll"), true);
var shvdnVer = GetVer(shvdnPath);
if (shvdnVer < new Version(3, 6, 0))
{
MessageBox.Show("Please update ScriptHookVDotNet to latest version!" +
$"\nCurrent version is {shvdnVer}, 3.6.0 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());
}
}
UpdateStatus("Removing old versions"); UpdateStatus("Removing old versions");
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories)) foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
{ {
if (f.EndsWith("RageCoop.Client.Settings.xml")) { continue; }
File.Delete(f); 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)) foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories))
{ {
File.Delete(f); 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..."); UpdateStatus("Installing...");
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath)); CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath));
File.Copy("RageCoop.Client.Loader.dll", Path.Combine(scriptsPath, "RageCoop.Client.Loader.dll"), true);
Finish(); Finish();
} }
else 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() void Finish()
@ -124,7 +113,7 @@ namespace RageCoop.Client.Installer
checkKeys: checkKeys:
UpdateStatus("Checking conflicts"); UpdateStatus("Checking conflicts");
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini"); 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; Settings settings = null;
try try
{ {
@ -199,15 +188,5 @@ namespace RageCoop.Client.Installer
Dispatcher.BeginInvoke(new Action(() => Status.Content = status)); 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");
}
} }
} }

View 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>

View 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>

View File

@ -59,15 +59,5 @@ namespace RageCoop.Client.Installer {
resourceCulture = value; 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));
}
}
} }
} }

View File

@ -118,7 +118,4 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <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> </root>

View File

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

View 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
View 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());
}
}
}
}
}

View 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")]

View 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>

View File

@ -7,6 +7,7 @@ using System.Windows.Forms;
namespace RageCoop.Client namespace RageCoop.Client
{ {
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class DevTool : Script internal class DevTool : Script
{ {
public static Vehicle ToMark; public static Vehicle ToMark;
@ -14,10 +15,14 @@ namespace RageCoop.Client
public static int Current = 0; public static int Current = 0;
public static int Secondary = 0; public static int Secondary = 0;
public static MuzzleDir Direction = MuzzleDir.Forward; public static MuzzleDir Direction = MuzzleDir.Forward;
public static Script Instance;
public DevTool() public DevTool()
{ {
Util.StartUpCheck();
Instance = this;
Tick += OnTick; Tick += OnTick;
KeyDown += OnKeyDown; KeyDown += OnKeyDown;
Pause();
} }
private void OnKeyDown(object sender, KeyEventArgs e) private void OnKeyDown(object sender, KeyEventArgs e)
@ -73,7 +78,7 @@ namespace RageCoop.Client
} }
DevToolMenu.secondaryBoneIndexItem.AltTitle = Secondary.ToString(); 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; } if (ToMark == null || !ToMark.Exists()) { return; }
Update(); Update();

View File

@ -2,24 +2,28 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using SHVDN;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Console = GTA.Console;
using Script = GTA.Script;
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class Main : Script internal class Main : Script
{ {
private bool _gameLoaded = false; private static bool _gameLoaded = false;
internal static Version Version = typeof(Main).Assembly.GetName().Version; internal static Version Version = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0; internal static int LocalPlayerID = 0;
@ -27,48 +31,79 @@ namespace RageCoop.Client
internal static RelationshipGroup SyncedPedsGroup; internal static RelationshipGroup SyncedPedsGroup;
internal static new Settings Settings = null; internal static new Settings Settings = null;
internal static Scripting.BaseScript BaseScript = new Scripting.BaseScript();
#if !NON_INTERACTIVE #if !NON_INTERACTIVE
#endif #endif
internal static Chat MainChat = null; internal static Chat MainChat = null;
internal static Stopwatch Counter = new Stopwatch(); internal static Stopwatch Counter = new Stopwatch();
internal static Logger Logger = null; internal static Logger Logger = null;
internal static ulong Ticked = 0; internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition; internal static Vector3 PlayerPosition;
internal static Scripting.Resources Resources = null; internal static Resources Resources = null;
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>(); private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
public static Worker Worker; public static string LogPath => $"{Settings.DataDirectory}\\RageCoop.Client.log";
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
public Main() 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 try
{ {
Settings = Util.ReadSettings(); 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 catch
{ {
GTA.UI.Notification.Show("Malformed configuration, overwriting with default values..."); // GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
Settings = new Settings(); Settings = new Settings();
Util.SaveSettings(); Util.SaveSettings();
} }
Directory.CreateDirectory(Settings.DataDirectory); Directory.CreateDirectory(Settings.DataDirectory);
Logger = new Logger() Logger = new Logger()
{ {
LogPath = $"{Settings.DataDirectory}\\RageCoop.Client.log", Writers = new List<StreamWriter> { CoreUtils.OpenWriter(LogPath) },
UseConsole = false,
#if DEBUG #if DEBUG
LogLevel = 0, LogLevel = 0,
#else #else
LogLevel=Settings.LogLevel, LogLevel = Settings.LogLevel,
#endif #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) if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{ {
Tick += (object sender, EventArgs e) => Tick += (object sender, EventArgs e) =>
@ -91,21 +126,74 @@ namespace RageCoop.Client
#if !NON_INTERACTIVE #if !NON_INTERACTIVE
#endif #endif
MainChat = new Chat(); MainChat = new Chat();
Aborted += OnAborted;
Tick += OnTick; Tick += OnTick;
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
KeyDown += OnKeyDown; 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(); Util.NativeMemory();
Counter.Restart(); 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 Ped P;
public static float FPS; public static float FPS;
private bool _lastDead; private static bool _lastDead;
private void OnTick(object sender, EventArgs e) private static void OnTick(object sender, EventArgs e)
{ {
P = Game.Player.Character; P = Game.Player.Character;
PlayerPosition = P.ReadPosition(); PlayerPosition = P.ReadPosition();
@ -126,7 +214,6 @@ namespace RageCoop.Client
#endif #endif
DoQueuedActions();
if (!Networking.IsOnServer) if (!Networking.IsOnServer)
{ {
return; return;
@ -135,16 +222,6 @@ namespace RageCoop.Client
{ {
Game.TimeScale = 1; Game.TimeScale = 1;
} }
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
#if DEBUG
Main.Logger.Error(ex);
#endif
}
if (Networking.ShowNetworkInfo) if (Networking.ShowNetworkInfo)
{ {
@ -155,7 +232,7 @@ namespace RageCoop.Client
MainChat.Tick(); MainChat.Tick();
PlayerList.Tick(); PlayerList.Tick();
if (!Scripting.API.Config.EnableAutoRespawn) if (!API.Config.EnableAutoRespawn)
{ {
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true); Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
Function.Call(Hash.IGNORE_NEXT_RESTART, true); Function.Call(Hash.IGNORE_NEXT_RESTART, true);
@ -169,8 +246,8 @@ namespace RageCoop.Client
{ {
P.Health = 1; P.Health = 1;
Game.Player.WantedLevel = 0; Game.Player.WantedLevel = 0;
Main.Logger.Debug("Player died."); Logger.Debug("Player died.");
Scripting.API.Events.InvokePlayerDied(KillMessage()); API.Events.InvokePlayerDied();
} }
GTA.UI.Screen.StopEffects(); GTA.UI.Screen.StopEffects();
} }
@ -181,13 +258,13 @@ namespace RageCoop.Client
} }
else if (P.IsDead && !_lastDead) else if (P.IsDead && !_lastDead)
{ {
Scripting.API.Events.InvokePlayerDied(KillMessage()); API.Events.InvokePlayerDied();
} }
_lastDead = P.IsDead; _lastDead = P.IsDead;
Ticked++; Ticked++;
} }
private void OnKeyDown(object sender, KeyEventArgs e) private static void OnKeyDown(object sender, KeyEventArgs e)
{ {
if (MainChat.Focused) if (MainChat.Focused)
{ {
@ -298,7 +375,7 @@ namespace RageCoop.Client
{ {
Voice.Init(); Voice.Init();
} }
QueueAction(() => API.QueueAction(() =>
{ {
WorldThread.Traffic(!Settings.DisableTraffic); WorldThread.Traffic(!Settings.DisableTraffic);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true); Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
@ -314,7 +391,7 @@ namespace RageCoop.Client
Logger.Info($">> Disconnected << reason: {reason}"); Logger.Info($">> Disconnected << reason: {reason}");
QueueAction(() => API.QueueAction(() =>
{ {
if (MainChat.Focused) if (MainChat.Focused)
{ {
@ -326,83 +403,15 @@ namespace RageCoop.Client
WorldThread.Traffic(true); WorldThread.Traffic(true);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false); Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting(); CoopMenu.DisconnectedMenuSetting();
if (reason != "Abort")
GTA.UI.Notification.Show("~r~Disconnected: " + reason); GTA.UI.Notification.Show("~r~Disconnected: " + reason);
LocalPlayerID = default; LocalPlayerID = default;
Resources.Unload();
}); });
Memory.RestorePatches(); Memory.RestorePatches();
DownloadManager.Cleanup(); DownloadManager.Cleanup();
Voice.ClearAll(); 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)
{
#if DEBUG
Logger.Error(ex);
#endif
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);
});
}
private string KillMessage()
{
if (P.Killer != null)
{
var killer = EntityPool.GetPedByHandle(P.Killer.Handle);
if (killer != null && killer.ID == killer.Owner.ID)
return $"~h~{PlayerList.GetPlayer(LocalPlayerID).Username}~h~ was killed by ~h~{killer.Owner.Username}~h~ ({P.CauseOfDeath})";
}
return $"~h~{PlayerList.GetPlayer(LocalPlayerID).Username}~h~ died";
}
} }
} }

View File

@ -50,7 +50,7 @@ namespace RageCoop.Client.Menus
{ {
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0); Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated; _usernameItem.Activated += UsernameActivated;
_passwordItem.Activated += _passwordActivated; _passwordItem.Activated += _passwordActivated;
@ -67,18 +67,16 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(SettingsMenu.Menu); Menu.AddSubMenu(SettingsMenu.Menu);
Menu.AddSubMenu(DevToolMenu.Menu); Menu.AddSubMenu(DevToolMenu.Menu);
#if DEBUG
Menu.AddSubMenu(DebugMenu.Menu); Menu.AddSubMenu(DebugMenu.Menu);
#endif Menu.AddSubMenu(UpdateMenu.Menu);
MenuPool.Add(Menu); MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu); MenuPool.Add(SettingsMenu.Menu);
MenuPool.Add(DevToolMenu.Menu); MenuPool.Add(DevToolMenu.Menu);
#if DEBUG
MenuPool.Add(DebugMenu.Menu); MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu); MenuPool.Add(DebugMenu.DiagnosticMenu);
#endif
MenuPool.Add(ServersMenu.Menu); MenuPool.Add(ServersMenu.Menu);
MenuPool.Add(UpdateMenu.Menu);
MenuPool.Add(PopUp); MenuPool.Add(PopUp);
Menu.Add(_aboutItem); Menu.Add(_aboutItem);

View File

@ -1,5 +1,4 @@
#if DEBUG using GTA;
using GTA;
using LemonUI.Menus; using LemonUI.Menus;
using System; using System;
using System.Drawing; using System.Drawing;
@ -18,6 +17,7 @@ namespace RageCoop.Client
UseMouse = false, UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left 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 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); 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); private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
@ -25,7 +25,7 @@ namespace RageCoop.Client
static DebugMenu() static DebugMenu()
{ {
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0); Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
DiagnosticMenu.Opening += (sender, e) => DiagnosticMenu.Opening += (sender, e) =>
@ -47,14 +47,18 @@ namespace RageCoop.Client
}; };
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; }; ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); }; ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
ReloadItem.Activated += ReloadDomain;
Menu.Add(SimulatedLatencyItem); Menu.Add(SimulatedLatencyItem);
Menu.Add(ShowNetworkInfoItem); Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem); Menu.Add(ShowOwnerItem);
Menu.Add(ReloadItem);
Menu.AddSubMenu(DiagnosticMenu); Menu.AddSubMenu(DiagnosticMenu);
} }
private static void ReloadDomain(object sender, EventArgs e)
{
Loader.LoaderContext.RequestUnload();
}
} }
} }
#endif

View File

@ -22,7 +22,7 @@ namespace RageCoop.Client
static DevToolMenu() static DevToolMenu()
{ {
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0); Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
enableItem.Activated += enableItem_Activated; enableItem.Activated += enableItem_Activated;
enableItem.Checked = false; enableItem.Checked = false;
@ -73,10 +73,12 @@ namespace RageCoop.Client
{ {
if (enableItem.Checked) if (enableItem.Checked)
{ {
DevTool.Instance.Resume();
DevTool.ToMark = Game.Player.Character.CurrentVehicle; DevTool.ToMark = Game.Player.Character.CurrentVehicle;
} }
else else
{ {
DevTool.Instance.Pause();
DevTool.ToMark = null; DevTool.ToMark = null;
} }
} }

View File

@ -1,6 +1,7 @@
using GTA.UI; using GTA.UI;
using LemonUI.Menus; using LemonUI.Menus;
using Newtonsoft.Json; using Newtonsoft.Json;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -30,7 +31,7 @@ namespace RageCoop.Client.Menus
static ServersMenu() static ServersMenu()
{ {
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0); Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) => Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) =>
{ {
@ -60,7 +61,7 @@ namespace RageCoop.Client.Menus
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl)); serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
// Need to be processed in main thread // Need to be processed in main thread
Main.QueueAction(() => API.QueueAction(() =>
{ {
if (serverList == null) if (serverList == null)
{ {
@ -76,7 +77,7 @@ namespace RageCoop.Client.Menus
foreach (ServerInfo server in serverList) foreach (ServerInfo server in serverList)
{ {
string address = $"{server.address}:{server.port}"; string address = $"{server.address}:{server.port}";
NativeItem tmpItem = new NativeItem($"[{server.country}] {server.name}", $"~b~{address}~s~~n~~g~Version {server.version}~s~") { AltTitle = $"[{server.players}/{server.maxPlayers}]" }; NativeItem tmpItem = new NativeItem($"[{server.country}] {server.name}", $"~b~{address}~s~~n~~g~Version {server.version}.x~s~") { AltTitle = $"[{server.players}/{server.maxPlayers}]" };
tmpItem.Activated += (object sender, EventArgs e) => tmpItem.Activated += (object sender, EventArgs e) =>
{ {
try try
@ -127,7 +128,7 @@ namespace RageCoop.Client.Menus
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.QueueAction(() => API.QueueAction(() =>
{ {
ResultItem.Title = "Download failed!"; ResultItem.Title = "Download failed!";
ResultItem.Description = ex.Message; ResultItem.Description = ex.Message;

View File

@ -17,19 +17,16 @@ namespace RageCoop.Client.Menus
private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic); private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu); private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableAlternatePause); private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableAlternatePause);
private static readonly NativeCheckboxItem _showBlip = new NativeCheckboxItem("Show player blip", "Show other player's blip on map", Main.Settings.ShowPlayerBlip);
private static readonly NativeCheckboxItem _showNametag = new NativeCheckboxItem("Show player nametag", "Show other player's nametag on your screen", Main.Settings.ShowPlayerNameTag);
private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable voice", "Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends", Main.Settings.Voice); private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable voice", "Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends", Main.Settings.Voice);
private static readonly NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString()); private static readonly NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString());
private static readonly NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString()); private static readonly NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
private static readonly NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldVehicleSoftLimit.ToString()); private static readonly NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldVehicleSoftLimit.ToString());
private static readonly NativeItem _pedSoftLimit = new NativeItem("Ped limit (soft)", "The game won't spawn more NPCs if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldPedSoftLimit.ToString());
static SettingsMenu() static SettingsMenu()
{ {
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0); Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged; _disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
_disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged; _disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged;
@ -38,17 +35,6 @@ namespace RageCoop.Client.Menus
_menuKey.Activated += ChaneMenuKey; _menuKey.Activated += ChaneMenuKey;
_passengerKey.Activated += ChangePassengerKey; _passengerKey.Activated += ChangePassengerKey;
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated; _vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
_pedSoftLimit.Activated += PedSoftLimitActivated;
_showBlip.Activated += (s, e) =>
{
Main.Settings.ShowPlayerBlip = _showBlip.Checked;
Util.SaveSettings();
};
_showNametag.Activated += (s, e) =>
{
Main.Settings.ShowPlayerNameTag = _showNametag.Checked;
Util.SaveSettings();
};
Menu.Add(_disableTrafficItem); Menu.Add(_disableTrafficItem);
Menu.Add(_disablePauseAlt); Menu.Add(_disablePauseAlt);
@ -57,9 +43,6 @@ namespace RageCoop.Client.Menus
Menu.Add(_menuKey); Menu.Add(_menuKey);
Menu.Add(_passengerKey); Menu.Add(_passengerKey);
Menu.Add(_vehicleSoftLimit); Menu.Add(_vehicleSoftLimit);
Menu.Add(_pedSoftLimit);
Menu.Add(_showBlip);
Menu.Add(_showNametag);
} }
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e) private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
@ -92,19 +75,7 @@ namespace RageCoop.Client.Menus
Main.Settings.WorldVehicleSoftLimit = int.Parse( Main.Settings.WorldVehicleSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20, Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.WorldVehicleSoftLimit.ToString(), 20)); Main.Settings.WorldVehicleSoftLimit.ToString(), 20));
_vehicleSoftLimit.AltTitle = Main.Settings.WorldVehicleSoftLimit.ToString(); _menuKey.AltTitle = Main.Settings.WorldVehicleSoftLimit.ToString();
Util.SaveSettings();
}
catch { }
}
private static void PedSoftLimitActivated(object sender, EventArgs e)
{
try
{
Main.Settings.WorldPedSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.WorldPedSoftLimit.ToString(), 20));
_pedSoftLimit.AltTitle = Main.Settings.WorldPedSoftLimit.ToString();
Util.SaveSettings(); Util.SaveSettings();
} }
catch { } catch { }

View File

@ -0,0 +1,110 @@
using ICSharpCode.SharpZipLib.Zip;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace RageCoop.Client.Menus
{
internal class UpdateMenu
{
public static bool IsUpdating { get; private set; } = false;
private static readonly NativeItem _updatingItem = new NativeItem("Updating...");
private static readonly NativeItem _downloadItem = new NativeItem("Download", "Download and update to latest nightly");
private static readonly string _downloadPath = Path.Combine(Main.Settings.DataDirectory, "RageCoop.Client.zip");
public static NativeMenu Menu = new NativeMenu("Update", "Update", "Download and install latest nightly build from GitHub")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
static UpdateMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += Opening;
_downloadItem.Activated += StartUpdate;
}
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);
Task.Run(() =>
{
try
{
if (File.Exists(_downloadPath)) { File.Delete(_downloadPath); }
WebClient client = new WebClient();
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
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);
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
});
}
private static void Install()
{
try
{
API.QueueAction(() =>
{
_updatingItem.AltTitle = "Installing...";
});
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, insatllPath, FastZip.Overwrite.Always, null, null, null, true);
try { File.Delete(_downloadPath); } catch { }
try { File.Delete(Path.Combine(insatllPath, "RageCoop.Client.Installer.exe")); } catch { }
Loader.LoaderContext.RequestUnload();
IsUpdating = false;
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
}
private static void Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
Menu.Clear();
if (Networking.IsOnServer)
{
Menu.Add(new NativeItem("Disconnect from the server first"));
}
else if (IsUpdating)
{
Menu.Add(_updatingItem);
}
else
{
Menu.Add(_downloadItem);
}
}
}
}

View File

@ -49,7 +49,7 @@ namespace RageCoop.Client
} }
catch (Exception ex) 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); Main.Logger.Error(ex);
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed }; return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
} }

View File

@ -1,5 +1,6 @@
using GTA.UI; using GTA.UI;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,21 +26,22 @@ namespace RageCoop.Client
static Networking() static Networking()
{ {
Security = new Security(Main.Logger); Security = new Security(Main.Logger);
Packets.CustomEvent.ResolveHandle = _resolveHandle;
} }
public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null) public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null)
{ {
Menus.CoopMenu.Menu.Visible = false; Menus.CoopMenu.Menu.Visible = false;
if (IsConnecting) Peer?.Shutdown("Bye");
if (IsOnServer)
{
// ?
}
else if (IsConnecting)
{ {
_publicKeyReceived.Set(); _publicKeyReceived.Set();
IsConnecting = false; IsConnecting = false;
Main.QueueAction(() => Notification.Show("Connection has been canceled")); Notification.Show("Connection has been canceled");
Peer?.Shutdown("Bye");
}
else if (IsOnServer)
{
Peer?.Shutdown("Bye");
} }
else else
{ {
@ -53,14 +55,13 @@ namespace RageCoop.Client
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0") NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{ {
AutoFlushSendQueue = false, AutoFlushSendQueue = false,
SimulatedMinimumLatency = SimulatedLatency,
SimulatedRandomLatency = 0,
AcceptIncomingConnections = true, AcceptIncomingConnections = true,
MaximumConnections = 32, MaximumConnections = 32,
PingInterval = 5 PingInterval = 5
}; };
#if DEBUG
config.SimulatedMinimumLatency = SimulatedLatency;
config.SimulatedRandomLatency = 0;
#endif
config.EnableMessageType(NetIncomingMessageType.UnconnectedData); config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess); config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
@ -97,14 +98,9 @@ namespace RageCoop.Client
Peer.OnMessageReceived += (s, m) => Peer.OnMessageReceived += (s, m) =>
{ {
try { ProcessMessage(m); } try { ProcessMessage(m); }
catch (Exception ex) catch (Exception ex) { Main.Logger.Error(ex); }
{
#if DEBUG
Main.Logger.Error(ex);
#endif
}
}; };
Main.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); }); API.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
Menus.CoopMenu._serverConnectItem.Enabled = false; Menus.CoopMenu._serverConnectItem.Enabled = false;
Security.Regen(); Security.Regen();
if (publicKey == null) if (publicKey == null)
@ -139,13 +135,13 @@ namespace RageCoop.Client
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger.Error("Cannot connect to server: ", 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; IsConnecting = false;
}); });
} }
} }
public static bool IsOnServer { get => ServerConnection?.Status == NetConnectionStatus.Connected; } public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
#region -- PLAYER -- #region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet) private static void PlayerConnect(Packets.PlayerConnect packet)
@ -158,7 +154,7 @@ namespace RageCoop.Client
PlayerList.SetPlayer(packet.PedID, packet.Username); PlayerList.SetPlayer(packet.PedID, packet.Username);
Main.Logger.Debug($"player connected:{p.Username}"); Main.Logger.Debug($"player connected:{p.Username}");
Main.QueueAction(() => API.QueueAction(() =>
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected.")); GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected."));
} }
private static void PlayerDisconnect(Packets.PlayerDisconnect packet) private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
@ -166,7 +162,7 @@ namespace RageCoop.Client
var player = PlayerList.GetPlayer(packet.PedID); var player = PlayerList.GetPlayer(packet.PedID);
if (player == null) { return; } if (player == null) { return; }
PlayerList.RemovePlayer(packet.PedID); PlayerList.RemovePlayer(packet.PedID);
Main.QueueAction(() => API.QueueAction(() =>
{ {
EntityPool.RemoveAllFromPlayer(packet.PedID); EntityPool.RemoveAllFromPlayer(packet.PedID);
GTA.UI.Notification.Show($"~h~{player.Username}~h~ left."); GTA.UI.Notification.Show($"~h~{player.Username}~h~ left.");

View File

@ -1,9 +1,10 @@
using GTA; using GTA;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting;
using System; using System;
using System.Linq;
using System.Threading; using System.Threading;
namespace RageCoop.Client namespace RageCoop.Client
@ -150,8 +151,7 @@ namespace RageCoop.Client
} }
catch (Exception ex) catch (Exception ex)
{ {
#if DEBUG API.QueueAction(() =>
Main.QueueAction(() =>
{ {
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}"); GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true; return true;
@ -159,8 +159,6 @@ namespace RageCoop.Client
Main.Logger.Error($"[{packetType}] {ex.Message}"); Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex); Main.Logger.Error(ex);
Peer.Shutdown($"Packet Error [{packetType}]"); Peer.Shutdown($"Packet Error [{packetType}]");
#endif
_recycle = false;
} }
break; break;
} }
@ -240,7 +238,7 @@ namespace RageCoop.Client
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b)); Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
packet.Deserialize(msg); 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; break;
@ -263,23 +261,23 @@ namespace RageCoop.Client
case PacketType.CustomEvent: case PacketType.CustomEvent:
{ {
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle); Packets.CustomEvent packet = new Packets.CustomEvent();
packet.Deserialize(msg); if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketType.CustomEventQueued:
{ {
recycle = false; recycle = false;
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle); API.QueueAction(() =>
Main.QueueAction(() =>
{ {
packet.Deserialize(msg); packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet); Scripting.API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg); Peer.Recycle(msg);
}); });
} }
else
{
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
}
break; break;
case PacketType.FileTransferChunk: case PacketType.FileTransferChunk:
@ -295,7 +293,7 @@ namespace RageCoop.Client
{ {
recycle = false; recycle = false;
// Dispatch to script thread // Dispatch to script thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); return true; }); API.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
} }
break; break;
} }
@ -305,15 +303,11 @@ namespace RageCoop.Client
{ {
SyncedPed c = EntityPool.GetPedByID(packet.ID); SyncedPed c = EntityPool.GetPedByID(packet.ID);
if (c == null) if (c == null)
{
if (EntityPool.PedsByID.Count(x => x.Value.OwnerID == packet.OwnerID) < Main.Settings.WorldPedSoftLimit / PlayerList.Players.Count ||
EntityPool.VehiclesByID.Any(x => x.Value.Position.DistanceTo(packet.Position) < 2) || packet.ID == packet.OwnerID)
{ {
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}"); // Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID)); EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
} }
else return; PedDataFlags flags = packet.Flags;
}
c.ID = packet.ID; c.ID = packet.ID;
c.OwnerID = packet.OwnerID; c.OwnerID = packet.OwnerID;
c.Health = packet.Health; c.Health = packet.Health;
@ -359,14 +353,8 @@ namespace RageCoop.Client
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID); SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
if (v == null) if (v == null)
{ {
if (EntityPool.VehiclesByID.Count(x => x.Value.OwnerID == packet.OwnerID) < Main.Settings.WorldVehicleSoftLimit / PlayerList.Players.Count ||
EntityPool.PedsByID.Any(x => x.Value.VehicleID == packet.ID || x.Value.Position.DistanceTo(packet.Position) < 2))
{
// Main.Logger.Debug($"Creating vehicle for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID)); EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
} }
else return;
}
if (v.IsLocal) { return; } if (v.IsLocal) { return; }
v.ID = packet.ID; v.ID = packet.ID;
v.OwnerID = packet.OwnerID; v.OwnerID = packet.OwnerID;
@ -399,6 +387,7 @@ namespace RageCoop.Client
} }
private static void ProjectileSync(Packets.ProjectileSync packet) private static void ProjectileSync(Packets.ProjectileSync packet)
{ {
var p = EntityPool.GetProjectileByID(packet.ID); var p = EntityPool.GetProjectileByID(packet.ID);
if (p == null) if (p == null)
{ {

View File

@ -2,6 +2,7 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -90,9 +91,9 @@ namespace RageCoop.Client
Blip b; Blip b;
if (sp.IsPlayer) if (sp.IsPlayer)
{ {
p.BlipColor = Scripting.API.Config.BlipColor; p.BlipColor = API.Config.BlipColor;
p.BlipSprite = Scripting.API.Config.BlipSprite; p.BlipSprite = API.Config.BlipSprite;
p.BlipScale = Scripting.API.Config.BlipScale; p.BlipScale = API.Config.BlipScale;
} }
else if ((b = ped.AttachedBlip) != null) else if ((b = ped.AttachedBlip) != null)
{ {

View File

@ -2,6 +2,7 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -83,7 +84,7 @@ namespace RageCoop.Client
p._latencyToServer = packet.Latency; p._latencyToServer = packet.Latency;
p.Position = packet.Position; p.Position = packet.Position;
p.IsHost = packet.IsHost; p.IsHost = packet.IsHost;
Main.QueueAction(() => API.QueueAction(() =>
{ {
if (p.FakeBlip?.Exists() != true) if (p.FakeBlip?.Exists() != true)
{ {
@ -95,9 +96,9 @@ namespace RageCoop.Client
} }
else else
{ {
p.FakeBlip.Color = Scripting.API.Config.BlipColor; p.FakeBlip.Color = API.Config.BlipColor;
p.FakeBlip.Scale = Scripting.API.Config.BlipScale; p.FakeBlip.Scale = API.Config.BlipScale;
p.FakeBlip.Sprite = Scripting.API.Config.BlipSprite; p.FakeBlip.Sprite = API.Config.BlipSprite;
p.FakeBlip.DisplayType = BlipDisplayType.Default; p.FakeBlip.DisplayType = BlipDisplayType.Default;
p.FakeBlip.Position = p.Position; p.FakeBlip.Position = p.Position;
} }
@ -124,7 +125,7 @@ namespace RageCoop.Client
if (Players.TryGetValue(id, out var player)) if (Players.TryGetValue(id, out var player))
{ {
Players.Remove(id); Players.Remove(id);
Main.QueueAction(() => player.FakeBlip?.Delete()); API.QueueAction(() => player.FakeBlip?.Delete());
} }
} }
public static void Cleanup() public static void Cleanup()

View File

@ -15,8 +15,8 @@ using System.Resources;
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
// Version information // Version informationr(
[assembly: AssemblyVersion("1.5.4.7")] [assembly: AssemblyVersion("1.5.6.1")]
[assembly: AssemblyFileVersion("1.5.4.7")] [assembly: AssemblyFileVersion("1.5.6.1")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )] [assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View 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>

View File

@ -2,8 +2,11 @@
using GTA; using GTA;
using Newtonsoft.Json; using Newtonsoft.Json;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
@ -25,7 +28,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Provides vital functionality to interact with RAGECOOP /// Provides vital functionality to interact with RAGECOOP
/// </summary> /// </summary>
public class API public static class API
{ {
#region INTERNAL #region INTERNAL
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>(); internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
@ -91,7 +94,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// The local player is dead /// The local player is dead
/// </summary> /// </summary>
public static event EventHandler<string> OnPlayerDied; public static event EmptyEvent OnPlayerDied;
/// <summary> /// <summary>
/// A local vehicle is spawned /// A local vehicle is spawned
@ -116,16 +119,22 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// This is equivalent of <see cref="GTA.Script.Tick"/>. /// This is equivalent of <see cref="GTA.Script.Tick"/>.
/// </summary> /// </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; public static event EmptyEvent OnTick;
/// <summary> /// <summary>
/// This is equivalent of <see cref="Script.KeyDown"/> /// This is equivalent of <see cref="Script.KeyDown"/>
/// </summary> /// </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; public static KeyEventHandler OnKeyDown;
/// <summary> /// <summary>
/// This is equivalent of <see cref="Script.KeyUp"/> /// This is equivalent of <see cref="Script.KeyUp"/>
/// </summary> /// </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; public static KeyEventHandler OnKeyUp;
#region INVOKE #region INVOKE
@ -133,7 +142,7 @@ namespace RageCoop.Client.Scripting
internal static void InvokeVehicleDeleted(SyncedVehicle v) { OnVehicleDeleted?.Invoke(null, v); } internal static void InvokeVehicleDeleted(SyncedVehicle v) { OnVehicleDeleted?.Invoke(null, v); }
internal static void InvokePedSpawned(SyncedPed p) { OnPedSpawned?.Invoke(null, p); } internal static void InvokePedSpawned(SyncedPed p) { OnPedSpawned?.Invoke(null, p); }
internal static void InvokePedDeleted(SyncedPed p) { OnPedDeleted?.Invoke(null, p); } internal static void InvokePedDeleted(SyncedPed p) { OnPedDeleted?.Invoke(null, p); }
internal static void InvokePlayerDied(string m) { OnPlayerDied?.Invoke(null, m); } internal static void InvokePlayerDied() { OnPlayerDied?.Invoke(); }
internal static void InvokeTick() { OnTick?.Invoke(); } internal static void InvokeTick() { OnTick?.Invoke(); }
internal static void InvokeKeyDown(object s, KeyEventArgs e) { OnKeyDown?.Invoke(s, e); } internal static void InvokeKeyDown(object s, KeyEventArgs e) { OnKeyDown?.Invoke(s, e); }
@ -257,30 +266,12 @@ namespace RageCoop.Client.Scripting
Networking.SendChatMessage(message); 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> /// <summary>
/// Send an event and data to the server. /// Send an event and data to the server.
/// </summary> /// </summary>
/// <param name="eventHash">An unique identifier of the event</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> /// <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() Networking.Peer.SendTo(new Packets.CustomEvent()
@ -289,13 +280,27 @@ namespace RageCoop.Client.Scripting
Hash = eventHash Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered); }, 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> /// <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. /// 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> /// </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="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> /// <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) lock (CustomEventHandlers)
{ {
@ -335,5 +340,41 @@ namespace RageCoop.Client.Scripting
}); });
} }
#endregion #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);
}
} }
} }

View File

@ -9,14 +9,14 @@ using System.Threading.Tasks;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
internal class BaseScript : ClientScript internal static class BaseScript
{ {
private bool _isHost = false; private static bool _isHost = false;
public override void OnStart() public static void OnStart()
{ {
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); }; API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); }; API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
API.Events.OnPlayerDied += (s, m) => { API.SendCustomEvent(CustomEvents.OnPlayerDied, m); }; API.Events.OnPlayerDied += () => { API.SendCustomEvent(CustomEvents.OnPlayerDied); };
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn, SetAutoRespawn); API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn, SetAutoRespawn);
API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetDisplayNameTag); API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetDisplayNameTag);
@ -31,7 +31,7 @@ namespace RageCoop.Client.Scripting
API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip); API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip);
API.RegisterCustomEventHandler(CustomEvents.IsHost, (e) => { _isHost = (bool)e.Args[0]; }); API.RegisterCustomEventHandler(CustomEvents.IsHost, (e) => { _isHost = (bool)e.Args[0]; });
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync); API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied, (e) => { GTA.UI.Notification.Show((string)e.Args[0]); }); API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied, (e) => { GTA.UI.Notification.Show($"~h~{e.Args[0]}~h~ died."); });
Task.Run(() => Task.Run(() =>
{ {
while (true) while (true)
@ -46,7 +46,7 @@ namespace RageCoop.Client.Scripting
int weather1 = default(int); int weather1 = default(int);
int weather2 = default(int); int weather2 = default(int);
float percent2 = default(float); float percent2 = default(float);
Function.Call(Hash.GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2); Function.Call(Hash._GET_WEATHER_TYPE_TRANSITION, &weather1, &weather2, &percent2);
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes, time.Seconds, weather1, weather2, percent2); API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes, time.Seconds, weather1, weather2, percent2);
} }
}); });
@ -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]); World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
Function.Call(Hash.SET_CURR_WEATHER_STATE, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]); 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]); var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; } 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]); var p = Entity.FromHandle((int)e.Args[0]);
if (p == null) { return; } 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]; var vehicleModel = (Model)e.Args[1];
vehicleModel.Request(1000); vehicleModel.Request(1000);
Vehicle veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]); Vehicle veh;
while (veh == null) 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); Thread.Sleep(10);
} }
veh.CanPretendOccupants = false; veh.CanPretendOccupants = false;
@ -109,7 +108,7 @@ namespace RageCoop.Client.Scripting
EntityPool.Add(v); 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)) 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]; int id = (int)obj.Args[0];
var sprite = (BlipSprite)(ushort)obj.Args[1]; 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(); Entity.FromHandle((int)e.Args[0])?.Delete();
} }
public override void OnStop() private static void SetNameTag(CustomEventReceivedArgs e)
{
}
private void SetNameTag(CustomEventReceivedArgs e)
{ {
var p = PlayerList.GetPlayer((int)e.Args[0]); var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null) if (p != null)
@ -156,11 +152,11 @@ namespace RageCoop.Client.Scripting
p.DisplayNameTag = (bool)e.Args[1]; p.DisplayNameTag = (bool)e.Args[1];
} }
} }
private void SetAutoRespawn(CustomEventReceivedArgs args) private static void SetAutoRespawn(CustomEventReceivedArgs args)
{ {
API.Config.EnableAutoRespawn = (bool)args.Args[0]; API.Config.EnableAutoRespawn = (bool)args.Args[0];
} }
private void DeleteServerProp(CustomEventReceivedArgs e) private static void DeleteServerProp(CustomEventReceivedArgs e)
{ {
var id = (int)e.Args[0]; var id = (int)e.Args[0];
if (EntityPool.ServerProps.TryGetValue(id, out var prop)) 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; SyncedProp prop;
var id = (int)e.Args[0]; var id = (int)e.Args[0];
@ -185,9 +181,10 @@ namespace RageCoop.Client.Scripting
prop.Model = (Model)e.Args[1]; prop.Model = (Model)e.Args[1];
prop.Position = (Vector3)e.Args[2]; prop.Position = (Vector3)e.Args[2];
prop.Rotation = (Vector3)e.Args[3]; prop.Rotation = (Vector3)e.Args[3];
prop.Model.Request(1000);
prop.Update(); prop.Update();
} }
private void NativeCall(CustomEventReceivedArgs e) private static void NativeCall(CustomEventReceivedArgs e)
{ {
List<InputArgument> arguments = new List<InputArgument>(); List<InputArgument> arguments = new List<InputArgument>();
int i; int i;
@ -271,7 +268,7 @@ namespace RageCoop.Client.Scripting
} }
} }
private InputArgument GetInputArgument(object obj) private static InputArgument GetInputArgument(object obj)
{ {
// Implicit conversion // Implicit conversion
switch (obj) switch (obj)

View File

@ -5,15 +5,16 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <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. /// 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> /// </summary>
public abstract class ClientScript public abstract class ClientScript : GTA.Script
{ {
/// <summary> /// <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> /// </summary>
public abstract void OnStart(); public abstract void OnStart();
/// <summary> /// <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> /// </summary>
public abstract void OnStop(); public abstract void OnStop();

View 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();
}
}
}

View File

@ -22,7 +22,7 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
public string MasterServer { get; set; } = "https://masterserver.ragecoop.com/"; public string MasterServer { get; set; } = "https://masterserver.ragecoop.online/";
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
@ -36,7 +36,7 @@ namespace RageCoop.Client
/// LogLevel for RageCoop. /// LogLevel for RageCoop.
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error /// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary> /// </summary>
public int LogLevel = 00; public int LogLevel = 1;
/// <summary> /// <summary>
/// The key to open menu /// The key to open menu
@ -51,7 +51,7 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// Disable world NPC traffic, mission entities won't be affected /// Disable world NPC traffic, mission entities won't be affected
/// </summary> /// </summary>
public bool DisableTraffic { get; set; } = false; public bool DisableTraffic { get; set; } = true;
/// <summary> /// <summary>
/// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed. /// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed.
@ -67,25 +67,14 @@ namespace RageCoop.Client
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended). /// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary> /// </summary>
public int WorldPedSoftLimit { get; set; } = 30; public int WorldPedSoftLimit { get; set; } = 30;
/// <summary> /// <summary>
/// The directory where log and resources downloaded from server will be placed. /// The directory where log and resources downloaded from server will be placed.
/// </summary> /// </summary>
public string DataDirectory { get; set; } = "Scripts\\RageCoop\\Data"; public string DataDirectory { get; set; } = "RageCoop\\Data";
/// <summary> /// <summary>
/// Show the owner name of the entity you're aiming at /// Show the owner name of the entity you're aiming at
/// </summary> /// </summary>
public bool ShowEntityOwnerName { get; set; } = false; public bool ShowEntityOwnerName { get; set; } = false;
/// <summary>
/// Show other player's nametag on your screen
/// </summary>
public bool ShowPlayerNameTag { get; set; } = true;
/// <summary>
/// Show other player's blip on map
/// </summary>
public bool ShowPlayerBlip { get; set; } = true;
} }
} }

View File

@ -40,8 +40,10 @@ namespace RageCoop.Client
var flag = AnimationFlags.Loop; var flag = AnimationFlags.Loop;
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3)) if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
{ {
if (LoadAnim(animDict) == null) { return; }
MainPed.Task.ClearAll(); 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) private string LoadAnim(string anim)
{ {
ulong startTime = Util.GetTickCount64(); if (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
while (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
{ {
Script.Yield();
Function.Call(Hash.REQUEST_ANIM_DICT, anim); Function.Call(Hash.REQUEST_ANIM_DICT, anim);
if (Util.GetTickCount64() - startTime >= 1000) return null;
{
break;
} }
}
return anim; return anim;
} }
} }

View File

@ -28,7 +28,7 @@ namespace RageCoop.Client
MainPed = p; MainPed = p;
OwnerID = Main.LocalPlayerID; OwnerID = Main.LocalPlayerID;
//Function.Call(Hash.SET_PED_IS_IGNORED_BY_AUTO_OPEN_DOORS, false); Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true); MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
// MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableMelee, true); // MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableMelee, true);
@ -76,13 +76,12 @@ namespace RageCoop.Client
} }
} }
if (!Main.Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255; if (((byte)BlipColor == 255) && (PedBlip != null))
if ((byte)BlipColor == 255 && PedBlip != null)
{ {
PedBlip.Delete(); PedBlip.Delete();
PedBlip = null; PedBlip = null;
} }
else if ((byte)BlipColor != 255 && PedBlip == null) else if (((byte)BlipColor != 255) && PedBlip == null)
{ {
PedBlip = MainPed.AddBlip(); PedBlip = MainPed.AddBlip();
@ -141,12 +140,6 @@ namespace RageCoop.Client
} }
} }
if (!IsPlayer && Health <= 0 && !MainPed.IsDead)
{
MainPed.Kill();
return;
}
if (Speed >= 4) if (Speed >= 4)
{ {
DisplayInVehicle(); DisplayInVehicle();
@ -177,7 +170,7 @@ namespace RageCoop.Client
private void RenderNameTag() private void RenderNameTag()
{ {
if (!Owner.DisplayNameTag || !Main.Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible || !MainPed.IsInRange(Main.PlayerPosition, 40f)) if (!Owner.DisplayNameTag || (MainPed == null) || !MainPed.IsVisible || !MainPed.IsInRange(Main.PlayerPosition, 40f))
{ {
return; return;
} }
@ -211,7 +204,7 @@ namespace RageCoop.Client
MainPed = null; MainPed = null;
} }
if (PedBlip != null) if (PedBlip != null && PedBlip.Exists())
{ {
PedBlip.Delete(); PedBlip.Delete();
PedBlip = null; PedBlip = null;
@ -242,7 +235,7 @@ namespace RageCoop.Client
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true); Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
Function.Call(Hash.SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false); Function.Call(Hash.SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true); Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
Function.Call(Hash.SET_PED_IS_IGNORED_BY_AUTO_OPEN_DOORS, false); Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false); Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater, false); MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater, false);
@ -290,7 +283,10 @@ namespace RageCoop.Client
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3)) 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; return;
} }
@ -319,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)) 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; return;
@ -426,7 +424,7 @@ namespace RageCoop.Client
} }
_lastIsJumping = false; _lastIsJumping = false;
if (IsRagdoll || (IsPlayer && Health == 0)) if (IsRagdoll || Health == 0)
{ {
if (!MainPed.IsRagdoll) if (!MainPed.IsRagdoll)
{ {
@ -604,8 +602,6 @@ namespace RageCoop.Client
MainPed.Task.StandStill(2000); MainPed.Task.StandStill(2000);
LastMoving = false; LastMoving = false;
} }
if (MainPed.IsTaskActive(TaskType.CTaskDiveToGround)) MainPed.Task.ClearAll();
break; break;
} }
SmoothTransition(); SmoothTransition();
@ -722,7 +718,7 @@ namespace RageCoop.Client
case 5: case 5:
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle || MainPed.GetSeatTryingToEnter() != Seat) if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle || MainPed.GetSeatTryingToEnter() != Seat)
{ {
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5, EnterVehicleFlags.JackAnyone); MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5, EnterVehicleFlags.AllowJacking);
} }
break; break;
case 6: case 6:

View File

@ -27,6 +27,7 @@ namespace RageCoop.Client
{ {
if (!NeedUpdate) { return; } if (!NeedUpdate) { return; }
if (!Model.IsLoaded) { Model.Request(); return; }
if (MainProp == null || !MainProp.Exists()) if (MainProp == null || !MainProp.Exists())
{ {
MainProp = World.CreateProp(Model, Position, Rotation, false, false); MainProp = World.CreateProp(Model, Position, Rotation, false, false);

View File

@ -4,7 +4,6 @@ using GTA.Native;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -114,7 +113,7 @@ namespace RageCoop.Client
{ {
if (MainVehicle.IsDead) if (MainVehicle.IsDead)
{ {
Main.Delay(() => WorldThread.Delay(() =>
{ {
if (MainVehicle.IsDead && !IsDead) if (MainVehicle.IsDead && !IsDead)
{ {
@ -198,13 +197,13 @@ namespace RageCoop.Client
if (!_lastTransformed) if (!_lastTransformed)
{ {
_lastTransformed = true; _lastTransformed = true;
Function.Call(Hash.TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false); Function.Call(Hash._TRANSFORM_VEHICLE_TO_SUBMARINE, MainVehicle.Handle, false);
} }
} }
else if (_lastTransformed) else if (_lastTransformed)
{ {
_lastTransformed = false; _lastTransformed = false;
Function.Call(Hash.TRANSFORM_TO_CAR, MainVehicle.Handle, false); Function.Call(Hash._TRANSFORM_SUBMARINE_TO_VEHICLE, MainVehicle.Handle, false);
} }
} }
else if (IsDeluxo) else if (IsDeluxo)
@ -308,16 +307,6 @@ namespace RageCoop.Client
} }
private bool CreateVehicle() private bool CreateVehicle()
{ {
var existing = World.GetNearbyVehicles(Position, 2).ToList().FirstOrDefault();
if (existing != null && existing != MainVehicle)
{
if (EntityPool.VehiclesByHandle.ContainsKey(existing.Handle))
{
EntityPool.RemoveVehicle(ID);
return false;
}
existing.Delete();
}
MainVehicle?.Delete(); MainVehicle?.Delete();
MainVehicle = Util.CreateVehicle(Model, Position); MainVehicle = Util.CreateVehicle(Model, Position);
if (!Model.IsInCdImage) if (!Model.IsInCdImage)
@ -348,5 +337,41 @@ namespace RageCoop.Client
Model.MarkAsNoLongerNeeded(); Model.MarkAsNoLongerNeeded();
return true; return true;
} }
#region -- PEDALING --
/*
* Thanks to @oldnapalm.
*/
private string PedalingAnimDict()
{
switch ((VehicleHash)Model)
{
case VehicleHash.Bmx:
return "veh@bicycle@bmx@front@base";
case VehicleHash.Cruiser:
return "veh@bicycle@cruiserfront@base";
case VehicleHash.Scorcher:
return "veh@bicycle@mountainfront@base";
default:
return "veh@bicycle@roadfront@base";
}
}
private string PedalingAnimName(bool fast)
{
return fast ? "fast_pedal_char" : "cruise_pedal_char";
}
private void StartPedalingAnim(bool fast)
{
MainVehicle.Driver?.Task.PlayAnimation(PedalingAnimDict(), PedalingAnimName(fast), 8.0f, -8.0f, -1, AnimationFlags.Loop | AnimationFlags.AllowRotation, 1.0f);
}
private void StopPedalingAnim(bool fast)
{
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
}
#endregion
} }
} }

View File

@ -43,7 +43,7 @@ namespace RageCoop.Client
#endregion #endregion
public static void Cleanup(bool keepPlayer = true, bool keepMine = true) public static void Cleanup(bool keepPlayer = true, bool keepMine = true)
{ {
foreach (var ped in PedsByID.Values.ToArray()) foreach (var ped in PedsByID.Values)
{ {
if ((keepPlayer && (ped.ID == Main.LocalPlayerID)) || (keepMine && (ped.OwnerID == Main.LocalPlayerID))) { continue; } if ((keepPlayer && (ped.ID == Main.LocalPlayerID)) || (keepMine && (ped.OwnerID == Main.LocalPlayerID))) { continue; }
RemovePed(ped.ID); RemovePed(ped.ID);
@ -51,7 +51,7 @@ namespace RageCoop.Client
PedsByID.Clear(); PedsByID.Clear();
PedsByHandle.Clear(); PedsByHandle.Clear();
foreach (int id in VehiclesByID.Keys.ToArray()) foreach (int id in new List<int>(VehiclesByID.Keys))
{ {
if (keepMine && (VehiclesByID[id].OwnerID == Main.LocalPlayerID)) { continue; } if (keepMine && (VehiclesByID[id].OwnerID == Main.LocalPlayerID)) { continue; }
RemoveVehicle(id); RemoveVehicle(id);
@ -277,8 +277,8 @@ namespace RageCoop.Client
{ {
ProjectilesByHandle.Remove(p.Handle); ProjectilesByHandle.Remove(p.Handle);
} }
//Main.Logger.Debug($"Removing projectile {sp.ID}. Reason:{reason}"); Main.Logger.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
if (sp.Exploded) p.Explode(); p.Explode();
} }
ProjectilesByID.Remove(id); ProjectilesByID.Remove(id);
} }
@ -359,24 +359,21 @@ namespace RageCoop.Client
lock (PedsLock) lock (PedsLock)
{ {
AddPlayer(); AddPlayer();
var mainCharacters = new List<PedHash> { PedHash.Michael, PedHash.Franklin, PedHash.Franklin02, PedHash.Trevor };
foreach (Ped p in allPeds) foreach (Ped p in allPeds)
{ {
if (!PedsByHandle.ContainsKey(p.Handle) && p != Game.Player.Character && !mainCharacters.Contains((PedHash)p.Model.Hash)) SyncedPed c = GetPedByHandle(p.Handle);
if (c == null && (p != Game.Player.Character))
{ {
if (PedsByID.Count(x => x.Value.IsLocal) > Main.Settings.WorldPedSoftLimit) if (allPeds.Length > Main.Settings.WorldPedSoftLimit && p.PopulationType == EntityPopulationType.RandomAmbient)
{
if (p.PopulationType == EntityPopulationType.RandomAmbient && !p.IsInVehicle())
{ {
p.Delete(); p.Delete();
continue; continue;
} }
if (p.PopulationType == EntityPopulationType.RandomScenario) continue;
}
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}"); // Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c = new SyncedPed(p);
Add(new SyncedPed(p)); Add(c);
} }
} }
#if BENCHMARK #if BENCHMARK
@ -440,9 +437,10 @@ namespace RageCoop.Client
{ {
if (!VehiclesByHandle.ContainsKey(veh.Handle)) if (!VehiclesByHandle.ContainsKey(veh.Handle))
{ {
if (VehiclesByID.Count(x => x.Value.IsLocal) > Main.Settings.WorldVehicleSoftLimit) if (allVehicles.Length > Main.Settings.WorldVehicleSoftLimit)
{ {
if (veh.PopulationType == EntityPopulationType.RandomAmbient || veh.PopulationType == EntityPopulationType.RandomParked) var type = veh.PopulationType;
if (type == EntityPopulationType.RandomAmbient || type == EntityPopulationType.RandomParked)
{ {
foreach (var p in veh.Occupants) foreach (var p in veh.Occupants)
{ {

View File

@ -8,6 +8,7 @@ namespace RageCoop.Client
{ {
internal static class SyncEvents internal static class SyncEvents
{ {
#region TRIGGER #region TRIGGER
public static void TriggerPedKilled(SyncedPed victim) public static void TriggerPedKilled(SyncedPed victim)
{ {
@ -126,7 +127,7 @@ namespace RageCoop.Client
} }
var p = EntityPool.GetPedByID(ownerID)?.MainPed; var p = EntityPool.GetPedByID(ownerID)?.MainPed;
if (p == null) { return; /* p = Game.Player.Character; Main.Logger.Warning("Failed to find owner for bullet"); */ } if (p == null) { p = Game.Player.Character; Main.Logger.Warning("Failed to find owner for bullet"); }
if (!CorePFXAsset.IsLoaded) { CorePFXAsset.Request(); } if (!CorePFXAsset.IsLoaded) { CorePFXAsset.Request(); }
if (_lastWeaponHash != weaponHash) if (_lastWeaponHash != weaponHash)
{ {
@ -256,7 +257,7 @@ namespace RageCoop.Client
if (!getBulletImpact()) if (!getBulletImpact())
{ {
Main.QueueAction(getBulletImpact); Scripting.API.QueueAction(getBulletImpact);
} }
} }
else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default) else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default)

View File

@ -1,9 +1,4 @@
using GTA; using GTA;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client namespace RageCoop.Client
{ {

View File

@ -296,7 +296,7 @@ namespace RageCoop.Client
{ {
result = true; result = true;
} }
else if (veh.GetPedOnSeat(seat) != null) else
{ {
bool isDead = veh.GetPedOnSeat(seat).IsDead; bool isDead = veh.GetPedOnSeat(seat).IsDead;

View File

@ -1,21 +1,32 @@
using GTA; using GTA;
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Newtonsoft.Json;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml.Serialization;
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")] [assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static class Util 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 public static SizeF ResolutionMaintainRatio
{ {
get get
@ -110,28 +121,21 @@ namespace RageCoop.Client
#endregion #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) public static Settings ReadSettings(string path = null)
{ {
path = path ?? SettingsPath; path = path ?? SettingsPath;
XmlSerializer ser = new XmlSerializer(typeof(Settings));
Directory.CreateDirectory(Directory.GetParent(path).FullName); Directory.CreateDirectory(Directory.GetParent(path).FullName);
Settings settings = null; Settings settings;
try
if (File.Exists(path))
{ {
using (FileStream stream = File.OpenRead(path)) settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));
{
settings = (Settings)ser.Deserialize(stream);
} }
} catch (Exception ex)
else
{ {
using (FileStream stream = File.OpenWrite(path)) Main.Logger?.Error(ex);
{ File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented));
ser.Serialize(stream, settings = new Settings());
}
} }
return settings; return settings;
@ -144,17 +148,13 @@ namespace RageCoop.Client
settings = settings ?? Main.Settings; settings = settings ?? Main.Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName); Directory.CreateDirectory(Directory.GetParent(path).FullName);
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite)) File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
ser.Serialize(stream, settings);
}
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger?.Error(ex);
return false; 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); 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")] [DllImport("kernel32.dll")]
public static extern ulong GetTickCount64(); public static extern ulong GetTickCount64();
#endregion
} }
} }

View File

@ -50,7 +50,7 @@ namespace RageCoop.Client
flags |= VehicleDataFlags.IsHornActive; flags |= VehicleDataFlags.IsHornActive;
} }
if (v.IsSubmarineCar && Function.Call<bool>(Hash.IS_VEHICLE_IN_SUBMARINE_MODE, veh.Handle)) if (v.IsSubmarineCar && Function.Call<bool>(Hash._GET_IS_SUBMARINE_VEHICLE_TRANSFORMED, veh.Handle))
{ {
flags |= VehicleDataFlags.IsTransformed; flags |= VehicleDataFlags.IsTransformed;
} }
@ -89,7 +89,7 @@ namespace RageCoop.Client
} }
public static bool IsRocketBoostActive(this Vehicle veh) public static bool IsRocketBoostActive(this Vehicle veh)
{ {
return Function.Call<bool>(Hash.IS_ROCKET_BOOST_ACTIVE, veh); return Function.Call<bool>(Hash._IS_VEHICLE_ROCKET_BOOST_ACTIVE, veh);
} }
public static bool IsParachuteActive(this Vehicle veh) public static bool IsParachuteActive(this Vehicle veh)
{ {
@ -97,7 +97,7 @@ namespace RageCoop.Client
} }
public static void SetRocketBoostActive(this Vehicle veh, bool toggle) public static void SetRocketBoostActive(this Vehicle veh, bool toggle)
{ {
Function.Call(Hash.SET_ROCKET_BOOST_ACTIVE, veh, toggle); Function.Call(Hash._SET_VEHICLE_ROCKET_BOOST_ACTIVE, veh, toggle);
} }
public static void SetParachuteActive(this Vehicle veh, bool toggle) public static void SetParachuteActive(this Vehicle veh, bool toggle)
{ {
@ -242,7 +242,7 @@ namespace RageCoop.Client
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover) public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{ {
Function.Call(Hash.SET_SPECIAL_FLIGHT_MODE_TARGET_RATIO, deluxo, hover ? 1f : 0f); Function.Call(Hash._SET_VEHICLE_HOVER_TRANSFORM_PERCENTAGE, deluxo, hover ? 1f : 0f);
} }
public static bool IsDeluxoHovering(this Vehicle deluxo) public static bool IsDeluxoHovering(this Vehicle deluxo)
{ {
@ -250,7 +250,7 @@ namespace RageCoop.Client
} }
public static void SetDeluxoWingRatio(this Vehicle v, float ratio) public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
{ {
Function.Call(Hash.SET_HOVER_MODE_WING_RATIO, v, ratio); Function.Call(Hash._SET_SPECIALFLIGHT_WING_RATIO, v, ratio);
} }
public static float GetDeluxoWingRatio(this Vehicle v) public static float GetDeluxoWingRatio(this Vehicle v)
{ {
@ -258,7 +258,7 @@ namespace RageCoop.Client
} }
public static float GetNozzleAngel(this Vehicle plane) public static float GetNozzleAngel(this Vehicle plane)
{ {
return Function.Call<float>(Hash.GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane); return Function.Call<float>(Hash._GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
} }
public static bool HasNozzle(this Vehicle v) public static bool HasNozzle(this Vehicle v)
{ {

View File

@ -1,20 +1,27 @@
using GTA; using GTA;
using GTA.Native; using GTA.Native;
using System; using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </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> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
public WorldThread() public WorldThread()
{ {
Util.StartUpCheck();
Instance = this;
Tick += OnTick; Tick += OnTick;
Aborted += (sender, e) => Aborted += (sender, e) =>
{ {
@ -25,6 +32,7 @@ namespace RageCoop.Client
private static bool _trafficEnabled; private static bool _trafficEnabled;
private void OnTick(object sender, EventArgs e) private void OnTick(object sender, EventArgs e)
{ {
DoQueuedActions();
if (Game.IsLoading || !Networking.IsOnServer) if (Game.IsLoading || !Networking.IsOnServer)
{ {
return; return;
@ -120,7 +128,7 @@ namespace RageCoop.Client
if ((c == null) || (c.IsLocal && (ped.Handle != Game.Player.Character.Handle) && ped.PopulationType != EntityPopulationType.Mission)) if ((c == null) || (c.IsLocal && (ped.Handle != Game.Player.Character.Handle) && ped.PopulationType != EntityPopulationType.Mission))
{ {
// Main.Logger.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic"); Main.Logger.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
ped.CurrentVehicle?.Delete(); ped.CurrentVehicle?.Delete();
ped.Kill(); ped.Kill();
ped.Delete(); ped.Delete();
@ -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
View 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>

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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.Extensions.ObjectPool" version="6.0.8" targetFramework="net48" />
<package id="Microsoft.NETCore.Platforms" version="1.1.0" 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.Primitives" version="4.3.0" targetFramework="net48" />
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" /> <package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" />
<package id="NAudio" version="2.1.0" targetFramework="net481" /> <package id="NAudio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Asio" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Asio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Core" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Core" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Midi" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Midi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Wasapi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net481" /> <package id="NAudio.WinForms" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net481" /> <package id="NAudio.WinMM" version="2.1.0" targetFramework="net48" />
<package id="NETStandard.Library" version="1.6.1" 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.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" version="4.3.0" targetFramework="net48" />
<package id="System.Collections.Concurrent" 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" /> <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.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Linq" 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.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.Http" version="4.3.0" targetFramework="net48" />
<package id="System.Net.Primitives" 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.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.ObjectModel" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection" 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.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection.Primitives" 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.Resources.ResourceManager" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime" 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.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.Handles" 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" /> <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.Text.RegularExpressions" version="4.3.0" targetFramework="net48" />
<package id="System.Threading" 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" 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.Threading.Timer" version="4.3.0" targetFramework="net48" />
<package id="System.Xml.ReaderWriter" 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" /> <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" />

View File

@ -1,6 +1,7 @@
using GTA.Math; using GTA.Math;
using Lidgren.Network; using Lidgren.Network;
using Newtonsoft.Json; using Newtonsoft.Json;
using RageCoop.Core.Scripting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -9,6 +10,7 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -24,15 +26,49 @@ namespace RageCoop.Core
{ {
private static readonly HashSet<string> ToIgnore = new HashSet<string>() private static readonly HashSet<string> ToIgnore = new HashSet<string>()
{ {
"RageCoop.Client.dll", "RageCoop.Client",
"RageCoop.Core.dll", "RageCoop.Client.Loader",
"RageCoop.Server.dll", "RageCoop.Client.Installer",
"ScriptHookVDotNet3.dll", "RageCoop.Core",
"ScriptHookVDotNet.dll" "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) 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) public static void GetBytesFromObject(object obj, NetOutgoingMessage m)
{ {
@ -225,6 +261,11 @@ namespace RageCoop.Core
} }
return addresses; 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 internal class IpInfo
{ {
@ -272,22 +313,27 @@ namespace RageCoop.Core
p.Deserialize(msg); p.Deserialize(msg);
return p; 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) public static Type GetActualType(this TypeCode code)
{ {
@ -401,13 +447,11 @@ namespace RageCoop.Core
return output; 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) return !type.IsAbstract && type.IsSubclassOf(scriptType);
if (t.FullName == baseTypeName)
return true;
return false;
} }
} }
/// <summary> /// <summary>

176
Core/Logger.cs Normal file
View 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();
}
}
}

View File

@ -1,8 +1,11 @@
namespace RageCoop.Core using System;
namespace RageCoop.Core
{ {
/// <summary> /// <summary>
/// A json object representing a server's information as annouced to master server. /// A json object representing a server's information as annouced to master server.
/// </summary> /// </summary>
[Serializable]
public class ServerInfo public class ServerInfo
{ {
#pragma warning disable 1591 #pragma warning disable 1591

View File

@ -1,8 +1,5 @@
using GTA.Math; using GTA.Math;
using Lidgren.Network; using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Text;
namespace RageCoop.Core namespace RageCoop.Core
{ {

View File

@ -1,4 +1,5 @@
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Core.Scripting;
using System; using System;
namespace RageCoop.Core namespace RageCoop.Core
{ {
@ -7,21 +8,20 @@ namespace RageCoop.Core
internal class CustomEvent : Packet internal class CustomEvent : Packet
{ {
public override PacketType Type => (_queued ? PacketType.CustomEventQueued : PacketType.CustomEvent); public static Func<byte, NetIncomingMessage, object> ResolveHandle = null;
public CustomEvent(Func<byte, NetIncomingMessage, object> onResolve = null, bool queued = false) public CustomEventFlags Flags;
public override PacketType Type => PacketType.CustomEvent;
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
{ {
_resolve = onResolve; Flags = flags;
_queued = queued;
} }
private readonly bool _queued;
private Func<byte, NetIncomingMessage, object> _resolve { get; set; }
public int Hash { get; set; } public int Hash { get; set; }
public object[] Args { get; set; } public object[] Args { get; set; }
protected override void Serialize(NetOutgoingMessage m) protected override void Serialize(NetOutgoingMessage m)
{ {
Args = Args ?? new object[] { }; Args = Args ?? new object[] { };
m.Write((byte)Flags);
m.Write(Hash); m.Write(Hash);
m.Write(Args.Length); m.Write(Args.Length);
foreach (var arg in Args) foreach (var arg in Args)
@ -33,11 +33,10 @@ namespace RageCoop.Core
public override void Deserialize(NetIncomingMessage m) public override void Deserialize(NetIncomingMessage m)
{ {
Flags = (CustomEventFlags)m.ReadByte();
Hash = m.ReadInt32(); Hash = m.ReadInt32();
var len = m.ReadInt32(); Args = new object[m.ReadInt32()];
Args = new object[len]; for (int i = 0; i < Args.Length; i++)
for (int i = 0; i < len; i++)
{ {
byte type = m.ReadByte(); byte type = m.ReadByte();
switch (type) switch (type)
@ -73,13 +72,13 @@ namespace RageCoop.Core
case 0x15: case 0x15:
Args[i] = m.ReadByteArray(); break; Args[i] = m.ReadByteArray(); break;
default: default:
if (_resolve == null) if (ResolveHandle == null)
{ {
throw new InvalidOperationException($"Unexpected type: {type}"); throw new InvalidOperationException($"Unexpected type: {type}");
} }
else else
{ {
Args[i] = _resolve(type, m); break; Args[i] = ResolveHandle(type, m); break;
} }
} }
} }

View File

@ -24,7 +24,6 @@ namespace RageCoop.Core
AllResourcesSent = 15, AllResourcesSent = 15,
CustomEvent = 16, CustomEvent = 16,
CustomEventQueued = 17,
ConnectionRequest = 18, ConnectionRequest = 18,
P2PConnect = 19, P2PConnect = 19,

View File

@ -24,6 +24,14 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </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> <ItemGroup>
<PackageReference Include="Costura.Fody" Version="5.7.0"> <PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -34,7 +42,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" /> <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" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup> </ItemGroup>
@ -42,9 +51,6 @@
<Reference Include="Lidgren.Network"> <Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath> <HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3"> <Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath> <HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference> </Reference>

View 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);
}
}
}

View File

@ -15,7 +15,7 @@ RAGECOOP brings multiplayer experience to the story mode, you can complete missi
# 👁 Requirements # 👁 Requirements
- ScriptHookV - ScriptHookV
- ScriptHookVDotNet 3.6.0 or later - ScriptHookVDotNet 3.5.1 or later
- .NET Framework 4.8 Runtime or SDK - .NET Framework 4.8 Runtime or SDK
# 📋 Building the project # 📋 Building the project
@ -49,7 +49,7 @@ Then run `dotnet build` in the solution directory, built binaries are in the `bi
5. Decent compatibility with other mods, set up a private modded server to have some fun! 5. Decent compatibility with other mods, set up a private modded server to have some fun!
6. Weaponized vehicle sync(WIP). 6. Weaponized vehicle sync(WIP).
7. Optimization for high-Ping condition, play with friends around the world! 7. Optimization for high-Ping condition, play with friends around the world!
8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.com). 8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.online).
# ⚠ Known issues # ⚠ Known issues

View File

@ -3,13 +3,17 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166 VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1 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 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 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|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.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection 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 GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7} SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
EndGlobalSection EndGlobalSection

Some files were not shown because too many files have changed in this diff Show More