22 Commits

Author SHA1 Message Date
39b780e518 Merge pull request #56 from RAGECOOP/dev-nightly 2023-10-23 10:48:52 -03:00
4a54851c51 Update AssemblyInfo.cs 2023-10-23 10:36:57 -03:00
f0eefa575c Always sync player ped 2023-10-22 18:59:19 -03:00
3fc813b2d8 Fix bug disconnecting from server 2023-10-22 17:14:50 -03:00
fbff72ff14 Update LemonUI 2023-10-18 16:57:19 -03:00
4e52407591 Update nightly-build.yaml 2023-10-18 16:56:44 -03:00
4e6fde129d Add global limits 2023-10-18 10:41:34 -03:00
92e1a970a8 Show kill notification 2023-10-06 23:46:08 -03:00
d0eb0b7818 Bump version
Add kill message
2023-10-06 16:36:49 -03:00
14151d7b2c Add option to disable blip and nametag display 2023-08-14 14:08:30 -03:00
1f8d70a520 Fix player dying when switching characters in missions 2023-08-13 19:29:48 -03:00
2cb5baf5f7 Fix player dying when switching characters 2023-08-09 23:50:47 -03:00
34e33937e2 Merge pull request #49 from RAGECOOP/dev-nightly
Update README.md
2023-07-26 15:46:58 -03:00
9287aba0f9 Update README.md 2023-07-26 15:42:08 -03:00
e88e903096 Merge pull request #48 from RAGECOOP/dev-nightly
Dev nightly
2023-07-26 15:27:40 -03:00
99642fd40c Update SHVDN 2023-07-26 15:12:58 -03:00
2fbf06b504 Use Lidgren.Network release build 2023-07-24 10:40:33 -03:00
13b771ec9f Fix exception entering vehicle as passenger 2023-07-24 10:39:37 -03:00
3b987f59e0 Remove update menu 2023-07-14 09:43:00 -03:00
de96f29097 Don't delete peds in vehicle 2023-07-14 09:39:06 -03:00
6136cbfc14 Allow multiple servers on same address
with different ports
2023-07-14 09:38:02 -03:00
ed145aedd6 Update master server 2023-07-14 09:36:49 -03:00
261 changed files with 14591 additions and 359854 deletions

View File

@ -1,4 +0,0 @@
[*.cs]
# CS0649: Field 'VehicleInfo.Name' is never assigned to, and will always have its default value null
dotnet_diagnostic.CS0649.severity = silent

View File

@ -2,7 +2,9 @@ name: Build test
on:
push:
branches:
branches:
- '*' # matches every branch that doesn't contain a '/'
- '*/*' # matches every branch containing a single '/'
- '**' # matches every branch
- '!main' # excludes main
- '!dev-nightly' # excludes nightly
@ -16,7 +18,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: ['7.0.x']
dotnet-version: ['6.0.x']
steps:
- uses: actions/checkout@v3
@ -24,30 +26,23 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies
run: dotnet restore
- name: Restore nuget packages
run: nuget restore
- name: Build
run: .\build.cmd
- name: Build client and installer
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
- name: Build server win-x64
run: dotnet build RageCoop.Server/RageCoop.Server.csproj -o bin/Release/Server
- name: Upload server
uses: actions/upload-artifact@v3
with:
name: RageCoop.Server
path: bin/Release/Server
- name: Upload Client
uses: actions/upload-artifact@v3
with:
name: RageCoop.Client
path: bin/Release/Client
- uses: actions/checkout@v2

View File

@ -10,7 +10,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: ['7.0.x']
dotnet-version: ['6.0.x']
steps:
- uses: actions/checkout@v3
@ -18,72 +18,60 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies
run: dotnet restore
- name: Restore nuget packages
run: nuget restore
- name: Build artifacts
run: .\build.cmd
- name: Build server for different platforms
run: .\publish-server.cmd
- name: Build client and installer
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
- name: Build server win-x64
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
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
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Client
dest: RageCoop.Client.zip
- uses: vimtor/action-zip@v1
with:
files: bin/API
dest: bin/Artifacts/SDK.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/win-x64
dest: bin/Artifacts/RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/win-x86
dest: bin/Artifacts/RageCoop.Server-win-x86.zip
dest: RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-x64
dest: bin/Artifacts/RageCoop.Server-linux-x64.zip
dest: RageCoop.Server-linux-x64.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm64
dest: bin/Artifacts/RageCoop.Server-linux-arm64.zip
- name: Deploy binaries
uses: Sardelka9515/deploy-nightly@v0.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/GTAV-RESOURCES/releases/75463254/assets{?name,label}
release_id: 75463254
asset_path: bin/Artifacts
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
release_id: 70603992
asset_path: RageCoop.Client.zip
asset_name: RageCoop.Client.zip
asset_content_type: application/zip
max_releases: 7
- uses: actions/checkout@v2
- 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-win-x64.zip
asset_name: RageCoop.Server-win-x64.zip
asset_content_type: application/zip
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-x64.zip
asset_name: RageCoop.Server-linux-x64.zip
asset_content_type: application/zip
max_releases: 7

6
.gitmodules vendored
View File

@ -1,6 +0,0 @@
[submodule "libs/Lidgren.Network"]
path = libs/Lidgren.Network
url = https://github.com/RAGECOOP/lidgren-network-gen3
[submodule "libs/ScriptHookVDotNetCore"]
path = libs/ScriptHookVDotNetCore
url = https://github.com/Sardelka9515/scripthookvdotnetcore

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>

View File

@ -1,314 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO.MemoryMappedFiles;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.OffScreen;
using Microsoft.Win32.SafeHandles;
namespace RageCoop.Client.CefHost
{
public enum BufferMode
{
Full = 1,
Dirty = 2
}
public enum MouseButton
{
Left,
Middle,
Right
}
/// <summary>
/// Hosted by CefHost for managing cef instances.
/// </summary>
public class CefController : MarshalByRefObject, IDisposable
{
private static Process _host;
private static ActivatedClientTypeEntry _controllerEntry;
private static IpcChannel _adapterChannel;
public static Action<string> OnCefMessage;
private ChromiumWebBrowser _browser;
private MemoryMappedFile _mmf;
private string _mmfName;
private SafeMemoryMappedViewHandle _mmfView;
private BufferMode _mode;
private CefProcessor _processor;
public IntPtr PtrBuffer { get; private set; }
public int FrameRate
{
get => _browser.GetBrowserHost().WindowlessFrameRate;
set => _browser.GetBrowserHost().WindowlessFrameRate = value;
}
public void Dispose()
{
_browser?.Dispose();
_mmf?.Dispose();
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
_mmfView?.Dispose();
PtrBuffer = IntPtr.Zero;
_mmf = null;
_mmfView = null;
}
public static void Initialize(string fileName = "RageCoop.Client.CefHost.exe")
{
_host = new Process();
_host.StartInfo = new ProcessStartInfo
{
FileName = fileName,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
_host.EnableRaisingEvents = true;
_host.Start();
RegisterChannels(_host.StandardOutput.ReadLine());
Task.Run(() =>
{
while (_host?.HasExited == false) OnCefMessage?.Invoke("[CEF]: " + _host.StandardOutput.ReadLine());
});
Task.Run(() =>
{
while (_host?.HasExited == false)
OnCefMessage?.Invoke("[CEF][ERROR]: " + _host.StandardError.ReadLine());
});
}
public static void ShutDown()
{
if (_host == null) return;
_host.StandardInput.WriteLine("exit");
_host.WaitForExit(1000);
_host.Kill();
_host = null;
}
private static void RegisterChannels(string hostChannel)
{
var service = Guid.NewGuid().ToString();
Console.WriteLine("Registering adapter channel: " + service);
_adapterChannel = new IpcChannel(service);
ChannelServices.RegisterChannel(_adapterChannel, false);
_controllerEntry = new ActivatedClientTypeEntry(typeof(CefController), "ipc://" + hostChannel);
RemotingConfiguration.RegisterActivatedClientType(_controllerEntry);
Console.WriteLine("Registered controller entry: " + "ipc://" + hostChannel);
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefAdapter));
Console.WriteLine("Registered service: " + nameof(CefAdapter));
_host.StandardInput.WriteLine("ipc://" + service);
}
/// <summary>
/// Called inside client process
/// </summary>
/// <param name="id"></param>
/// <param name="size"></param>
/// <param name="adapter"></param>
/// <param name="bufferMode"></param>
/// <param name="bufferSize"></param>
/// <returns></returns>
public static CefController Create(int id, Size size, out CefAdapter adapter, BufferMode bufferMode,
long bufferSize = 1024 * 1024 * 16)
{
if (RemotingConfiguration.IsRemotelyActivatedClientType(typeof(CefController)) == null)
throw new RemotingException();
var controller = new CefController();
controller.Activate(id, size, bufferMode, bufferSize);
adapter = CefAdapter.Adapters[id];
controller.Ping();
return controller;
}
public unsafe void Activate(int id, Size size, BufferMode mode = BufferMode.Dirty,
long sharedMemorySize = 1024 * 1024 * 16)
{
_mode = mode;
_mmfName = Guid.NewGuid().ToString();
// Set up shared memory
_mmf = MemoryMappedFile.CreateNew(_mmfName, sharedMemorySize);
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
byte* pBuf = null;
try
{
_mmfView.AcquirePointer(ref pBuf);
PtrBuffer = (IntPtr)pBuf;
}
catch
{
Dispose();
throw;
}
var adapter = new CefAdapter();
adapter.Register(id, mode, _mmfName);
_browser = new ChromiumWebBrowser();
_browser.RenderHandler = _processor = new CefProcessor(size, adapter, PtrBuffer, mode);
while (_browser.GetBrowserHost() == null) Thread.Sleep(20); // Wait till the browser is actually created
Console.WriteLine("CefController created: " + size);
}
public void LoadUrl(string url)
{
_browser.LoadUrl(url);
}
public void Resize(Size size)
{
_browser.Size = size;
_processor.Size = size;
}
public void SendMouseClick(int x, int y, int modifiers, MouseButton button, bool mouseUp, int clicks)
{
var e = new MouseEvent(x, y, (CefEventFlags)modifiers);
_browser.GetBrowserHost()
?.SendMouseClickEvent(e, (MouseButtonType)button, mouseUp, clicks);
}
public void SendMouseMove(int x, int y, bool leave = false)
{
var e = new MouseEvent(x, y, 0);
_browser.GetBrowserHost()?.SendMouseMoveEvent(e, leave);
}
public DateTime Ping()
{
return DateTime.UtcNow;
;
}
public override object InitializeLifetimeService()
{
return null;
}
}
/// <summary>
/// Hosted by client for receiving rendering data
/// </summary>
public class CefAdapter : MarshalByRefObject, IDisposable
{
public delegate void PaintDelegate(int bufferSize, Rectangle dirtyRect);
public delegate void ResizeDelegate(Size newSize);
public static Dictionary<int, CefAdapter> Adapters = new Dictionary<int, CefAdapter>();
private MemoryMappedFile _mmf;
private SafeMemoryMappedViewHandle _mmfView;
public int Id;
public CefAdapter()
{
Console.WriteLine("Adapter created");
}
public Size Size { get; private set; }
public IntPtr PtrBuffer { get; private set; }
/// <summary>
/// Maximum buffer size for a paint event, use this property to allocate memory.
/// </summary>
/// <remarks>Value is equal to <see cref="Size" />*4, therefore will change upon resize</remarks>
public int MaxBufferSize => Size.Height * Size.Width * 4;
public BufferMode BufferMode { get; private set; }
public void Dispose()
{
_mmf?.Dispose();
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
_mmfView?.Dispose();
PtrBuffer = IntPtr.Zero;
_mmf = null;
_mmfView = null;
lock (Adapters)
{
if (Adapters.ContainsKey(Id)) Adapters.Remove(Id);
}
}
public event PaintDelegate OnPaint;
public event ResizeDelegate OnResize;
public void Resized(Size newSize)
{
Size = newSize;
OnResize?.Invoke(newSize);
}
public void Paint(Rectangle dirtyRect)
{
var size = BufferMode == BufferMode.Dirty
? dirtyRect.Width * dirtyRect.Height * 4
: Size.Width * Size.Height * 4;
OnPaint?.Invoke(size, dirtyRect);
}
public override object InitializeLifetimeService()
{
return null;
}
public unsafe void Register(int id, BufferMode mode, string mmfName)
{
lock (Adapters)
{
if (Adapters.ContainsKey(id)) throw new ArgumentException("Specified id is already used", nameof(id));
// Set up shared memory
_mmf = MemoryMappedFile.OpenExisting(mmfName);
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
byte* pBuf = null;
try
{
_mmfView.AcquirePointer(ref pBuf);
PtrBuffer = (IntPtr)pBuf;
}
catch
{
Dispose();
throw;
}
Id = id;
BufferMode = mode;
Adapters.Add(id, this);
}
}
/// <summary>
/// Ensure ipc connection
/// </summary>
/// <returns></returns>
public DateTime Ping()
{
return DateTime.UtcNow;
}
}
}

View File

@ -1,128 +0,0 @@
using System;
using System.Drawing;
using BitmapUtil;
using CefSharp;
using CefSharp.Enums;
using CefSharp.OffScreen;
using CefSharp.Structs;
using Size = System.Drawing.Size;
namespace RageCoop.Client.CefHost
{
internal class CefProcessor : IRenderHandler
{
private readonly CefAdapter _adapter;
private readonly BufferMode _mode;
private readonly IntPtr _pSharedBuffer;
private Rect _rect;
public CefProcessor(Size size, CefAdapter adapter, IntPtr pSharedBuffer, BufferMode mode)
{
_adapter = adapter;
_rect = new Rect(0, 0, size.Width, size.Height);
_pSharedBuffer = pSharedBuffer;
_mode = mode;
_adapter?.Resized(size);
}
public Size Size
{
get => new Size(_rect.Width, _rect.Height);
set
{
_rect = new Rect(0, 0, value.Width, value.Height);
_adapter?.Resized(value);
}
}
public void Dispose()
{
}
public ScreenInfo? GetScreenInfo()
{
return null;
}
public Rect GetViewRect()
{
return _rect;
}
public bool GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
{
screenX = viewX;
screenY = viewY;
return true;
}
public void OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle)
{
}
public void OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height)
{
var dirty = new Rectangle
{
Width = dirtyRect.Width,
Height = dirtyRect.Height,
X = dirtyRect.X,
Y = dirtyRect.Y
};
var source = new BitmapInfo
{
Width = width,
Height = height,
BytesPerPixel = 4,
Scan0 = buffer
};
switch (_mode)
{
case BufferMode.Dirty:
Unsafe.CopyRegion(source, _pSharedBuffer, dirty);
break;
case BufferMode.Full:
{
var target = source;
target.Scan0 = _pSharedBuffer;
Unsafe.UpdateRegion(source, target, dirty, dirty.Location);
break;
}
}
_adapter?.Paint(dirty);
}
public void OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
{
}
public bool StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y)
{
return true;
}
public void UpdateDragCursor(DragOperationsMask operation)
{
}
public void OnPopupShow(bool show)
{
}
public void OnPopupSize(Rect rect)
{
}
public void OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds)
{
;
}
public void OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode)
{
;
}
}
}

View File

@ -1,130 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.OffScreen;
namespace RageCoop.Client.CefHost
{
internal static class Program
{
[SecurityPermission(SecurityAction.Demand)]
private static void Main(string[] args)
{
Cef.Initialize(new CefSettings
{
BackgroundColor = 0x00
});
var name = Guid.NewGuid().ToString();
var channel = new IpcChannel(name);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefController));
// Write to stdout so it can be read by the client
Console.WriteLine(name);
var adapterUrl = Console.ReadLine();
var adapterEntry = new ActivatedClientTypeEntry(typeof(CefAdapter), adapterUrl);
Console.WriteLine("Registered adapter entry: " + adapterUrl);
RemotingConfiguration.RegisterActivatedClientType(adapterEntry);
var channelData = (ChannelDataStore)channel.ChannelData;
foreach (var uri in channelData.ChannelUris) Console.WriteLine("Channel URI: {0}", uri);
Task.Run(() =>
{
try
{
Util.GetParentProcess().WaitForExit();
Console.WriteLine("Parent process terminated, exiting...");
Environment.Exit(0);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
});
while (true)
switch (Console.ReadLine())
{
case "exit":
Cef.Shutdown();
Environment.Exit(0);
break;
}
}
}
/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Util
{
// These members must match PROCESS_BASIC_INFORMATION
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass,
ref Util processInformation, int processInformationLength, out int returnLength);
/// <summary>
/// Gets the parent process of the current process.
/// </summary>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess()
{
return GetParentProcess(Process.GetCurrentProcess().Handle);
}
/// <summary>
/// Gets the parent process of specified process.
/// </summary>
/// <param name="id">The process id.</param>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess(int id)
{
var process = Process.GetProcessById(id);
return GetParentProcess(process.Handle);
}
/// <summary>
/// Gets the parent process of a specified process.
/// </summary>
/// <param name="handle">The process handle.</param>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess(IntPtr handle)
{
var pbi = new Util();
int returnLength;
var status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
if (status != 0)
throw new Win32Exception(status);
try
{
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
// not found
return null;
}
}
}
}

View File

@ -1,35 +0,0 @@
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.CefHost")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RageCoop.Client.CefHost")]
[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("ba750e08-5e41-4b56-8ad5-875716d2ccea")]
// 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

@ -1,118 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props"
Condition="Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props')" />
<Import Project="..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props"
Condition="Exists('..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props')" />
<Import Project="..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props"
Condition="Exists('..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props')" />
<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>{BA750E08-5E41-4B56-8AD5-875716D2CCEA}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>RageCoop.Client.CefHost</RootNamespace>
<AssemblyName>RageCoop.Client.CefHost</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<OutDir>..\..\bin\$(Configuration)\Client\SubProcess</OutDir>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>
</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="BitmapUtil">
<HintPath>..\..\libs\BitmapUtil.dll</HintPath>
</Reference>
<Reference
Include="CefSharp, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
PrivateAssets="All">
<HintPath>..\..\packages\CefSharp.Common.106.0.290\lib\net452\CefSharp.dll</HintPath>
</Reference>
<Reference
Include="CefSharp.Core, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
PrivateAssets="All">
<HintPath>..\..\packages\CefSharp.Common.106.0.290\lib\net452\CefSharp.Core.dll</HintPath>
</Reference>
<Reference
Include="CefSharp.OffScreen, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
PrivateAssets="All">
<HintPath>..\..\packages\CefSharp.OffScreen.106.0.290\lib\net462\CefSharp.OffScreen.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Remoting" />
<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="CefController.cs" />
<Compile Include="CefProcessor.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props'))" />
<Error Condition="!Exists('..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props'))" />
<Error Condition="!Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets'))" />
</Target>
<Import Project="..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets"
Condition="Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets')" />
<PropertyGroup>
<PostBuildEvent>
if not exist "ref" mkdir "ref"
copy "$(TargetFileName)" "ref\$(TargetName).dll" /y
copy "CefSharp.OffScreen.dll" "ref\CefSharp.OffScreen.dll" /y
copy "CefSharp.dll" "ref\CefSharp.dll" /y
</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<StartAction>Program</StartAction>
<StartProgram>M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Client\SubProcess\RageCoop.Client.CefHost.exe</StartProgram>
</PropertyGroup>
</Project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="106.0.29" targetFramework="net48" />
<package id="cef.redist.x86" version="106.0.29" targetFramework="net48" />
<package id="CefSharp.Common" version="106.0.290" targetFramework="net48" />
<package id="CefSharp.OffScreen" version="106.0.290" targetFramework="net48" />
</packages>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,99 +0,0 @@
{
"Bullet": {
"2861067768": "VEHICLE_WEAPON_INSURGENT_MINIGUN",
"3683206664": "VEHICLE_WEAPON_TECHNICAL_MINIGUN",
"3048454573": "VEHICLE_WEAPON_AKULA_TURRET_SINGLE",
"476907586": "VEHICLE_WEAPON_AKULA_TURRET_DUAL",
"431576697": "VEHICLE_WEAPON_AKULA_MINIGUN",
"3405172033": "VEHICLE_WEAPON_ANNIHILATOR2_MINI",
"1000258817": "VEHICLE_WEAPON_BARRAGE_TOP_MINIGUN",
"525623141": "VEHICLE_WEAPON_BARRAGE_REAR_MINIGUN",
"1338760315": "VEHICLE_WEAPON_CARACARA_MINIGUN",
"490982948": "VEHICLE_WEAPON_DEATHBIKE_DUALMINIGUN",
"3909880809": "VEHICLE_WEAPON_DEATHBIKE2_MINIGUN_LASER",
"2600428406": "VEHICLE_WEAPON_DELUXO_MG",
"1416047217": "VEHICLE_WEAPON_DUNE_MINIGUN",
"3003147322": "VEHICLE_WEAPON_FLAMETHROWER",
"2182329506": "VEHICLE_WEAPON_FLAMETHROWER_SCIFI",
"855547631": "VEHICLE_WEAPON_HAVOK_MINIGUN",
"2263283790": "VEHICLE_WEAPON_POUNDER2_MINI",
"2431961420": "VEHICLE_WEAPON_SEASPARROW2_MINIGUN",
"2667462330": "VEHICLE_WEAPON_SPEEDO4_TURRET_MINI",
"3670375085": "VEHICLE_WEAPON_TAMPA_FIXEDMINIGUN",
"1744687076": "VEHICLE_WEAPON_TAMPA_DUALMINIGUN",
"1697521053": "VEHICLE_WEAPON_THRUSTER_MG",
"376489128": "VEHICLE_WEAPON_TULA_MINIGUN",
"4109257098": "VEHICLE_WEAPON_RCTANK_FLAME",
"3959029566": "VEHICLE_WEAPON_CANNON_BLAZER",
"1119849093": "WEAPON_MINIGUN",
"4256881901": "WEAPON_DIGISCANNER",
"1186503822": "VEHICLE_WEAPON_PLAYER_BUZZARD",
"3056410471": "WEAPON_RAYMINIGUN",
"729375873": "VEHICLE_WEAPON_TURRET_LIMO",
"2756787765": "VEHICLE_WEAPON_TURRET_VALKYRIE",
"50118905": "VEHICLE_WEAPON_RUINER_BULLET"
},
"Lazer": {
"955522731": "VEHICLE_WEAPON_STRIKEFORCE_CANNON",
"539292904": "WEAPON_EXPLOSION",
"1638077257": "VEHICLE_WEAPON_PLAYER_SAVAGE"
},
"Others": {
"3441901897": "WEAPON_BATTLEAXE",
"4192643659": "WEAPON_BOTTLE",
"2460120199": "WEAPON_DAGGER",
"2343591895": "WEAPON_FLASHLIGHT",
"3794977420": "WEAPON_GARBAGEBAG",
"3494679629": "WEAPON_HANDCUFFS",
"4191993645": "WEAPON_HATCHET",
"3638508604": "WEAPON_KNUCKLE",
"3713923289": "WEAPON_MACHETE",
"2484171525": "WEAPON_POOLCUE",
"2725352035": "WEAPON_UNARMED",
"4194021054": "WEAPON_ANIMAL",
"148160082": "WEAPON_COUGAR",
"2578778090": "WEAPON_KNIFE",
"1737195953": "WEAPON_NIGHTSTICK",
"1317494643": "WEAPON_HAMMER",
"2508868239": "WEAPON_BAT",
"1141786504": "WEAPON_GOLFCLUB",
"2227010557": "WEAPON_CROWBAR",
"101631238": "WEAPON_FIREEXTINGUISHER",
"883325847": "WEAPON_PETROLCAN",
"2294779575": "WEAPON_BRIEFCASE",
"28811031": "WEAPON_BRIEFCASE_02",
"3450622333": "VEHICLE_WEAPON_SEARCHLIGHT",
"3530961278": "VEHICLE_WEAPON_RADAR",
"1223143800": "WEAPON_BARBED_WIRE",
"4284007675": "WEAPON_DROWNING",
"1936677264": "WEAPON_DROWNING_IN_VEHICLE",
"2339582971": "WEAPON_BLEEDING",
"2461879995": "WEAPON_ELECTRIC_FENCE",
"3452007600": "WEAPON_FALL",
"910830060": "WEAPON_EXHAUSTION",
"3425972830": "WEAPON_HIT_BY_WATER_CANNON",
"133987706": "WEAPON_RAMMED_BY_CAR",
"2741846334": "WEAPON_RUN_OVER_BY_CAR",
"341774354": "WEAPON_HELI_CRASH",
"2971687502": "VEHICLE_WEAPON_ROTORS",
"3750660587": "WEAPON_FIRE",
"3854032506": "WEAPON_ANIMAL_RETRIEVER",
"3146768957": "WEAPON_SMALL_DOG",
"743550225": "WEAPON_TIGER_SHARK",
"3030980043": "WEAPON_HAMMERHEAD_SHARK",
"4198358245": "WEAPON_KILLER_WHALE",
"861723357": "WEAPON_BOAR",
"1205296881": "WEAPON_PIG",
"1161062353": "WEAPON_COYOTE",
"4106648222": "WEAPON_DEER",
"955837630": "WEAPON_HEN",
"2793925639": "WEAPON_RABBIT",
"3799318422": "WEAPON_CAT",
"94548753": "WEAPON_COW",
"940833800": "WEAPON_STONE_HATCHET",
"3756226112": "WEAPON_SWITCHBLADE",
"419712736": "WEAPON_WRENCH",
"406929569": "WEAPON_FERTILIZERCAN",
"3126027122": "WEAPON_HAZARDCAN"
}
}

View File

@ -1,329 +0,0 @@
public enum WeaponHash : uint
{
Advancedrifle = 0xAF113F99,
AirDefenceGun = 0x2C082D7D,
AirstrikeRocket = 0x13579279,
Animal = 0xF9FBAEBE,
AnimalRetriever = 0xE5B7DE7A,
Appistol = 0x22D8FE39,
ArenaHomingMissile = 0x648A81D0,
ArenaMachineGun = 0x34FDFF66,
Assaultrifle = 0xBFEFFF6D,
AssaultrifleMk2 = 0x394F415C,
Assaultshotgun = 0xE284C527,
Assaultsmg = 0xEFE7E2DF,
Autoshotgun = 0x12E82D3D,
Ball = 0x23C9F95C,
BarbedWire = 0x48E7B178,
Bat = 0x958A4A8F,
Battleaxe = 0xCD274149,
BirdCrap = 0x6D5E2801,
Bleeding = 0x8B7333FB,
Boar = 0x335CDADD,
Bottle = 0xF9E6AA4B,
Briefcase = 0x88C78EB7,
Briefcase02 = 0x1B79F17,
Bullpuprifle = 0x7F229F94,
BullpuprifleMk2 = 0x84D6FAFD,
Bullpupshotgun = 0x9D61E50F,
Bzgas = 0xA0973D5E,
Carbinerifle = 0x83BF0278,
CarbinerifleMk2 = 0xFAD1F1C9,
Cat = 0xE274FF96,
Ceramicpistol = 0x2B5EF5EC,
Combatmg = 0x7FD62962,
CombatmgMk2 = 0xDBBD7280,
Combatpdw = 0xA3D4D34,
Combatpistol = 0x5EF9FEC4,
Combatshotgun = 0x5A96BA4,
Compactlauncher = 0x781FE4A,
Compactrifle = 0x624FE830,
Cougar = 0x8D4BE52,
Cow = 0x5A2B311,
Coyote = 0x453467D1,
Crowbar = 0x84BD7BFD,
Dagger = 0x92A27487,
Dbshotgun = 0xEF951FBB,
Deer = 0xF4C67A9E,
Digiscanner = 0xFDBADCED,
Doubleaction = 0x97EA20B8,
Drowning = 0xFF58C4FB,
DrowningInVehicle = 0x736F5990,
ElectricFence = 0x92BD4EBB,
Emplauncher = 0xDB26713A,
Exhaustion = 0x364A29EC,
Explosion = 0x2024F4E8,
Fall = 0xCDC174B0,
Fertilizercan = 0x184140A1,
Fire = 0xDF8E89EB,
Fireextinguisher = 0x60EC506,
Firework = 0x7F7497E5,
Flare = 0x497FACC3,
Flaregun = 0x47757124,
Flashlight = 0x8BB05FD7,
Gadgetpistol = 0x57A4368C,
Garbagebag = 0xE232C28C,
Golfclub = 0x440E4788,
Grenade = 0x93E220BD,
Grenadelauncher = 0xA284510B,
GrenadelauncherSmoke = 0x4DD2DC56,
Gusenberg = 0x61012683,
Hammer = 0x4E875F73,
HammerheadShark = 0xB4A915CB,
Handcuffs = 0xD04C944D,
Hatchet = 0xF9DCBF2D,
Hazardcan = 0xBA536372,
Heavypistol = 0xD205520E,
Heavyrifle = 0xC78D71B4,
Heavyshotgun = 0x3AABBBAA,
Heavysniper = 0xC472FE2,
HeavysniperMk2 = 0xA914799,
HeliCrash = 0x145F1012,
Hen = 0x38F8ECBE,
HitByWaterCannon = 0xCC34325E,
Hominglauncher = 0x63AB0442,
KillerWhale = 0xFA3DDCE5,
Knife = 0x99B507EA,
Knuckle = 0xD8DF3C3C,
Machete = 0xDD5DF8D9,
Machinepistol = 0xDB1AA450,
Marksmanpistol = 0xDC4DB296,
Marksmanrifle = 0xC734385A,
MarksmanrifleMk2 = 0x6A6C02E0,
Mg = 0x9D07F764,
Microsmg = 0x13532244,
Militaryrifle = 0x9D1F17E6,
Minigun = 0x42BF8A85,
Minismg = 0xBD248B55,
Molotov = 0x24B17070,
Musket = 0xA89CB99E,
Navyrevolver = 0x917F6C8C,
Nightstick = 0x678B81B1,
PassengerRocket = 0x166218FF,
Petrolcan = 0x34A67B97,
Pig = 0x47D75EF1,
Pipebomb = 0xBA45E8B8,
Pistol = 0x1B06D571,
Pistol50 = 0x99AEEB3B,
PistolMk2 = 0xBFE256D4,
Poolcue = 0x94117305,
Proxmine = 0xAB564B93,
Pumpshotgun = 0x1D073A89,
PumpshotgunMk2 = 0x555AF99A,
Rabbit = 0xA687EC07,
Railgun = 0x6D544C99,
RammedByCar = 0x7FC7D7A,
Raycarbine = 0x476BF155,
Rayminigun = 0xB62D1F67,
Raypistol = 0xAF3696A1,
Remotesniper = 0x33058E22,
Revolver = 0xC1B3C3D1,
RevolverMk2 = 0xCB96392F,
Rpg = 0xB1CA77B1,
RunOverByCar = 0xA36D413E,
Sawnoffshotgun = 0x7846A318,
SmallDog = 0xBB8FE23D,
Smg = 0x2BE6766B,
SmgMk2 = 0x78A97CD0,
Smokegrenade = 0xFDBC8A50,
Sniperrifle = 0x5FC3C11,
Snowball = 0x787F0BB,
Snspistol = 0xBFD21232,
SnspistolMk2 = 0x88374054,
Specialcarbine = 0xC0A3098D,
SpecialcarbineMk2 = 0x969C3D67,
Stickybomb = 0x2C3731D9,
Stinger = 0x687652CE,
StoneHatchet = 0x3813FC08,
Stungun = 0x3656C8C1,
StungunMp = 0x45CD9CF3,
Switchblade = 0xDFE37640,
TigerShark = 0x2C51AD11,
Tranquilizer = 0x32A888BD,
Unarmed = 0xA2719263,
VehicleRocket = 0xBEFDC581,
Vintagepistol = 0x83839C4,
Wrench = 0x19044EE0,
}
public enum VehicleWeaponHash : uint
{
Invalid = 0xFFFFFFFF,
AkulaBarrage = 0x880D14F2,
AkulaMinigun = 0x19B95679,
AkulaMissile = 0x7CBE304C,
AkulaTurretDual = 0x1C6D0842,
AkulaTurretSingle = 0xB5B3B9AD,
Annihilator2Barrage = 0x35D8CC90,
Annihilator2Mini = 0xCAF6CD41,
Annihilator2Missile = 0xC76BC6B7,
ApcCannon = 0x138F71D8,
ApcMg = 0xB56E4E4,
ApcMissile = 0x44A56189,
ArdentMg = 0xC44E4341,
AvengerCannon = 0x9867203B,
BarrageRearGl = 0xA44C228D,
BarrageRearMg = 0x47894765,
BarrageRearMinigun = 0x1F545F65,
BarrageTopMg = 0xF7498994,
BarrageTopMinigun = 0x3B9EBD01,
Bomb = 0x9AF0B90C,
BombCluster = 0xD28BCA3,
BombGas = 0x5540A91E,
BombIncendiary = 0x6AF7A717,
BombStandardWide = 0x6EA548D0,
BombushkaCannon = 0xD8443A59,
BombushkaDualmg = 0x2C2B2D58,
BombWater = 0xF444C4C8,
Bruiser250calLaser = 0x3D6A0196,
Bruiser50cal = 0xD73DC601,
Brutus250calLaser = 0x68C7A4C3,
Brutus50cal = 0xEB5E5C0A,
CannonBlazer = 0xEBF9FF3E,
CaracaraMg = 0x6C516BA8,
CaracaraMinigun = 0x4FCBDC7B,
ChernoMissile = 0xA247D03E,
CometMg = 0xEAA835F3,
Deathbike2MinigunLaser = 0xE90C0BE9,
DeathbikeDualminigun = 0x1D43CE24,
DeluxoMg = 0x9AFF6376,
DeluxoMissile = 0xB4F96934,
DogfighterMg = 0x5F1834E2,
DogfighterMissile = 0xCA46F87D,
Dominator450cal = 0xF80C9B0F,
Dominator550calLaser = 0xB4246A5F,
DuneGrenadelauncher = 0xA0FC710D,
DuneMg = 0xD11507CF,
DuneMinigun = 0x54672A71,
EnemyLaser = 0x5D6660AB,
Flamethrower = 0xB300643A,
FlamethrowerScifi = 0x8213B4A2,
Granger2Mg = 0xEAE2E19A,
HackerMissile = 0x766FF7B1,
HackerMissileHoming = 0x77EACF96,
HalftrackDualmg = 0x4F6384FB,
HalftrackQuadmg = 0x491B2E74,
HavokMinigun = 0x32FE9EEF,
HunterBarrage = 0x2ED14835,
HunterCannon = 0x2A00AB1A,
HunterMg = 0x42BA80A7,
HunterMissile = 0x924A5F5,
Impaler250cal = 0x5F565C09,
Impaler350calLaser = 0x8CBDFC88,
Imperator250calLaser = 0x7817C526,
Imperator50cal = 0xB662C67B,
InsurgentMinigun = 0xAA886DF8,
Issi450cal = 0x7648E34D,
Issi550calLaser = 0x767F6925,
Jb700Mg = 0x373AD53C,
KhanjaliCannon = 0x1E3ACFA0,
KhanjaliCannonHeavy = 0x838B716D,
KhanjaliGl = 0x178605E2,
KhanjaliMg = 0x2A6F8E1D,
KosatkaTorpedo = 0x62E2140E,
MenacerMg = 0xDFCAF8A4,
MicrolightMg = 0xC4E0216C,
Mine = 0x59EAE9A4,
MineEmp = 0x69E10D60,
MineEmpRc = 0x5454B4C6,
MineKinetic = 0x3C09584E,
MineKineticRc = 0x252AF560,
MineSlick = 0x56FACAC7,
MineSlickRc = 0x84E87B17,
MineSpike = 0xD96DA06C,
MineSpikeRc = 0x7C2AFE51,
MineTar = 0xF4418BA0,
MineTarRc = 0x7D3474D6,
MobileopsCannon = 0xE53E69A4,
MogulDualnose = 0xE5F3AE2F,
MogulDualturret = 0xBA277C01,
MogulNose = 0xF6189F4A,
MogulTurret = 0xE2FD135E,
Monster3Glkin = 0xE5AE53DD,
MortarExplosive = 0xA1A8CCD2,
MortarKinetic = 0x632A22FD,
Mule4Mg = 0x84558727,
Mule4Missile = 0x4772F84B,
Mule4TurretGl = 0xDD124A65,
NightsharkMg = 0xA61AC574,
NoseTurretValkyrie = 0x4170E491,
Oppressor2Cannon = 0xD64D3469,
Oppressor2Mg = 0xE2451DD6,
Oppressor2Missile = 0x753A78F1,
OppressorMg = 0xD9322EDD,
OppressorMissile = 0x8BB7C63E,
Paragon2Mg = 0x2CAC4286,
PatrolboatDualmg = 0x4C2FB4E9,
PlaneRocket = 0xCF0896E0,
PlayerBullet = 0x4B139B2D,
PlayerBuzzard = 0x46B89C8E,
PlayerHunter = 0x9F1A91DE,
PlayerLaser = 0xEFFD014B,
PlayerLazer = 0xE2822A29,
PlayerSavage = 0x61A31349,
Pounder2Barrage = 0x926B8CE4,
Pounder2Gl = 0x9318FF16,
Pounder2Mini = 0x86E6F84E,
Pounder2Missile = 0x9A8EA9A,
Radar = 0xD276317E,
RctankFlame = 0xF4EE498A,
RctankGun = 0x52FCA619,
RctankLazer = 0x57F22C50,
RctankRocket = 0x76F744CB,
RevolterMg = 0xBD5E626A,
RogueCannon = 0xE72ABBC2,
RogueMg = 0x97273CD,
RogueMissile = 0x6C88E47D,
Rotors = 0xB1205A4E,
RuinerBullet = 0x2FCC0F9,
RuinerRocket = 0x50DC6AB,
SavestraMg = 0xEB41E84E,
Scarab250calLaser = 0xE22DEDCC,
Scarab50cal = 0x217FEF28,
ScramjetMg = 0xDCE6112,
ScramjetMissile = 0xBCE908DB,
SeabreezeMg = 0x51B8D4E8,
Searchlight = 0xCDAC517D,
Seasparrow2Minigun = 0x90F4C94C,
Slamvan450cal = 0x3AAB6E6B,
Slamvan550calLaser = 0x519543AE,
SpaceRocket = 0xF8A3939F,
Speedo4Mg = 0xC7FCF93C,
Speedo4TurretMg = 0xD6561141,
Speedo4TurretMini = 0x9EFE3EBA,
StrikeforceBarrage = 0x39BC6683,
StrikeforceCannon = 0x38F41EAB,
StrikeforceMissile = 0x1EF01D8A,
SubcarMg = 0x461DDDB0,
SubcarMissile = 0xD4897C0E,
SubcarTorpedo = 0xE783C3BA,
SubMissileHoming = 0xAAE74AC1,
TampaDualminigun = 0x67FDCFE4,
TampaFixedminigun = 0xDAC57AAD,
TampaMissile = 0x9E5840A2,
TampaMortar = 0x3C83C410,
Tank = 0x73F7C04B,
TechnicalMinigun = 0xDB894608,
ThrusterMg = 0x652E1D9D,
ThrusterMissile = 0x4635DD15,
TrailerDualaa = 0x808C4D4C,
TrailerMissile = 0x145599F7,
TrailerQuadmg = 0x4711B02C,
TulaDualmg = 0xB0D15C0B,
TulaMg = 0x488BD081,
TulaMinigun = 0x1670C4A8,
TulaNosemg = 0x419D8E15,
TurretBoxville = 0xB54F4918,
TurretDinghy550cal = 0xB3B155FD,
TurretInsurgent = 0x44DB5498,
TurretLimo = 0x2B796481,
TurretPatrolboat50cal = 0xDF4EA041,
TurretTechnical = 0x7FD2EA0B,
TurretValkyrie = 0xA4513E35,
VigilanteMg = 0xF4077EE7,
VigilanteMissile = 0x504DA665,
ViserisMg = 0x87A02E06,
VolatolDualmg = 0x4497AC40,
Zr380250calLaser = 0x220093BC,
Zr38050cal = 0x6AB93C82,
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -1,52 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoAotCompile>true</NoAotCompile>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AllowedReferenceRelatedFileExtensions>
-
</AllowedReferenceRelatedFileExtensions>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<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="..\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>
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile; runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -1,9 +0,0 @@
<?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

@ -1,24 +0,0 @@
using System;
namespace GTA
{
internal class Console
{
private static readonly SHVDN.Console console = AppDomain.CurrentDomain.GetData("Console") as SHVDN.Console;
public static void Warning(object format, params object[] objects)
{
console.PrintInfo("[~o~WARNING~w~] ", format.ToString(), objects);
}
public static void Error(object format, params object[] objects)
{
console.PrintError("[~r~ERROR~w~] ", format.ToString(), objects);
}
public static void Info(object format, params object[] objects)
{
console.PrintWarning("[~b~INFO~w~] ", format.ToString(), objects);
}
}
}

View File

@ -1,216 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using GTA.UI;
using SHVDN;
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);
// Delete API assemblies
Directory.GetFiles(dir, "ScriptHookVDotNet*", SearchOption.AllDirectories).ToList()
.ForEach(x => File.Delete(x));
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"));
// Copy to target domain base directory
// Delete loader assembly
var loaderPath = Path.Combine(dir, Path.GetFileName(typeof(LoaderContext).Assembly.Location));
if (File.Exists(loaderPath)) File.Delete(loaderPath);
var context = (LoaderContext)newDomain.AppDomain.CreateInstanceFromAndUnwrap(
typeof(LoaderContext).Assembly.Location,
typeof(LoaderContext).FullName, false,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { }
, null, null);
newDomain.AppDomain.SetData("RageCoop.Client.LoaderContext", context);
newDomain.Start();
_loadedDomains.TryAdd(dir, context);
return _loadedDomains[dir];
}
catch (Exception ex)
{
Notification.Show(ex.ToString());
Console.Error(ex);
if (newDomain != null) ScriptDomain.Unload(newDomain);
throw;
}
}
}
private static Assembly ResolveLoader(object sender, ResolveEventArgs args)
{
Log.Message(Log.Level.Debug, "resolving assembly " + args.Name);
if (args.Name == typeof(LoaderContext).Assembly.GetName().Name) return typeof(LoaderContext).Assembly;
return null;
}
public static void Unload(LoaderContext domain)
{
lock (_loadedDomains)
{
var name = domain.CurrentDomain.Name;
Console.Info("Unloading domain: " + name);
if (!_loadedDomains.TryRemove(domain.BaseDirectory.ToLower(), out _))
throw new Exception("Failed to remove domain from list");
domain.Dispose();
ScriptDomain.Unload(domain.CurrentDomain);
Console.Info("Unloaded domain: " + name);
}
}
public static void Unload(string dir)
{
lock (_loadedDomains)
{
Unload(_loadedDomains[Path.GetFullPath(dir).ToLower()]);
}
}
public static void TickAll()
{
lock (_loadedDomains)
{
foreach (var c in _loadedDomains.Values) c.DoTick();
}
}
public static void KeyEventAll(Keys keys, bool status)
{
lock (_loadedDomains)
{
foreach (var c in _loadedDomains.Values) c.DoKeyEvent(keys, status);
}
}
public static void UnloadAll()
{
lock (_loadedDomains)
{
foreach (var d in _loadedDomains.Values.ToArray()) Unload(d);
}
}
#endregion
#region LOAD-CONTEXT
private readonly Action _domainDoTick;
private readonly Action<Keys, bool> _domainDoKeyEvent;
private LoaderContext()
{
AppDomain.CurrentDomain.DomainUnload += (s, e) => Dispose();
var tickMethod = typeof(ScriptDomain).GetMethod("DoTick", BindingFlags.Instance | BindingFlags.NonPublic);
var doKeyEventMethod =
typeof(ScriptDomain).GetMethod("DoKeyEvent", BindingFlags.Instance | BindingFlags.NonPublic);
// Create delegates to avoid using reflection to call method each time, which is slow
_domainDoTick = (Action)Delegate.CreateDelegate(typeof(Action), CurrentDomain, tickMethod);
_domainDoKeyEvent =
(Action<Keys, bool>)Delegate.CreateDelegate(typeof(Action<Keys, bool>), CurrentDomain,
doKeyEventMethod);
Console.Info(
$"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
}
public static ScriptDomain PrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain;
public static LoaderContext CurrentContext =>
(LoaderContext)AppDomain.CurrentDomain.GetData("RageCoop.Client.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 therefore cannot be unloaded automatically");
CurrentContext.UnloadRequested = true;
}
public override object InitializeLifetimeService()
{
return null;
}
public void DoTick()
{
_domainDoTick();
}
public void DoKeyEvent(Keys key, bool status)
{
_domainDoKeyEvent(key, status);
}
public void Dispose()
{
lock (this)
{
if (PrimaryDomain == null) return;
AppDomain.CurrentDomain.SetData("Primary", null);
}
}
#endregion
}
}

View File

@ -1,51 +0,0 @@
using System;
using System.IO;
using GTA;
using GTA.UI;
using SHVDN;
using Script = GTA.Script;
namespace RageCoop.Client.Loader
{
[ScriptAttributes(Author = "RageCoop", NoScriptThread = true,
SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
public class Main : Script
{
private static readonly string GameDir = Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName;
private static readonly string ScriptsLocation = Path.Combine(GameDir, "RageCoop", "Scripts");
private bool _loaded;
public Main()
{
if (LoaderContext.PrimaryDomain != null) return;
Tick += OnTick;
KeyDown += (s, e) => LoaderContext.KeyEventAll(e.KeyCode, true);
KeyUp += (s, e) => LoaderContext.KeyEventAll(e.KeyCode, false);
Aborted += (s, e) => LoaderContext.UnloadAll();
}
private void OnTick(object sender, EventArgs e)
{
if (!_loaded)
{
_loaded = !Game.IsLoading;
return;
}
LoaderContext.CheckForUnloadRequest();
if (!LoaderContext.IsLoaded(ScriptsLocation))
{
if (!File.Exists(Path.Combine(ScriptsLocation, "RageCoop.Client.dll")))
{
Notification.Show("~r~Main assembly is missing, please re-install the client");
Abort();
return;
}
LoaderContext.Load(ScriptsLocation);
}
LoaderContext.TickAll();
}
}
}

View File

@ -1,35 +0,0 @@
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

@ -1,58 +0,0 @@
<?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>
<OutPutPath>..\..\bin\$(Configuration)\Client\Loader</OutPutPath>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ScriptHookVDotNet3, Version=3.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>True</SpecificVersion>
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.asi</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="Console.cs" />
<Compile Include="LoaderContext.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,47 +0,0 @@
namespace RageCoop.Client.Scripting
{
public static unsafe partial class APIBridge
{
public static class Config
{
public static System.String Username => GetConfig<System.String>("Username");
public static System.Boolean EnableAutoRespawn => GetConfig<System.Boolean>("EnableAutoRespawn");
public static GTA.BlipColor BlipColor => GetConfig<GTA.BlipColor>("BlipColor");
public static GTA.BlipSprite BlipSprite => GetConfig<GTA.BlipSprite>("BlipSprite");
public static System.Single BlipScale => GetConfig<System.Single>("BlipScale");
public static System.Boolean ShowPlayerNameTag => GetConfig<System.Boolean>("ShowPlayerNameTag");
}
#region PROPERTIES
public static System.Int32 LocalPlayerID => GetProperty<System.Int32>("LocalPlayerID");
public static System.Boolean IsOnServer => GetProperty<System.Boolean>("IsOnServer");
public static System.Net.IPEndPoint ServerEndPoint => GetProperty<System.Net.IPEndPoint>("ServerEndPoint");
public static System.Boolean IsMenuVisible => GetProperty<System.Boolean>("IsMenuVisible");
public static System.Boolean IsChatFocused => GetProperty<System.Boolean>("IsChatFocused");
public static System.Boolean IsPlayerListVisible => GetProperty<System.Boolean>("IsPlayerListVisible");
public static System.Version CurrentVersion => GetProperty<System.Version>("CurrentVersion");
public static System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo> Players => GetProperty<System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo>>("Players");
#endregion
#region FUNCTIONS
public static void Connect(System.String address) => InvokeCommand("Connect", address);
public static void Disconnect() => InvokeCommand("Disconnect");
public static System.Collections.Generic.List<RageCoop.Core.ServerInfo> ListServers() => InvokeCommand<System.Collections.Generic.List<RageCoop.Core.ServerInfo>>("ListServers");
public static void LocalChatMessage(System.String from, System.String message) => InvokeCommand("LocalChatMessage", from, message);
public static void SendChatMessage(System.String message) => InvokeCommand("SendChatMessage", message);
public static RageCoop.Client.Scripting.ClientResource GetResource(System.String name) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResource", name);
public static RageCoop.Client.Scripting.ClientResource GetResourceFromPath(System.String path) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResourceFromPath", path);
public static System.Object GetConfig(System.String name) => InvokeCommand<System.Object>("GetConfig", name);
public static void SetConfig(System.String name, System.String jsonVal) => InvokeCommand("SetConfig", name, jsonVal);
public static void RegisterCustomEventHandler(RageCoop.Core.Scripting.CustomEventHash hash, RageCoop.Core.Scripting.CustomEventHandler handler) => InvokeCommand("RegisterCustomEventHandler", hash, handler);
#endregion
}
}

View File

@ -1,161 +0,0 @@
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("RageCoop.Client")] // For debugging
namespace RageCoop.Client.Scripting
{
public static unsafe partial class APIBridge
{
static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]);
static readonly List<CustomEventHandler> _handlers = new();
static APIBridge()
{
if (SHVDN.Core.GetPtr == null)
throw new InvalidOperationException("Game not running");
foreach(var fd in typeof(APIBridge).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
var importAttri = fd.GetCustomAttribute<ApiImportAttribute>();
if (importAttri == null)
continue;
importAttri.EntryPoint ??= fd.Name;
var key = $"RageCoop.Client.Scripting.API.{importAttri.EntryPoint}";
var fptr = SHVDN.Core.GetPtr(key);
if (fptr == default)
throw new KeyNotFoundException($"Failed to find function pointer: {key}");
fd.SetValue(null,fptr);
}
}
/// <summary>
/// Copy content of string to a sequential block of memory
/// </summary>
/// <param name="strs"></param>
/// <returns>Pointer to the start of the block, can be used as argv</returns>
/// <remarks>Call <see cref="Marshal.FreeHGlobal(nint)"/> with the returned pointer when finished using</remarks>
internal static char** StringArrayToMemory(string[] strs)
{
var argc = strs.Length;
var cbSize = sizeof(IntPtr) * argc + strs.Sum(s => (s.Length + 1) * sizeof(char));
var result = (char**)Marshal.AllocHGlobal(cbSize);
var pCurStr = (char*)(result + argc);
for (int i = 0; i < argc; i++)
{
result[i] = pCurStr;
var len = strs[i].Length;
var cbStrSize = (len + 1) * sizeof(char); // null terminator
fixed (char* pStr = strs[i])
{
System.Buffer.MemoryCopy(pStr, pCurStr, cbStrSize, cbStrSize);
}
pCurStr += len + 1;
}
return result;
}
internal static void InvokeCommand(string name, params object[] args)
=> InvokeCommandAsJson(name, args);
internal static T InvokeCommand<T>(string name, params object[] args)
=> JsonDeserialize<T>(InvokeCommandAsJson(name, args));
/// <summary>
/// Invoke command and get the return value as json
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
/// <returns>The json representation of returned object</returns>
/// <exception cref="Exception"></exception>
internal static string InvokeCommandAsJson(string name, params object[] args)
{
var argc = args.Length;
var argv = StringArrayToMemory(args.Select(JsonSerialize).ToArray());
try
{
fixed(char* pName = name)
{
var resultLen = InvokeCommandAsJsonUnsafe(pName, argc, argv);
if (resultLen == 0)
throw new Exception(GetLastResult());
return GetLastResult();
}
}
finally
{
Marshal.FreeHGlobal((IntPtr)argv);
}
}
public static string GetLastResult()
{
var countCharsRequired = GetLastResultLenInChars() + 1;
if (countCharsRequired > _resultBuf.Value.Length)
{
_resultBuf.Value = new char[countCharsRequired];
}
var cbBufSize = _resultBuf.Value.Length * sizeof(char);
fixed (char* pBuf = _resultBuf.Value)
{
if (GetLastResultUnsafe(pBuf, cbBufSize) > 0)
{
return new string(pBuf);
}
return null;
}
}
public static void SendCustomEvent(CustomEventHash hash, params object[] args)
=> SendCustomEvent(CustomEventFlags.None, hash, args);
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args)
{
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);
SendCustomEventUnsafe(flags, hash, writer.Address, writer.Position);
}
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
internal static string GetPropertyAsJson(string name) => InvokeCommandAsJson("GetProperty", name);
internal static string GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
internal static T GetProperty<T>(string name) => JsonDeserialize<T>(GetPropertyAsJson(name));
internal static T GetConfig<T>(string name) => JsonDeserialize<T>(GetConfigAsJson(name));
internal static void SetProperty(string name, object val) => InvokeCommand("SetProperty", name, val);
internal static void SetConfig(string name, object val) => InvokeCommand("SetConfig", name, val);
[ApiImport]
public static delegate* unmanaged<char*, CustomEventHash> GetEventHash;
[ApiImport]
private static delegate* unmanaged<char*,void> SetLastResult;
[ApiImport(EntryPoint = "GetLastResult")]
private static delegate* unmanaged<char*, int, int> GetLastResultUnsafe;
[ApiImport(EntryPoint = "InvokeCommand")]
private static delegate* unmanaged<char*, int, char**, int> InvokeCommandAsJsonUnsafe;
[ApiImport(EntryPoint = "SendCustomEvent")]
private static delegate* unmanaged<CustomEventFlags, int, byte*, int, void> SendCustomEventUnsafe;
[ApiImport]
private static delegate* unmanaged<int> GetLastResultLenInChars;
[ApiImport]
public static delegate* unmanaged<LogLevel, char*, void> LogEnqueue;
}
[AttributeUsage(AttributeTargets.Field)]
class ApiImportAttribute : Attribute
{
public string EntryPoint;
}
}

View File

@ -1,28 +0,0 @@
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
public class ClientFile : ResourceFile
{
public ClientFile() {
GetStream = GetStreamMethod;
}
[JsonInclude]
public string FullPath { get; internal set; }
Stream GetStreamMethod()
{
if (IsDirectory)
{
return File.Open(FullPath, FileMode.Open);
}
throw new InvalidOperationException("Cannot open directory as file");
}
}
}

View File

@ -1,49 +0,0 @@
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
/// <summary>
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
[JsonInclude]
public string Name { get; internal set; }
/// <summary>
/// Directory where the scripts is loaded from
/// </summary>
[JsonInclude]
public string ScriptsDirectory { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
[JsonInclude]
public string DataFolder { get; internal set; }
/// <summary>
/// Get the <see cref="ClientFile" /> where this script is loaded from.
/// </summary>
[JsonInclude]
public Dictionary<string, ClientFile> Files { get; internal set; } = new Dictionary<string, ClientFile>();
/// <summary>
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
/// </summary>
[JsonIgnore]
public ResourceLogger Logger => ResourceLogger.Default;
}
}

View File

@ -1,62 +0,0 @@
using GTA;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace RageCoop.Client.Scripting
{
[JsonDontSerialize]
[ScriptAttributes(NoDefaultInstance = true)]
public abstract class ClientScript : Script
{
readonly ConcurrentQueue<Func<bool>> _jobQueue = new();
readonly Queue<Func<bool>> _reAdd = new();
public ClientScript()
{
var dir = SHVDN.Core.CurrentDirectory;
CurrentResource = APIBridge.GetResourceFromPath(dir);
if (CurrentResource == null)
throw new Exception("No resource associated with this script is found");
CurrentFile = CurrentResource.Files.Values.FirstOrDefault(x => x?.FullPath?.ToLower() == FilePath?.ToLower());
if (CurrentFile == null)
{
Logger.Warning("No file associated with curent script was found");
}
}
protected void QueueAction(Func<bool> action) => _jobQueue.Enqueue(action);
protected void QueueAction(Action action) => QueueAction(() => { action(); return true; });
protected override void OnTick()
{
base.OnTick();
DoQueuedJobs();
}
private void DoQueuedJobs()
{
while (_reAdd.TryDequeue(out var toAdd))
_jobQueue.Enqueue(toAdd);
while (_jobQueue.TryDequeue(out var job))
{
if (!job())
_reAdd.Enqueue(job);
}
}
/// <summary>
/// Get the <see cref="ClientFile" /> instance where this script is loaded from.
/// </summary>
public ClientFile CurrentFile { get; }
/// <summary>
/// Get the <see cref="ClientResource" /> that this script belongs to.
/// </summary>
public ClientResource CurrentResource { get; }
/// <summary>
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
/// </summary>
public ResourceLogger Logger => CurrentResource.Logger;
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
public class PlayerInfo
{
public byte HolePunchStatus { get; internal set; }
public bool IsHost { get; internal set; }
public string Username { get; internal set; }
public int ID { get; internal set; }
public int EntityHandle { get; internal set; }
public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; }
public float Ping { get; internal set; }
public float PacketTravelTime { get; internal set; }
public bool DisplayNameTag { get; internal set; }
public bool HasDirectConnection { get; internal set; }
}
}

View File

@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<OutDir>..\..\bin\API</OutDir>
<DocumentationFile>..\..\bin\API\RageCoop.Client.Scripting.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
public class ResourceLogger : Core.Logger
{
public static readonly ResourceLogger Default = new();
public ResourceLogger()
{
FlushImmediately = true;
OnFlush += FlushToMainModule;
}
private unsafe void FlushToMainModule(LogLine line, string fomatted)
{
fixed (char* pMsg = line.Message)
{
APIBridge.LogEnqueue(line.LogLevel, pMsg);
}
}
}
}

View File

@ -1,13 +0,0 @@
global using static RageCoop.Core.Shared;
global using static RageCoop.Client.Scripting.Shared;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
internal class Shared
{
}
}

View File

@ -1,92 +0,0 @@

namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
public class ClientSettings
{
/// <summary>
/// LogLevel for RageCoop.
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 1;
/// <summary>
/// Don't use it!
/// </summary>
public string Username { get; set; } = "Player";
/// <summary>
/// The password used to authenticate when connecting to a server.
/// </summary>
public string Password { get; set; } = "";
/// <summary>
/// Don't use it!
/// </summary>
public string LastServerAddress { get; set; } = "127.0.0.1:4499";
/// <summary>
/// Don't use it!
/// </summary>
public string MasterServer { get; set; } = "https://masterserver.ragecoop.online/";
/// <summary>
/// Don't use it!
/// </summary>
public bool FlipMenu { get; set; } = false;
/// <summary>
/// Don't use it!
/// </summary>
public bool Voice { get; set; } = false;
/// <summary>
/// The key to open menu
/// </summary>
public Keys MenuKey { get; set; } = Keys.F7;
/// <summary>
/// The key to enter a vehicle as passenger.
/// </summary>
public Keys PassengerKey { get; set; } = Keys.G;
/// <summary>
/// Disable world NPC traffic, mission entities won't be affected
/// </summary>
public bool DisableTraffic { get; set; } = true;
/// <summary>
/// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed.
/// </summary>
public bool DisableAlternatePause { get; set; } = true;
/// <summary>
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary>
public int WorldVehicleSoftLimit { get; set; } = 20;
/// <summary>
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary>
public int WorldPedSoftLimit { get; set; } = 30;
/// <summary>
/// Show the owner name of the entity you're aiming at
/// </summary>
public bool ShowEntityOwnerName { get; set; } = false;
/// <summary>
/// Show other player's nametag on your screen, only effective if server didn't disable nametag display
/// </summary>
public bool ShowPlayerNameTag { get; set; } = true;
/// <summary>
/// Show other player's blip on map, can be overridden by server resource
/// </summary>
public bool ShowPlayerBlip { get; set; } = true;
}
}

View File

@ -1,93 +0,0 @@
using System;
using System.Drawing;
using GTA;
using RageCoop.Core;
namespace RageCoop.Client
{
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class DevTool : Script
{
public static Vehicle ToMark;
public static Script Instance;
public DevTool()
{
Instance = this;
Pause();
}
protected override void OnTick()
{
base.OnTick();
foreach (var p in World.GetAllPeds()) DrawWeaponBone(p);
if (ToMark == null) return;
if (WeaponUtil.VehicleWeapons.TryGetValue((uint)(int)ToMark.Model, out var info))
foreach (var ws in info.Weapons)
foreach (var w in ws.Value.Bones)
DrawBone(w.BoneName, ws.Value.Name + ":" + ws.Key.ToHex());
var P = Game.Player.Character;
var b = ToMark.GetMuzzleBone(P.VehicleWeapon);
if (b != null) World.DrawLine(b.Position, b.Position + b.ForwardVector * 5, Color.Brown);
}
public static void DrawWeaponBone(Ped p)
{
var wb = p.Weapons?.CurrentWeaponObject?.Bones["gun_muzzle"];
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.RightVector, Color.Blue);
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.ForwardVector, Color.Red);
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.UpVector, Color.Green);
}
private void FindAndDraw()
{
DrawBone("weapon_1a");
DrawBone("weapon_1b");
DrawBone("weapon_1c");
DrawBone("weapon_1d");
DrawBone("weapon_2a");
DrawBone("weapon_2b");
DrawBone("weapon_2c");
DrawBone("weapon_2d");
DrawBone("weapon_3a");
DrawBone("weapon_3b");
DrawBone("weapon_3c");
DrawBone("weapon_3d");
DrawBone("weapon_4a");
DrawBone("weapon_4b");
DrawBone("weapon_4c");
DrawBone("weapon_4d");
DrawBone("weapon_1e");
DrawBone("weapon_1f");
DrawBone("weapon_1g");
DrawBone("weapon_1h");
DrawBone("weapon_2e");
DrawBone("weapon_2f");
DrawBone("weapon_2g");
DrawBone("weapon_2h");
DrawBone("weapon_3e");
DrawBone("weapon_3f");
DrawBone("weapon_3g");
DrawBone("weapon_3h");
DrawBone("weapon_4e");
DrawBone("weapon_4f");
DrawBone("weapon_4g");
DrawBone("weapon_4h");
}
private void DrawBone(string name, string text = null)
{
text = text ?? name;
var b = ToMark.Bones[name];
if (b.IsValid)
{
var start = b.Position;
var end = b.Position + b.ForwardVector * 5;
World.DrawLine(start, end, Color.AliceBlue);
Util.DrawTextFromCoord(end, text, 0.35f);
}
}
}
}

View File

@ -1,112 +0,0 @@
using System;
using System.Drawing;
using DXHook.Hook.Common;
using GTA;
using RageCoop.Client.CefHost;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.GUI
{
public class CefClient
{
public readonly int Id;
internal CefAdapter Adapter;
internal CefController Controller;
internal ImageElement MainFrame;
internal CefClient(int id, Size size)
{
Id = id;
Controller = CefController.Create(id, size, out Adapter, BufferMode.Full);
MainFrame = new ImageElement(size.Width, size.Height, 4, Adapter.PtrBuffer);
Adapter.OnPaint += (len, dirty) =>
{
try
{
// Image is using same shared buffer, so just need to make it re-copied to GPU
MainFrame.Invalidate();
}
catch (Exception ex)
{
API.Logger.Error(ex);
}
};
}
internal void Destroy()
{
Controller.Dispose();
Adapter.Dispose();
MainFrame.Dispose();
}
public Point GetLocationInFrame(Point screenPos)
{
screenPos.X -= MainFrame.Location.X;
screenPos.Y -= MainFrame.Location.Y;
return screenPos;
}
public Point GetLocationInCef(Point screenPos)
{
var p = GetLocationInFrame(screenPos);
p.X = (int)(p.X / Scale);
p.Y = (int)(p.Y / Scale);
return p;
}
internal bool PointInArea(Point screen)
{
screen = GetLocationInFrame(screen);
return screen.X.IsBetween(0, Width) && screen.Y.IsBetween(0, Height);
}
internal void Tick()
{
var mousePos = Util.CursorPosition;
if (!PointInArea(mousePos)) return;
var pos = GetLocationInCef(mousePos);
if (Game.IsControlJustPressed(Control.CursorAccept))
Controller.SendMouseClick(pos.X, pos.Y, 0, MouseButton.Left, false, 1);
else if (Game.IsControlJustReleased(Control.CursorAccept))
Controller.SendMouseClick(pos.X, pos.Y, 0, MouseButton.Left, true, 1);
}
#region FRAME-APPERANCE
public float Scale
{
get => MainFrame.Scale;
set => MainFrame.Scale = value;
}
public Color Tint
{
get => MainFrame.Tint;
set => MainFrame.Tint = value;
}
public byte Opacity
{
get => MainFrame.Opacity;
set => MainFrame.Opacity = value;
}
public Point Location
{
get => MainFrame.Location;
set => MainFrame.Location = value;
}
public int Width => MainFrame.Width;
public int Height => MainFrame.Height;
public bool Hidden
{
get => MainFrame.Hidden;
set => MainFrame.Hidden = value;
}
#endregion
}
}

View File

@ -1,111 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.Native;
using RageCoop.Client.CefHost;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using static RageCoop.Client.Shared;
namespace RageCoop.Client.GUI
{
internal static class CefManager
{
private static readonly ConcurrentDictionary<int, CefClient> Clients =
new ConcurrentDictionary<int, CefClient>();
private static readonly Overlay CefOverlay = new Overlay
{
Elements = new List<IOverlayElement>(),
Hidden = false
};
static CefManager()
{
Main.CefRunning = true;
HookManager.Initialize();
CefController.Initialize(CefSubProcessPath);
CefController.OnCefMessage = m => API.Logger.Debug(m);
HookManager.AddOverLay(CefOverlay);
}
public static CefClient ActiveClient { get; set; }
public static void Tick()
{
if (ActiveClient != null)
{
Game.DisableAllControlsThisFrame();
Function.Call(Hash.SET_MOUSE_CURSOR_THIS_FRAME);
ActiveClient.Tick();
}
}
public static void KeyDown(Keys key)
{
}
public static void KeyUp(Keys key)
{
}
public static bool DestroyClient(CefClient client)
{
lock (Clients)
{
if (Clients.TryRemove(client.Id, out var c) && client == c)
{
client.Destroy();
CefOverlay.Elements.Remove(client.MainFrame);
if (ActiveClient == client) ActiveClient = null;
return true;
}
}
return false;
}
public static CefClient CreateClient(Size size)
{
lock (Clients)
{
var id = 0;
while (id == 0 || Clients.ContainsKey(id)) id = CoreUtils.RandInt(0, int.MaxValue);
var client = new CefClient(id, size);
if (Clients.TryAdd(id, client))
{
CefOverlay.Elements.Add(client.MainFrame);
return client;
}
API.Logger.Warning("Failed to create CefClient");
client.Destroy();
return null;
}
}
public static void CleanUp()
{
Main.CefRunning = false;
ActiveClient = null;
try
{
lock (Clients)
{
foreach (var c in Clients.Values) DestroyClient(c);
Clients.Clear();
}
CefController.ShutDown();
}
catch (Exception ex)
{
API.Logger.Error(ex);
}
}
}
}

View File

@ -1,62 +0,0 @@
using System.Collections.Generic;
using DXHook.Hook;
using DXHook.Hook.Common;
using DXHook.Interface;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.GUI
{
internal static class HookManager
{
public static readonly CaptureInterface Interface = new CaptureInterface();
private static DXHookD3D11 _hook;
public static Overlay DefaultOverlay = new Overlay();
public static bool Hooked => _hook != null;
public static void GetOverlays()
{
new List<IOverlay>(_hook.Overlays);
}
public static void AddOverLay(IOverlay ol)
{
_hook.Overlays.Add(ol);
_hook.IsOverlayUpdatePending = true;
}
public static void RemoveOverlay(IOverlay ol)
{
_hook.Overlays.Remove(ol);
_hook.IsOverlayUpdatePending = true;
}
public static void Initialize()
{
if (_hook != null) return;
_hook = new DXHookD3D11(Interface);
_hook.Config = new CaptureConfig
{
Direct3DVersion = Direct3DVersion.Direct3D11,
ShowOverlay = true
};
_hook.Overlays = new List<IOverlay>();
_hook.Hook();
_hook.OnPresent += Present;
DefaultOverlay.Elements = new List<IOverlayElement>();
AddOverLay(DefaultOverlay);
Interface.RemoteMessage += m => { API.Logger.Debug("DXHook: " + m.Message); };
API.Logger.Debug("Hooked DX3D11");
}
private static void Present()
{
}
public static void CleanUp()
{
_hook?.Cleanup();
_hook?.Dispose();
_hook = null;
}
}
}

View File

@ -1,420 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using LemonUI.Elements;
using LemonUI.Menus;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using Control = GTA.Control;
namespace RageCoop.Client
{
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V", NoScriptThread = true)]
internal class Main : Script
{
internal static Version ModVersion = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0;
internal static RelationshipGroup SyncedPedsGroup;
internal static ClientSettings Settings = null;
internal static Chat MainChat = null;
internal static Stopwatch Counter = new();
internal static Logger Log = null;
internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition;
internal static Resources MainRes = null;
public static Ped P;
public static Vehicle V;
public static Vehicle LastV;
public static float FPS;
private static bool _lastDead;
public static bool CefRunning;
public static bool IsUnloading { get; private set; }
public static Script Instance { get; private set; }
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
Instance = this;
Directory.CreateDirectory(DataPath);
try
{
Settings = Util.ReadSettings();
}
catch
{
Notification.Show("Malformed configuration, overwriting with default values...");
Settings = new();
Util.SaveSettings();
}
Log = new Logger()
{
FlushImmediately = true,
Writers = null,
#if DEBUG
LogLevel = 0,
#else
LogLevel = Settings.LogLevel,
#endif
};
Log.OnFlush += (line, formatted) =>
{
SHVDN.Logger.Write($"[RageCoop] {line.Message}", (uint)line.LogLevel);
};
// Run static constructor to register all function pointers and remoting entries
RuntimeHelpers.RunClassConstructor(typeof(API).TypeHandle);
}
protected override void OnAborted(AbortedEventArgs e)
{
base.OnAborted(e);
try
{
IsUnloading = e.IsUnloading;
CleanUp("Abort");
WorldThread.DoQueuedActions();
if (IsUnloading)
{
ThreadManager.OnUnload();
Log.Dispose();
}
}
catch (Exception ex)
{
Log.Error(ex);
}
}
protected override void OnStart()
{
base.OnStart();
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{
throw new NotSupportedException("Please update your GTA5 to v1.0.1290 or newer!");
}
MainRes = new();
Log.Info(
$"Main script initialized");
BaseScript.OnStart();
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral,
true);
MainChat = new Chat();
Util.NativeMemory();
Counter.Restart();
}
protected override void OnTick()
{
base.OnTick();
var lastVehicleHandle = Call<int>(GET_PLAYERS_LAST_VEHICLE);
var playerHandle = Call<int>(PLAYER_PED_ID);
if (LastV?.Handle != lastVehicleHandle)
LastV = Entity.FromHandle(lastVehicleHandle) as Vehicle;
if (P?.Handle != playerHandle)
P = (Ped)Entity.FromHandle(playerHandle);
var playerVehHandle = Call<int>(GET_VEHICLE_PED_IS_IN, P.Handle, false);
if (V?.Handle != playerVehHandle)
V = Entity.FromHandle(playerVehHandle) as Vehicle;
PlayerPosition = P.ReadPosition();
FPS = Game.FPS;
#if CEF
if (CefRunning)
{
CefManager.Tick();
}
#endif
if (!Networking.IsOnServer)
{
return;
}
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
Log.Error(ex);
}
if (Game.TimeScale != 1.0f)
{
Game.TimeScale = 1;
}
if (Networking.ShowNetworkInfo)
{
new ScaledText(new PointF(200, 0),
$"L: {Networking.Latency * 1000:N0}ms", 0.5f)
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(200, 30),
$"R: {NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f)
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(200, 60),
$"S: {NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f)
{ Alignment = Alignment.Center }.Draw();
}
MainChat.Tick();
PlayerList.Tick();
if (!API.Config.EnableAutoRespawn)
{
Call(PAUSE_DEATH_ARREST_RESTART, true);
Call(IGNORE_NEXT_RESTART, true);
Call(FORCE_GAME_STATE_PLAYING);
Call(TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
if (P.IsDead)
{
Call(SET_FADE_OUT_AFTER_DEATH, false);
if (P.Health != 1)
{
P.Health = 1;
Game.Player.WantedLevel = 0;
Log.Debug("Player died.");
API.Events.InvokePlayerDied();
}
Screen.StopEffects();
}
else
{
Call(DISPLAY_HUD, true);
}
}
else if (P.IsDead && !_lastDead)
{
API.Events.InvokePlayerDied();
}
_lastDead = P.IsDead;
Ticked++;
}
protected override void OnKeyUp(GTA.KeyEventArgs e)
{
base.OnKeyUp(e);
#if CEF
if (CefRunning)
{
CefManager.KeyUp(e.KeyCode);
}
#endif
}
protected unsafe override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (MainChat.Focused)
{
MainChat.OnKeyDown(e.KeyCode);
return;
}
#if CEF
if (CefRunning)
{
CefManager.KeyDown(e.KeyCode);
}
#endif
if (Networking.IsOnServer)
{
if (Voice.WasInitialized())
{
if (Game.IsControlPressed(Control.PushToTalk))
{
Voice.StartRecording();
return;
}
if (Voice.IsRecording())
{
Voice.StopRecording();
return;
}
}
if (Game.IsControlPressed(Control.FrontendPause))
{
Call(ACTIVATE_FRONTEND_MENU,
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{
Call(ACTIVATE_FRONTEND_MENU,
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
}
if (e.KeyCode == Settings.MenuKey)
{
if (CoopMenu.MenuPool.AreAnyVisible)
{
CoopMenu.MenuPool.ForEach<NativeMenu>(x =>
{
if (x.Visible)
{
CoopMenu.LastMenu = x;
x.Visible = false;
}
});
}
else
{
CoopMenu.LastMenu.Visible = true;
}
}
else if (Game.IsControlJustPressed(Control.MpTextChatAll))
{
if (Networking.IsOnServer)
{
MainChat.Focused = true;
}
}
else if (MainChat.Focused)
{
return;
}
else if (Game.IsControlJustPressed(Control.MultiplayerInfo))
{
if (Networking.IsOnServer)
{
ulong currentTimestamp = Util.GetTickCount64();
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000
? (currentTimestamp - 6000)
: currentTimestamp;
}
}
else if (e.KeyCode == Settings.PassengerKey)
{
if (P == null || P.IsInVehicle())
{
return;
}
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
{
P.Task.ClearAll();
}
else
{
var V = World.GetClosestVehicle(P.ReadPosition(), 15);
if (V != null)
{
var seat = P.GetNearestSeat(V);
var p = V.GetPedOnSeat(seat);
if (p != null && !p.IsDead)
{
for (int i = -1; i < V.PassengerCapacity; i++)
{
seat = (VehicleSeat)i;
p = V.GetPedOnSeat(seat);
if (p == null || p.IsDead)
{
break;
}
}
}
P.Task.EnterVehicle(V, seat, -1, 5, EnterVehicleFlags.None);
}
}
}
}
internal static void Connected()
{
Memory.ApplyPatches();
if (Settings.Voice && !Voice.WasInitialized())
{
Voice.Init();
}
API.QueueAction(() =>
{
WorldThread.Traffic(!Settings.DisableTraffic);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
CoopMenu.ConnectedMenuSetting();
MainChat.Init();
Notification.Show("~g~Connected!");
});
Log.Info(">> Connected <<");
}
private static readonly object _cleanupLock = new();
public static void CleanUp(string reason)
{
lock (_cleanupLock)
{
if (reason != "Abort")
{
Log.Info($">> Disconnected << reason: {reason}");
Notification.Show("~r~Disconnected: " + reason);
}
if (MainChat.Focused)
{
MainChat.Focused = false;
}
PlayerList.Cleanup();
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
LocalPlayerID = default;
MainRes.Unload();
Memory.RestorePatches();
#if CEF
if (CefRunning)
{
CefManager.CleanUp();
}
#endif
DownloadManager.Cleanup();
Voice.ClearAll();
Networking.Peer?.Dispose();
Networking.Peer = null;
}
}
}
}

View File

@ -1,113 +0,0 @@
using System;
using System.Drawing;
using System.Linq;
using System.Reflection;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class DebugMenu
{
public static NativeMenu Menu = new("RAGECOOP", "Debug", "Debug settings")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeMenu DiagnosticMenu = new("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeMenu TuneMenu = new("RAGECOOP", "Change tunable values")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeItem SimulatedLatencyItem =
new("Simulated network latency", "Simulated network latency in ms (one way)", "0");
public static NativeCheckboxItem ShowOwnerItem = new("Show entity owner",
"Show the owner name of the entity you're aiming at", false);
private static readonly NativeCheckboxItem ShowNetworkInfoItem =
new("Show Network Info", Networking.ShowNetworkInfo);
static DebugMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
TuneMenu.Opening += (s, e) =>
{
TuneMenu.Clear();
foreach (var t in typeof(Main).Assembly.GetTypes())
{
foreach (var field in t.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic))
{
var attri = field.GetCustomAttribute<DebugTunableAttribute>();
if (attri == null)
continue;
var item = new NativeItem(field.Name);
item.AltTitle = field.GetValue(null).ToString();
item.Activated += (s, e) =>
{
try
{
field.SetValue(null, Convert.ChangeType(Game.GetUserInput(), field.FieldType));
item.AltTitle = field.GetValue(null).ToString();
}
catch (Exception ex)
{
Log.Error(ex);
}
};
TuneMenu.Add(item);
}
}
};
DiagnosticMenu.Opening += (sender, e) =>
{
DiagnosticMenu.Clear();
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
// foreach (var pair in Debug.TimeStamps)
// DiagnosticMenu.Add(
// new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
};
ShowOwnerItem.CheckboxChanged += (s, e) =>
{
Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
Util.SaveSettings();
};
#if DEBUG
SimulatedLatencyItem.Activated += (s, e) =>
{
try
{
SimulatedLatencyItem.AltTitle =
((Networking.SimulatedLatency =
int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
}
catch (Exception ex)
{
Log.Error(ex);
}
};
Menu.Add(SimulatedLatencyItem);
#endif
Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.AddSubMenu(DiagnosticMenu);
Menu.AddSubMenu(TuneMenu);
}
}
}

View File

@ -1,74 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using GTA;
using GTA.Native;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class DevToolMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "DevTool", "Internal testing tools")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Show weapon bones");
public static readonly NativeItem DumpFixItem = new NativeItem("Dump weapon fixes");
public static readonly NativeItem GetAnimItem = new NativeItem("Get current animation");
public static readonly NativeItem DumpVwHashItem = new NativeItem("Dump VehicleWeaponHash.cs");
static DevToolMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
enableItem.Activated += ShowBones;
enableItem.Checked = false;
DumpFixItem.Activated += (s, e) => WeaponUtil.DumpWeaponFix(WeaponFixDataPath);
GetAnimItem.Activated += (s, e) =>
{
if (File.Exists(AnimationsDataPath))
{
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(AnimationsDataPath));
foreach (var anim in anims)
foreach (var a in anim.Animations)
if (Call<bool>(IS_ENTITY_PLAYING_ANIM, P, anim.DictionaryName, a, 3))
{
Console.PrintInfo(anim.DictionaryName + " : " + a);
Notification.Show(anim.DictionaryName + " : " + a);
}
}
else
{
Notification.Show($"~r~{AnimationsDataPath} not found");
}
};
Menu.Add(enableItem);
Menu.Add(DumpVwHashItem);
Menu.Add(DumpFixItem);
Menu.Add(GetAnimItem);
}
private static void ShowBones(object sender, EventArgs e)
{
if (enableItem.Checked)
{
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
DevTool.Instance.Resume();
}
else
{
DevTool.Instance.Pause();
DevTool.ToMark = null;
}
}
}
}

View File

@ -1,197 +0,0 @@
using System.Collections.Generic;
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static int SyncInterval = 30;
public static List<NetConnection> Targets = new List<NetConnection>();
public static void SendSync(Packet p, ConnectionChannel channel = ConnectionChannel.Default,
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
Peer.SendTo(p, Targets, channel, method);
}
public static void SendPed(SyncedPed sp, bool full)
{
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
var ped = sp.MainPed;
var p = SendPackets.PedPacket;
p.ID = sp.ID;
p.OwnerID = sp.OwnerID;
p.Health = ped.Health;
p.Rotation = ped.ReadRotation();
p.Velocity = ped.ReadVelocity();
p.Speed = ped.GetPedSpeed();
p.Flags = ped.GetPedFlags();
p.Heading = ped.Heading;
if (p.Flags.HasPedFlag(PedDataFlags.IsAiming)) p.AimCoords = ped.GetAimCoord();
if (p.Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{
p.HeadPosition = ped.Bones[Bone.SkelHead].Position;
p.RightFootPosition = ped.Bones[Bone.SkelRightFoot].Position;
p.LeftFootPosition = ped.Bones[Bone.SkelLeftFoot].Position;
}
else
{
// Seat sync
if (p.Speed >= 4)
{
var veh = ped.CurrentVehicle?.GetSyncEntity() ??
ped.VehicleTryingToEnter?.GetSyncEntity() ?? ped.LastVehicle?.GetSyncEntity();
p.VehicleID = veh?.ID ?? 0;
if (p.VehicleID == 0) Log.Error("Invalid vehicle");
if (p.Speed == 5)
p.Seat = ped.GetSeatTryingToEnter();
else
p.Seat = ped.SeatIndex;
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
{
veh.OwnerID = LocalPlayerID;
SyncEvents.TriggerChangeOwner(veh.ID, LocalPlayerID);
}
}
p.Position = ped.ReadPosition();
}
sp.LastSentStopWatch.Restart();
if (full)
{
if (p.Speed == 4)
p.VehicleWeapon = ped.VehicleWeapon;
p.CurrentWeapon = ped.Weapons.Current.Hash;
p.Flags |= PedDataFlags.IsFullSync;
p.Clothes = ped.GetPedClothes();
p.ModelHash = ped.Model.Hash;
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
p.WeaponTint = (byte)Call<int>(GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
Blip b;
if (sp.IsPlayer)
{
p.BlipColor = API.Config.BlipColor;
p.BlipSprite = API.Config.BlipSprite;
p.BlipScale = API.Config.BlipScale;
}
else if ((b = ped.AttachedBlip) != null)
{
p.BlipColor = b.Color;
p.BlipSprite = b.Sprite;
if (p.BlipSprite == BlipSprite.PoliceOfficer || p.BlipSprite == BlipSprite.PoliceOfficer2)
p.BlipScale = 0.5f;
}
else
{
p.BlipColor = (BlipColor)255;
}
}
SendSync(p, ConnectionChannel.PedSync);
}
public static void SendVehicle(SyncedVehicle v, bool full)
{
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
var veh = v.MainVehicle;
var packet = SendPackets.VehicelPacket;
packet.ED.ID = v.ID;
packet.ED.OwnerID = v.OwnerID;
packet.ED.Position = veh.ReadPosition();
packet.ED.Velocity = veh.Velocity;
packet.ED.Quaternion = veh.ReadQuaternion();
packet.ED.ModelHash = veh.Model.Hash;
packet.VD.Flags = v.GetVehicleFlags();
packet.VD.SteeringAngle = veh.SteeringAngle;
packet.VD.ThrottlePower = veh.ThrottlePower;
packet.VD.BrakePower = veh.BrakePower;
packet.VD.Flags |= VehicleDataFlags.IsFullSync;
packet.VD.LockStatus = veh.LockStatus;
v.LastSentStopWatch.Restart();
if (packet.VD.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
packet.VD.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio();
if (full)
{
byte primaryColor = 0;
byte secondaryColor = 0;
unsafe
{
Call<byte>(GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
packet.VDF.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
packet.VDF.RoofState = (byte)veh.RoofState;
packet.VDF.Colors = (primaryColor, secondaryColor);
packet.VDF.DamageModel = veh.GetVehicleDamageModel();
packet.VDF.EngineHealth = veh.EngineHealth;
packet.VDF.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
packet.VDF.HeadlightColor = (byte)Call<int>(GET_VEHICLE_XENON_LIGHT_COLOR_INDEX, veh);
packet.VDF.ExtrasMask = v.GetVehicleExtras();
packet.VDF.RadioStation = v.MainVehicle == LastV
? Util.GetPlayerRadioIndex() : byte.MaxValue;
if (packet.VDF.EngineHealth > v.LastEngineHealth) packet.VD.Flags |= VehicleDataFlags.Repaired;
packet.VDV.Mods = v.GetVehicleMods(out packet.VDF.ToggleModsMask);
packet.VDV.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
v.LastEngineHealth = packet.VDF.EngineHealth;
}
SendSync(packet, ConnectionChannel.VehicleSync);
}
public static void SendProjectile(SyncedProjectile sp)
{
sp.ExtractData(ref SendPackets.ProjectilePacket);
if (sp.MainProjectile.IsDead) EntityPool.RemoveProjectile(sp.ID, "Dead");
SendSync(SendPackets.ProjectilePacket, ConnectionChannel.ProjectileSync);
}
public static void SendChatMessage(string message)
{
Peer.SendTo(new Packets.ChatMessage(s => Security.Encrypt(s.GetBytes()))
{ Username = Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat,
NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
}
public static void SendVoiceMessage(byte[] buffer, int recorded)
{
SendSync(new Packets.Voice { ID = LocalPlayerID, Buffer = buffer, Recorded = recorded },
ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
private static class SendPackets
{
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
#region SYNC EVENTS
public static void SendBullet(int ownerID, uint weapon, Vector3 end)
{
SendSync(new Packets.BulletShot
{
EndPosition = end,
OwnerID = ownerID,
WeaponHash = weapon
}, ConnectionChannel.SyncEvents);
}
#endregion
}
}

View File

@ -1,47 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableDynamicLoading>true</EnableDynamicLoading>
<NoAotCompile>false</NoAotCompile>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Library</OutputType>
<OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowedReferenceRelatedFileExtensions>
.dll
.pdb
</AllowedReferenceRelatedFileExtensions>
<NoWarn>CS1591</NoWarn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<TrimmerRootDescriptor Include="Trimming.xml" />
<Compile Remove="GUI\**" />
<EmbeddedResource Remove="GUI\**" />
<None Remove="GUI\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Scripting\ClientScript.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="LemonUI.SHVDNC">
<HintPath>..\..\libs\LemonUI.SHVDNC.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
<ProjectReference Include="..\Scripting\RageCoop.Client.Scripting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NAudio" Version="2.1.0" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.3.0" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(SolutionDir)' != '*Undefined*' and '$(Configuration)' != 'API'">
<Exec Command="xcopy &quot;$(SolutionDir)Client\Data&quot; &quot;$(OutDir)..\Data&quot; /i /s /y" />
</Target>
</Project>

View File

@ -1,3 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shared/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=util/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -1,140 +0,0 @@
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using static RageCoop.Core.Scripting.CustomEvents;
namespace RageCoop.Client.Scripting
{
internal static unsafe partial class API
{
static API()
{
RegisterFunctionPointers();
}
static void RegisterFunctionPointers()
{
foreach (var method in typeof(API).GetMethods(BindingFlags.Public | BindingFlags.Static))
{
var attri = method.GetCustomAttribute<ApiExportAttribute>();
if (attri == null) continue;
attri.EntryPoint ??= method.Name;
SHVDN.Core.SetPtr($"{typeof(API).FullName}.{attri.EntryPoint}", method.MethodHandle.GetFunctionPointer());
Log.Debug($"Registered function pointer for {method.DeclaringType}.{method.Name}");
}
}
[ThreadStatic]
static string _lastResult;
[ApiExportAttribute(EntryPoint = nameof(GetLastResult))]
public static int GetLastResult(char* buf, int cbBufSize)
{
if (_lastResult == null)
return 0;
fixed (char* pErr = _lastResult)
{
var cbToCopy = sizeof(char) * (_lastResult.Length + 1);
System.Buffer.MemoryCopy(pErr, buf, cbToCopy, Math.Min(cbToCopy, cbBufSize));
if (cbToCopy > cbBufSize && cbBufSize > 0)
{
buf[cbBufSize / sizeof(char) - 1] = '\0'; // Always add null terminator
}
return _lastResult.Length;
}
}
public static void SetLastResult(string msg) => _lastResult = msg;
[ApiExportAttribute(EntryPoint = nameof(SetLastResult))]
public static void SetLastResult(char* msg)
{
try
{
SetLastResult(msg == null ? null : new string(msg));
}
catch (Exception ex)
{
SHVDN.PInvoke.MessageBoxA(default, ex.ToString(), "error", default);
}
}
[ApiExportAttribute(EntryPoint = nameof(GetEventHash))]
public static CustomEventHash GetEventHash(char* name) => new string(name);
[ApiExportAttribute(EntryPoint = nameof(SendCustomEvent))]
public static void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData)
{
var payload = new byte[cbData];
Marshal.Copy((IntPtr)data, payload, 0, cbData);
Networking.Peer.SendTo(new Packets.CustomEvent()
{
Flags = flags,
Payload = payload,
Hash = hash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
[ApiExportAttribute(EntryPoint = nameof(InvokeCommand))]
public static int InvokeCommand(char* name, int argc, char** argv)
{
try
{
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = new(argv[i]);
}
_lastResult = _invokeCommand(new string(name), args);
return _lastResult.Length;
}
catch (Exception ex)
{
Log.Error(ex);
SetLastResult(ex.ToString());
return 0;
}
}
[ApiExportAttribute(EntryPoint = nameof(GetLastResultLenInChars))]
public static int GetLastResultLenInChars() => _lastResult?.Length ?? 0;
/// <summary>
/// Convert Entity ID to handle
/// </summary>
[ApiExportAttribute(EntryPoint = nameof(IdToHandle))]
public static int IdToHandle(byte type, int id)
{
return type switch
{
T_ID_PROP => EntityPool.GetPropByID(id)?.MainProp?.Handle ?? 0,
T_ID_PED => EntityPool.GetPedByID(id)?.MainPed?.Handle ?? 0,
T_ID_VEH => EntityPool.GetVehicleByID(id)?.MainVehicle?.Handle ?? 0,
T_ID_BLIP => EntityPool.GetBlipByID(id)?.Handle ?? 0,
_ => 0,
};
}
/// <summary>
/// Enqueue a message to the main logger
/// </summary>
/// <param name="level"></param>
/// <param name="msg"></param>
[ApiExportAttribute(EntryPoint = nameof(LogEnqueue))]
public static void LogEnqueue(LogLevel level, char* msg)
{
Log.Enqueue((int)level, new(msg));
}
class ApiExportAttribute : Attribute
{
public string EntryPoint;
}
}
}

View File

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal class RemotingAttribute : Attribute
{
public bool GenBridge = true;
}
/// <summary>
/// Some local remoting implementation with json-based serialization, somewhar slow, but convenient
/// </summary>
internal static unsafe partial class API
{
static readonly MethodInfo[] _apiEntries =
typeof(API).GetMethods(BindingFlags.Static | BindingFlags.Public);
static readonly Dictionary<string, MethodInfo> _commands =
new(typeof(API).GetMethods().
Where(md => md.CustomAttributes.
Any(attri => attri.AttributeType == typeof(RemotingAttribute))).
Select(x => new KeyValuePair<string, MethodInfo>(x.Name, x)));
static string _invokeCommand(string name, string[] argsJson)
{
if (_commands.TryGetValue(name, out var method))
{
var ps = method.GetParameters();
if (argsJson.Length != ps.Length)
throw new ArgumentException($"Parameter count mismatch, expecting {ps.Length} parameters, got {argsJson.Length}", nameof(argsJson));
object[] args = new object[ps.Length];
for (int i = 0; i < ps.Length; i++)
{
args[i] = JsonDeserialize(argsJson[i], ps[i].ParameterType);
}
var result = method.Invoke(null, args);
if (method.ReturnType == typeof(void))
{
return "void";
}
return JsonSerialize(result);
}
throw new KeyNotFoundException($"Command {name} was not found");
}
}
}

View File

@ -1,487 +0,0 @@
#undef DEBUG
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
[assembly: InternalsVisibleTo("CodeGen")] // For generating api bridge
namespace RageCoop.Client.Scripting
{
/// <summary>
/// Provides vital functionality to interact with RAGECOOP
/// </summary>
internal static unsafe partial class API
{
#region INTERNAL
internal static Dictionary<int, List<CustomEventHandler>> CustomEventHandlers =
new();
#endregion
/// <summary>
/// Client configuration, this will conflict with server-side config.
/// </summary>
public static class Config
{
/// <summary>
/// Get or set local player's username, set won't be effective if already connected to a server.
/// </summary>
public static string Username
{
get => Settings.Username;
set
{
if (Networking.IsOnServer || string.IsNullOrEmpty(value)) return;
Settings.Username = value;
}
}
/// <summary>
/// Enable automatic respawn for this player.
/// </summary>
public static bool EnableAutoRespawn { get; set; } = true;
/// <summary>
/// Get or set player's blip color
/// </summary>
public static BlipColor BlipColor { get; set; } = BlipColor.White;
/// <summary>
/// Get or set player's blip sprite
/// </summary>
public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard;
/// <summary>
/// Get or set scale of player's blip
/// </summary>
public static float BlipScale { get; set; } = 1;
public static bool ShowPlayerNameTag
{
get => Settings.ShowPlayerNameTag;
set
{
if (value == ShowPlayerNameTag) return;
Settings.ShowPlayerNameTag = value;
Util.SaveSettings();
}
}
}
/// <summary>
/// Base events for RageCoop
/// </summary>
public static class Events
{
/// <summary>
/// The local player is dead
/// </summary>
public static event EmptyEvent OnPlayerDied;
/// <summary>
/// A local vehicle is spawned
/// </summary>
public static event EventHandler<SyncedVehicle> OnVehicleSpawned;
/// <summary>
/// A local vehicle is deleted
/// </summary>
public static event EventHandler<SyncedVehicle> OnVehicleDeleted;
/// <summary>
/// A local ped is spawned
/// </summary>
public static event EventHandler<SyncedPed> OnPedSpawned;
/// <summary>
/// A local ped is deleted
/// </summary>
public static event EventHandler<SyncedPed> OnPedDeleted;
#region DELEGATES
/// <summary>
/// </summary>
public delegate void EmptyEvent();
/// <summary>
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
public delegate void CustomEvent(int hash, List<object> args);
#endregion
#region INVOKE
internal static void InvokeVehicleSpawned(SyncedVehicle v)
{
OnVehicleSpawned?.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 InvokePedDeleted(SyncedPed p)
{
OnPedDeleted?.Invoke(null, p);
}
internal static void InvokePlayerDied()
{
OnPlayerDied?.Invoke();
}
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
{
// Log.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers))
{
fixed (byte* pData = p.Payload)
{
foreach (var handler in handlers)
{
try
{
handler.Invoke(p.Hash, pData, p.Payload.Length);
}
catch (Exception ex)
{
Log.Error("InvokeCustomEvent", ex);
}
}
}
}
}
#endregion
}
#region PROPERTIES
/// <summary>
/// Get the local player's ID
/// </summary>
/// <returns>PlayerID</returns>
public static int LocalPlayerID => Main.LocalPlayerID;
/// <summary>
/// Check if player is connected to a server
/// </summary>
public static bool IsOnServer => Networking.IsOnServer;
/// <summary>
/// Get an <see cref="System.Net.IPEndPoint" /> that the player is currently connected to, or null if not connected to
/// the server
/// </summary>
public static IPEndPoint ServerEndPoint =>
Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
/// <summary>
/// Check if a RAGECOOP menu is visible
/// </summary>
public static bool IsMenuVisible => CoopMenu.MenuPool.AreAnyVisible;
/// <summary>
/// Check if the RAGECOOP chat is visible
/// </summary>
public static bool IsChatFocused => MainChat.Focused;
/// <summary>
/// Check if the RAGECOOP list of players is visible
/// </summary>
public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
/// <summary>
/// Get the version of RAGECOOP
/// </summary>
public static Version CurrentVersion => Main.ModVersion;
/// <summary>
/// Get all players indexed by their ID
/// </summary>
public static Dictionary<int, Player> Players => new(PlayerList.Players);
#endregion
#region FUNCTIONS
/// <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;
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);
}
/// <summary>
/// Send an event and data to the server.
/// </summary>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
/// types
/// </param>
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
=> SendCustomEvent(CustomEventFlags.None, eventHash, args);
/// <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)
{
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
{
Payload = writer.ToByteArray(writer.Position),
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">
/// An unique identifier of the event
/// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
/// <summary>
/// </summary>
/// <returns></returns>
public static void RequestSharedFile(string name, Action<string> callback)
{
EventHandler<string> handler = (s, e) =>
{
if (e.EndsWith(name)) callback(e);
};
DownloadManager.DownloadCompleted += handler;
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest
{
Name = name
},
p =>
{
if (p.Response != FileResponse.Loaded)
{
DownloadManager.DownloadCompleted -= handler;
throw new ArgumentException("Requested file was not found on the server: " + name);
}
});
}
/// <summary>
/// Connect to a server
/// </summary>
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
[Remoting]
public static void Connect(string address)
{
if (Networking.IsOnServer || Networking.IsConnecting)
throw new InvalidOperationException("Cannot connect to server when another connection is active");
Networking.ToggleConnection(address);
}
/// <summary>
/// Disconnect from current server or cancel the connection attempt.
/// </summary>
[Remoting]
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
}
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
[Remoting]
public static List<ServerInfo> ListServers()
{
return JsonDeserialize<List<ServerInfo>>(
HttpHelper.DownloadString(Settings.MasterServer));
}
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
[Remoting]
public static void LocalChatMessage(string from, string message)
{
MainChat.AddMessage(from, message);
}
/// <summary>
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="message"></param>
[Remoting]
public static void SendChatMessage(string message)
{
if (!IsOnServer)
throw new InvalidOperationException("Not on server");
Networking.SendChatMessage(message);
}
/// <summary>
/// Get the <see cref="ClientResource"/> with this name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Remoting]
public static ClientResource GetResource(string name)
{
if (MainRes.LoadedResources.TryGetValue(name, out var resource))
return resource;
return null;
}
/// <summary>
/// Get <see cref="ClientResource"/> that contains the specified file or directory
/// </summary>
/// <returns></returns>
[Remoting]
public static ClientResource GetResourceFromPath(string path)
{
path = Path.GetFullPath(path).ToLower();
foreach (var res in MainRes.LoadedResources.Values)
{
if (res.ScriptsDirectory.ToLower() == path || res.Files.Any(file => file.Value.FullPath.ToLower() == path))
return res;
}
return null;
}
[Remoting(GenBridge = false)]
public static object GetProperty(string name)
=> typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting(GenBridge = false)]
public static void SetProperty(string name, string jsonVal)
{
var prop = typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public); ;
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
[Remoting]
public static object GetConfig(string name)
=> typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting]
public static void SetConfig(string name, string jsonVal)
{
var prop = typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public);
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">
/// An unique identifier of the event
/// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
[Remoting]
public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler)
{
if (handler.Directory == default)
throw new ArgumentException("Script directory not specified");
if (handler.FunctionPtr == default)
throw new ArgumentException("Function pointer not specified");
lock (CustomEventHandlers)
{
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
CustomEventHandlers.Add(hash, handlers = new());
handlers.Add(handler);
}
}
#endregion
}
}

View File

@ -1,24 +0,0 @@
using GTA;
using RageCoop.Core;
using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
{
public abstract class ClientScript : Script
{
/// <summary>
/// Get the <see cref="ResourceFile" /> instance where this script is loaded from.
/// </summary>
public ResourceFile CurrentFile { get; internal set; }
/// <summary>
/// Get the <see cref="ClientResource" /> that this script belongs to.
/// </summary>
public ClientResource CurrentResource { get; internal set; }
/// <summary>
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
/// </summary>
public Logger Logger => CurrentResource.Logger;
}
}

View File

@ -1,112 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using ICSharpCode.SharpZipLib.Zip;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
namespace RageCoop.Client.Scripting
{
internal class Resources
{
public static string TempPath = Path.Combine(Path.GetTempPath(), "RageCoop");
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new();
static Resources()
{
if (Directory.Exists(TempPath))
try
{
Directory.Delete(TempPath, true);
}
catch
{
}
TempPath = CoreUtils.GetTempDirectory(TempPath);
Directory.CreateDirectory(TempPath);
}
public unsafe void Load(string path, string[] zips)
{
LoadedResources.Clear();
foreach (var zip in zips)
{
var zipPath = Path.Combine(path, zip);
Log?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
Unpack(zipPath, Path.Combine(path, "Data"));
}
}
public unsafe void Unload()
{
var dirs = LoadedResources.Values.Select(x => x.ScriptsDirectory);
foreach (var dir in dirs)
{
SHVDN.Core.RuntimeController.RequestUnload(dir);
}
// Unregister associated handler
foreach (var handlers in API.CustomEventHandlers.Values)
{
foreach (var handler in handlers.ToArray())
{
if (dirs.Contains(handler.Directory, StringComparer.OrdinalIgnoreCase))
{
handlers.Remove(handler);
Log.Debug($"Unregistered handler from script directory {handler.Directory}");
}
}
}
LoadedResources.Clear();
}
private unsafe ClientResource Unpack(string zipPath, string dataFolderRoot)
{
var r = new ClientResource
{
Name = Path.GetFileNameWithoutExtension(zipPath),
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
ScriptsDirectory = Path.Combine(TempPath, 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 ClientFile
{
IsDirectory = true,
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/'),
FullPath = dir
});
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
{
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
var rfile = new ClientFile
{
IsDirectory = false,
Name = relativeName,
FullPath = file
};
r.Files.Add(relativeName, rfile);
}
SHVDN.Core.RuntimeController.RequestLoad(scriptsDir);
LoadedResources.TryAdd(r.Name, r);
return r;
}
}
}

View File

@ -1,50 +0,0 @@
global using GTA;
global using GTA.Native;
global using static GTA.Native.Function;
global using static GTA.Native.Hash;
global using static RageCoop.Client.Shared;
global using static RageCoop.Client.Main;
global using Console = GTA.Console;
global using static RageCoop.Core.Shared;
using System.IO;
using System;
using SHVDN;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace RageCoop.Client
{
[AttributeUsage(AttributeTargets.Field)]
class DebugTunableAttribute : Attribute
{
}
internal static class Shared
{
private static unsafe string GetBasePath()
{
using var fs = File.OpenRead(Instance.FilePath);
var buf = stackalloc char[1024];
GetFinalPathNameByHandleW(fs.SafeFileHandle.DangerousGetHandle(), buf, 1024, 0);
ErrorCheck32();
var scriptDir = Directory.GetParent(Marshal.PtrToStringUni((IntPtr)buf)).FullName;
if (Path.GetFileName(scriptDir).ToLower() != "scripts")
throw new Exception("Unexpected script location");
var result = Directory.GetParent(scriptDir).FullName;
Logger.Debug($"Base path is: {result}");
return result;
}
public static string BasePath = GetBasePath();
public static string DataPath = Path.Combine(BasePath, "Data");
public static string SettingsPath = Path.Combine(DataPath, "Setting.json");
public static string VehicleWeaponDataPath = Path.Combine(DataPath, "VehicleWeapons.json");
public static string WeaponFixDataPath = Path.Combine(DataPath, "WeaponFixes.json");
public static string WeaponInfoDataPath = Path.Combine(DataPath, "Weapons.json");
public static string AnimationsDataPath = Path.Combine(DataPath, "Animations.json");
public static string CefSubProcessPath = Path.Combine(BasePath, "SubProcess", "RageCoop.Client.CefHost.exe");
}
}

View File

@ -1,161 +0,0 @@
using GTA;
using GTA.Native;
namespace RageCoop.Client
{
public partial class SyncedPed
{
private void DisplaySpeaking(bool speaking)
{
if (!MainPed.IsHuman)
return;
if (speaking)
{
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mic_chatter", "mp_facial");
return;
}
switch (MainPed.Gender)
{
case Gender.Male:
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@gen_male@variations@normal");
break;
case Gender.Female:
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@gen_female@variations@normal");
break;
default:
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@mime@variations@normal");
break;
}
}
private void DisplayInCover()
{
var ourAnim = GetCoverAnim();
var animDict = GetCoverIdleAnimDict();
if (ourAnim != null && animDict != null)
{
var flag = AnimationFlags.Loop;
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
{
if (LoadAnim(animDict) == null) return;
MainPed.Task.ClearAll();
Call(TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
}
}
}
internal string GetCoverAnim()
{
if (IsInCover)
{
if (IsBlindFiring)
{
if (IsInCover)
return IsInCoverFacingLeft ? "blindfire_low_l_aim_med" : "blindfire_low_r_aim_med";
return IsInCoverFacingLeft ? "blindfire_hi_l_aim_med" : "blindfire_hi_r_aim_med";
}
return IsInCoverFacingLeft ? "idle_l_corner" : "idle_r_corner";
}
return null;
}
internal string GetCoverIdleAnimDict()
{
if (!IsInCover) return "";
var altitude = IsInLowCover ? "low" : "high";
var hands = GetWeaponHandsHeld((uint)CurrentWeapon);
if (IsBlindFiring)
{
if (hands == 1) return "cover@weapon@1h";
if (hands == 2 || hands == 5) return "cover@weapon@2h";
}
if (hands == 1) return "cover@idles@1h@" + altitude + "@_a";
if (hands == 2 || hands == 5) return "cover@idles@2h@" + altitude + "@_a";
if (hands == 3 || hands == 4 || hands == 0) return "cover@idles@unarmed@" + altitude + "@_a";
return "";
}
internal int GetWeaponHandsHeld(uint weapon)
{
switch (weapon)
{
case (uint)WeaponHash.Unarmed:
return 0;
case (uint)WeaponHash.RPG:
case (uint)WeaponHash.HomingLauncher:
case (uint)WeaponHash.Firework:
return 5;
case (uint)WeaponHash.Minigun:
return 5;
case (uint)WeaponHash.GolfClub:
case (uint)WeaponHash.PoolCue:
case (uint)WeaponHash.Bat:
return 4;
case (uint)WeaponHash.Knife:
case (uint)WeaponHash.Nightstick:
case (uint)WeaponHash.Hammer:
case (uint)WeaponHash.Crowbar:
case (uint)WeaponHash.Wrench:
case (uint)WeaponHash.BattleAxe:
case (uint)WeaponHash.Dagger:
case (uint)WeaponHash.Hatchet:
case (uint)WeaponHash.KnuckleDuster:
case unchecked((uint)-581044007):
case unchecked((uint)-102323637):
case unchecked((uint)-538741184):
return 3;
case unchecked((uint)-1357824103):
case unchecked((uint)-1074790547):
case 2132975508:
case unchecked((uint)-2084633992):
case unchecked((uint)-952879014):
case 100416529:
case (uint)WeaponHash.Gusenberg:
case (uint)WeaponHash.MG:
case (uint)WeaponHash.CombatMG:
case (uint)WeaponHash.CombatPDW:
case (uint)WeaponHash.AssaultSMG:
case (uint)WeaponHash.SMG:
case (uint)WeaponHash.HeavySniper:
case (uint)WeaponHash.PumpShotgun:
case (uint)WeaponHash.HeavyShotgun:
case (uint)WeaponHash.Musket:
case (uint)WeaponHash.AssaultShotgun:
case (uint)WeaponHash.BullpupShotgun:
case (uint)WeaponHash.SawnOffShotgun:
case (uint)WeaponHash.SweeperShotgun:
case (uint)WeaponHash.CompactRifle:
return 2;
}
return 1;
}
private string LoadAnim(string anim)
{
if (!Call<bool>(HAS_ANIM_DICT_LOADED, anim))
{
Call(REQUEST_ANIM_DICT, anim);
return null;
}
return anim;
}
}
}

View File

@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using GTA;
using GTA.Math;
using RageCoop.Core;
namespace RageCoop.Client
{
public partial class SyncedVehicle
{
public Vehicle MainVehicle { get; internal set; }
#region -- SYNC DATA --
internal VehicleData VD;
internal VehicleDataFull VDF;
internal VehicleDataVar VDV;
#endregion
#region FLAGS
internal bool EngineRunning => VD.Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning);
internal bool Transformed => VD.Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
internal bool HornActive => VD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
internal bool LightsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreLightsOn);
internal bool BrakeLightsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
internal bool HighBeamsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
internal bool SireneActive => VD.Flags.HasVehFlag(VehicleDataFlags.IsSirenActive);
internal bool IsDead => VD.Flags.HasVehFlag(VehicleDataFlags.IsDead);
internal bool IsDeluxoHovering => VD.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering);
#endregion
#region FIXED-DATA
internal bool IsMotorcycle;
internal bool IsAircraft;
internal bool HasRocketBoost;
internal bool HasParachute;
internal bool HasRoof;
internal bool IsSubmarineCar;
internal bool IsDeluxo;
internal bool IsTrain;
internal ushort AvalibleExtras;
[DebugTunable]
static float RotCalMult = 10f;
#endregion
#region PRIVATE
private VehicleData _lastVD;
private VehicleDataFull _lastVDF;
private VehicleDataVar _lastVDV;
private Vector3 _predictedPosition;
internal bool _lastTransformed => _lastVD.Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
internal bool _lastHornActive => _lastVD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
#endregion
#region OUTGOING
internal float LastNozzleAngle { get; set; }
internal float LastEngineHealth { get; set; }
internal Vector3 LastVelocity { get; set; }
#endregion
}
}

View File

@ -1,336 +0,0 @@
using System;
using System.Linq;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using RageCoop.Core;
using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
namespace RageCoop.Client
{
/// <summary>
/// A synchronized vehicle instance
/// </summary>
public partial class SyncedVehicle : SyncedEntity
{
internal override void Update()
{
// Check if all data available
if (!IsReady || Owner == null) return;
// Check existence
if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model != Model)
if (!CreateVehicle())
return;
DisplayVehicle();
// Skip update if no new sync message has arrived.
if (!NeedUpdate) return;
if (VD.SteeringAngle != MainVehicle.SteeringAngle)
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * VD.SteeringAngle);
MainVehicle.ThrottlePower = VD.ThrottlePower;
MainVehicle.BrakePower = VD.BrakePower;
if (IsDead)
{
if (MainVehicle.IsDead) return;
MainVehicle.Explode();
}
else
{
if (MainVehicle.IsDead)
WorldThread.Delay(() =>
{
if (MainVehicle.IsDead && !IsDead) MainVehicle.Repair();
}, 1000);
}
if (MainVehicle.IsOnFire)
{
if (!VD.Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
}
else if (VD.Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
{
Call(START_ENTITY_FIRE, MainVehicle);
}
if (EngineRunning != MainVehicle.IsEngineRunning) MainVehicle.IsEngineRunning = EngineRunning;
if (LightsOn != MainVehicle.AreLightsOn) MainVehicle.AreLightsOn = LightsOn;
if (HighBeamsOn != MainVehicle.AreHighBeamsOn) MainVehicle.AreHighBeamsOn = HighBeamsOn;
if (!IsAircraft)
{
if (MainVehicle.HasSiren && SireneActive != MainVehicle.IsSirenActive)
MainVehicle.IsSirenActive = SireneActive;
if (HornActive)
{
if (!_lastHornActive)
{
MainVehicle.SoundHorn(99999);
}
}
else if (_lastVD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive))
{
MainVehicle.SoundHorn(1);
}
if (HasRocketBoost && VD.Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
MainVehicle.IsRocketBoostActive)
MainVehicle.IsRocketBoostActive = VD.Flags.HasVehFlag(VehicleDataFlags.IsRocketBoostActive);
if (HasParachute && VD.Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
!MainVehicle.IsParachuteDeployed)
MainVehicle.StartParachuting(false);
if (IsSubmarineCar)
{
if (Transformed)
{
if (!_lastTransformed)
{
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
}
}
else if (_lastTransformed)
{
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
}
}
else if (IsDeluxo)
{
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(VD.DeluxoWingRatio);
}
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
MainVehicle.LockStatus = VD.LockStatus;
}
_lastVD = VD;
if (LastFullSynced >= LastUpdated)
{
if (IsAircraft)
{
if (VDF.LandingGear != (byte)MainVehicle.LandingGearState)
MainVehicle.LandingGearState = (VehicleLandingGearState)VDF.LandingGear;
}
if (HasRoof && MainVehicle.RoofState != (VehicleRoofState)VDF.RoofState)
MainVehicle.RoofState = (VehicleRoofState)VDF.RoofState;
if (VD.Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
if (VDF.Colors != _lastVDF.Colors)
{
Call(SET_VEHICLE_COLOURS, MainVehicle, VDF.Colors.Item1, VDF.Colors.Item2);
}
MainVehicle.EngineHealth = VDF.EngineHealth;
if (VDF.ToggleModsMask != _lastVDF.ToggleModsMask)
{
for (int i = 0; i < 7; i++)
{
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (VDF.ToggleModsMask & (1 << i)) != 0);
}
}
if (VDF.Livery != _lastVDF.Livery)
{
Call(SET_VEHICLE_LIVERY, MainVehicle, VDF.Livery);
}
if (VDF.HeadlightColor != _lastVDF.HeadlightColor)
{
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, VDF.HeadlightColor);
}
if (!CoreUtils.StructCmp(VDF.DamageModel, _lastVDF.DamageModel))
{
MainVehicle.SetDamageModel(VDF.DamageModel);
}
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != VDF.RadioStation)
Util.SetPlayerRadioIndex(MainVehicle.Handle, VDF.RadioStation);
if (VDF.ExtrasMask != _lastVDF.ExtrasMask)
{
for (int i = 1; i < 15; i++)
{
var flag = (ushort)(1 << i);
var hasExtra = (AvalibleExtras & (ushort)(1 << i)) != 0;
if (!hasExtra)
continue;
var on = (VDF.ExtrasMask & flag) != 0;
Call(SET_VEHICLE_EXTRA, MainVehicle.Handle, i, !on);
}
}
if (VDV.Mods != null && (_lastVDV.Mods == null || !VDV.Mods.SequenceEqual(_lastVDV.Mods)))
{
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (var mod in VDV.Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
}
if (VDV.LicensePlate != _lastVDV.LicensePlate)
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, VDV.LicensePlate);
_lastVDF = VDF;
_lastVDV = VDV;
}
LastUpdated = Ticked;
}
private void DisplayVehicle()
{
_predictedPosition = Predict(Position);
var current = MainVehicle.ReadPosition();
var distSquared = current.DistanceToSquared(_predictedPosition);
var cali = _predictedPosition - current;
if (!IsTrain) cali += 0.5f * (Velocity - MainVehicle.Velocity);
if (distSquared > 10 * 10)
{
MainVehicle.Position = _predictedPosition;
MainVehicle.Velocity = Velocity;
MainVehicle.Quaternion = Quaternion;
return;
}
var stopped = Velocity == Vector3.Zero;
// Calibrate position
if (distSquared > 0.03 * 0.03)
{
if (IsTrain || distSquared > 20 * 20) MainVehicle.Velocity = Velocity + cali;
else MainVehicle.ApplyWorldForceCenterOfMass(cali, ForceType.InternalImpulse, true);
}
if (NeedUpdate || stopped)
{
// Calibrate rotation
var diff = Quaternion.Diff(MainVehicle.ReadQuaternion());
MainVehicle.WorldRotationVelocity = diff.ToEulerAngles() * RotCalMult;
}
}
private bool CreateVehicle()
{
MainVehicle?.Delete();
MainVehicle = Util.CreateVehicle(Model, Position);
if (!Model.IsInCdImage)
// GTA.UI.Notification.Show($"~r~(Vehicle)Model ({CurrentVehicleModelHash}) cannot be loaded!");
return false;
if (MainVehicle == null)
{
Model.Request();
return false;
}
lock (EntityPool.VehiclesLock)
{
EntityPool.Add(this);
}
MainVehicle.Quaternion = Quaternion;
if (MainVehicle.HasRoof) MainVehicle.RoofState = (VehicleRoofState)VDF.RoofState;
foreach (var w in MainVehicle.Wheels) w.Fix();
if (IsInvincible) MainVehicle.IsInvincible = true;
SetUpFixedData();
Model.MarkAsNoLongerNeeded();
return true;
}
#region -- CONSTRUCTORS --
/// <summary>
/// Create a local entity (outgoing sync)
/// </summary>
/// <param name="v"></param>
internal SyncedVehicle(Vehicle v)
{
ID = EntityPool.RequestNewID();
MainVehicle = v;
MainVehicle.CanPretendOccupants = false;
OwnerID = LocalPlayerID;
SetUpFixedData();
}
internal void SetUpFixedData()
{
if (MainVehicle == null) return;
IsAircraft = MainVehicle.IsAircraft;
IsMotorcycle = MainVehicle.IsMotorcycle;
HasRocketBoost = MainVehicle.HasRocketBoost;
HasParachute = MainVehicle.HasParachute;
HasRoof = MainVehicle.HasRoof;
IsSubmarineCar = MainVehicle.IsSubmarineCar;
IsDeluxo = MainVehicle.Model == 1483171323;
IsTrain = MainVehicle.IsTrain;
AvalibleExtras = 0;
for (int i = 1; i < 15; i++)
{
if (Call<bool>(DOES_EXTRA_EXIST, MainVehicle.Handle, i))
AvalibleExtras |= (ushort)(1 << i);
}
}
/// <summary>
/// Create an empty VehicleEntity
/// </summary>
internal SyncedVehicle()
{
}
internal SyncedVehicle(int id)
{
ID = id;
LastSynced = Ticked;
}
#endregion
#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.Secondary, 1.0f);
}
private void StopPedalingAnim(bool fast)
{
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
}
#endregion
}
}

View File

@ -1,203 +0,0 @@
using System;
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class SyncEvents
{
#region TRIGGER
public static void TriggerPedKilled(SyncedPed victim)
{
Networking.SendSync(new Packets.PedKilled { VictimID = victim.ID }, ConnectionChannel.SyncEvents);
}
public static void TriggerChangeOwner(int vehicleID, int newOwnerID)
{
Networking.SendSync(new Packets.OwnerChanged
{
ID = vehicleID,
NewOwnerID = newOwnerID
}, ConnectionChannel.SyncEvents, NetDeliveryMethod.ReliableOrdered);
}
public static void TriggerNozzleTransform(int vehID, bool hover)
{
Networking.SendSync(new Packets.NozzleTransform { VehicleID = vehID, Hover = hover },
ConnectionChannel.SyncEvents);
}
#endregion
#region HANDLE
public static ParticleEffectAsset CorePFXAsset = new ParticleEffectAsset("core");
private static void HandlePedKilled(Packets.PedKilled p)
{
EntityPool.GetPedByID(p.VictimID)?.MainPed?.Kill();
}
private static void HandleOwnerChanged(Packets.OwnerChanged p)
{
var v = EntityPool.GetVehicleByID(p.ID);
if (v == null) return;
v.OwnerID = p.NewOwnerID;
v.SetLastSynced(true);
v.Position = v.MainVehicle.Position;
v.Quaternion = v.MainVehicle.Quaternion;
}
private static void HandleNozzleTransform(Packets.NozzleTransform p)
{
EntityPool.GetVehicleByID(p.VehicleID)?.MainVehicle?.SetNozzleAngel(p.Hover ? 1 : 0);
}
private static void HandleBulletShot(int ownerID, uint weaponHash, Vector3 end)
{
var c = EntityPool.GetPedByID(ownerID);
var p = c?.MainPed;
if (p == null)
{
return;
// p = Game.Player.Character;
// Log.Warning("Failed to find owner for bullet");
}
var damage = (int)p.GetWeaponDamage(weaponHash);
// Some weapon hash has some firing issue, so we need to replace it with known good ones
weaponHash = WeaponUtil.GetWeaponFix(weaponHash);
// Request asset for muzzle flash
if (!CorePFXAsset.IsLoaded) CorePFXAsset.Request();
// Request asset for materialising the bullet
var asset = new WeaponAsset(weaponHash);
if (!asset.IsLoaded) asset.Request();
var vehWeap = p.VehicleWeapon;
bool isVeh = vehWeap != VehicleWeaponHash.Invalid;
var bone = isVeh ? c.MainPed.CurrentVehicle.GetMuzzleBone(vehWeap) : c.MainPed.GetMuzzleBone();
if (bone == null)
{
Log.Warning($"Failed to find muzzle bone for {(isVeh ? vehWeap : (WeaponHash)weaponHash)}, {(isVeh ? p.CurrentVehicle.DisplayName : "")}");
return;
}
World.ShootBullet(bone.Position, end, p, asset, damage);
World.CreateParticleEffectNonLooped(CorePFXAsset,
!isVeh && p.Weapons.Current.Components.GetSuppressorComponent().Active
? "muz_pistol_silencer"
: ((WeaponHash)weaponHash).GetFlashFX(isVeh), bone.Position, isVeh ? bone.GetRotation() : bone.Owner.Rotation);
}
public static void HandleEvent(PacketType type, NetIncomingMessage msg)
{
switch (type)
{
case PacketType.BulletShot:
{
var p = msg.GetPacket<Packets.BulletShot>();
HandleBulletShot(p.OwnerID, p.WeaponHash, p.EndPosition);
break;
}
case PacketType.OwnerChanged:
{
HandleOwnerChanged(msg.GetPacket<Packets.OwnerChanged>());
}
break;
case PacketType.PedKilled:
{
HandlePedKilled(msg.GetPacket<Packets.PedKilled>());
}
break;
case PacketType.NozzleTransform:
{
HandleNozzleTransform(msg.GetPacket<Packets.NozzleTransform>());
break;
}
}
Networking.Peer.Recycle(msg);
}
#endregion
#region CHECK EVENTS
public static void Check(SyncedPed c)
{
var subject = c.MainPed;
// Check bullets
if (subject.IsShooting && !subject.IsUsingProjectileWeapon())
{
var i = 0;
// Some weapon is not instant hit, so we may need to wait a few ticks to get the impact position
bool getBulletImpact()
{
var endPos = subject.LastWeaponImpactPosition;
var vehWeap = subject.VehicleWeapon;
if (vehWeap == VehicleWeaponHash.Invalid)
{
// Ped weapon sync
var pedWeap = subject.Weapons.Current.Hash;
if (endPos != default)
{
Networking.SendBullet(c.ID, (uint)pedWeap, endPos);
return true;
}
// Get impact in next tick
if (++i <= 5) return false;
// Exceeded maximum wait of 5 ticks, return (inaccurate) aim coordinate
endPos = subject.GetAimCoord();
Networking.SendBullet(c.ID, (uint)pedWeap, endPos);
return true;
}
else
{
// Veh weapon sync
if (endPos == default)
{
var veh = c.MainPed.CurrentVehicle;
var b = veh.GetMuzzleBone(vehWeap);
if (b == null)
{
Log.Warning($"Failed to find muzzle bone for {vehWeap}, {veh.DisplayName}");
return true;
}
endPos = b.Position + b.ForwardVector * 200;
}
Networking.SendBullet(c.ID, (uint)vehWeap, endPos);
return true;
}
}
if (!getBulletImpact()) API.QueueAction(getBulletImpact);
}
}
public static void Check(SyncedVehicle v)
{
if (v.MainVehicle == null || !v.MainVehicle.HasNozzle()) return;
if (v.LastNozzleAngle == 1 && v.MainVehicle.GetNozzleAngel() != 1)
TriggerNozzleTransform(v.ID, false);
else if (v.LastNozzleAngle == 0 && v.MainVehicle.GetNozzleAngel() != 0) TriggerNozzleTransform(v.ID, true);
v.LastNozzleAngle = v.MainVehicle.GetNozzleAngel();
}
#endregion
}
}

View File

@ -1,84 +0,0 @@
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace RageCoop.Client
{
/// <summary>
/// Needed to properly stop all thread when the module unloads
/// </summary>
internal static class ThreadManager
{
private static readonly List<Thread> _threads = new();
private static readonly Thread _watcher = new(RemoveStopped);
static ThreadManager()
{
_watcher.Start();
}
private static void RemoveStopped()
{
while (!IsUnloading)
{
lock (_threads)
{
_threads.RemoveAll(t => !t.IsAlive);
}
Thread.Sleep(1000);
}
}
public static Thread CreateThread(Action callback, string name = "CoopThread", bool startNow = true)
{
lock (_threads)
{
var created = new Thread(() =>
{
try
{
callback();
}
catch (ThreadInterruptedException) { }
catch (Exception ex)
{
Log.Error($"Unhandled exception caught in thread {Environment.CurrentManagedThreadId}", ex);
}
finally
{
Log.Debug($"Thread stopped: " + Environment.CurrentManagedThreadId);
}
})
{
Name = name
};
Log.Debug($"Thread created: {name}, id: {created.ManagedThreadId}");
_threads.Add(created);
if (startNow) created.Start();
return created;
}
}
public static void OnUnload()
{
Log.Debug("Stopping background threads");
lock (_threads)
{
foreach (var thread in _threads)
{
if (thread.IsAlive)
{
Log.Debug($"Waiting for thread {thread.ManagedThreadId} to stop");
// thread.Interrupt(); PlatformNotSupportedException ?
thread.Join();
}
}
_threads.Clear();
}
Log.Debug("Stopping thread watcher");
_watcher.Join();
}
}
}

View File

@ -1,9 +0,0 @@
<linker>
<assembly fullname="RageCoop.Client" preserve="all" />
<assembly fullname="RageCoop.Client.Scripting" preserve="all" />
<assembly fullname="RageCoop.Core" preserve="all" />
<assembly fullname="System.Collections">
<type fullname="System.Collections.Generic.List`1" preserve="all" />
<type fullname="System.Collections.Generic.Dictionary`2" preserve="all" />
</assembly>
</linker>

View File

@ -1,34 +0,0 @@
global using static RageCoop.Client.PInvoke;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace RageCoop.Client
{
internal partial class PInvoke
{
public static void ClearLastError()
{
SetLastErrorEx(0, 0);
}
/// <summary>
/// Check and throw if an error occurred during last winapi call in current thread
/// </summary>
public static void ErrorCheck32()
{
var error = Marshal.GetLastWin32Error();
if (error != 0)
{
ClearLastError();
throw new Win32Exception(error);
}
}
[LibraryImport("user32.dll", SetLastError = true)]
internal static partial void SetLastErrorEx(uint dwErrCode, uint dwType);
[LibraryImport("Kernel32.dll", SetLastError = true)]
public static unsafe partial uint GetFinalPathNameByHandleW(IntPtr hFile, char* lpszFilePath, uint cchFilePath, uint dwFlags);
}
}

View File

@ -1,275 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using LemonUI.Elements;
using RageCoop.Core;
using static RageCoop.Client.Shared;
using Font = GTA.UI.Font;
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
namespace RageCoop.Client
{
internal static partial class Util
{
/// <summary>
/// The location of the cursor on screen between 0 and 1.
/// </summary>
public static PointF CursorPositionRelative
{
get
{
var cursorX = Game.IsControlEnabled(Control.CursorX)
? Game.GetControlValueNormalized(Control.CursorX)
: Game.GetDisabledControlValueNormalized(Control.CursorX);
var cursorY = Game.IsControlEnabled(Control.CursorY)
? Game.GetControlValueNormalized(Control.CursorY)
: Game.GetDisabledControlValueNormalized(Control.CursorY);
return new PointF(cursorX, cursorY);
}
}
public static Point CursorPosition
{
get
{
var p = CursorPositionRelative;
var res = Screen.Resolution;
return new Point((int)(p.X * res.Width), (int)(p.Y * res.Height));
}
}
public static SizeF ResolutionMaintainRatio
{
get
{
// Get the game width and height
var screenw = Screen.Resolution.Width;
var screenh = Screen.Resolution.Height;
// Calculate the ratio
var ratio = (float)screenw / screenh;
// And the width with that ratio
var width = 1080f * ratio;
// Finally, return a SizeF
return new SizeF(width, 1080f);
}
}
public static Vector3 GetRotation(this EntityBone b)
{
return b.ForwardVector.ToEulerRotation(b.UpVector);
}
public static void DrawTextFromCoord(Vector3 coord, string text, float scale = 0.5f, Point offset = default)
{
Point toDraw = default;
if (WorldToScreen(coord, ref toDraw))
{
toDraw.X += offset.X;
toDraw.Y += offset.Y;
new ScaledText(toDraw, text, scale, Font.ChaletLondon)
{
Outline = true,
Alignment = Alignment.Center,
Color = Color.White
}.Draw();
}
}
public static unsafe bool WorldToScreen(Vector3 pos, ref Point screenPos)
{
float x, y;
var res = ResolutionMaintainRatio;
if (Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, pos.X, pos.Y, pos.Z, &x, &y))
{
screenPos = new Point((int)(res.Width * x), (int)(y * 1080));
return true;
}
return false;
}
public static ClientSettings ReadSettings(string path = null)
{
path = path ?? SettingsPath;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
ClientSettings settings;
try
{
settings = JsonDeserialize<ClientSettings>(File.ReadAllText(path));
}
catch (Exception ex)
{
Log?.Error(ex);
File.WriteAllText(path, JsonSerialize(settings = new ClientSettings()));
}
return settings;
}
public static bool SaveSettings(string path = null, ClientSettings settings = null)
{
try
{
path = path ?? SettingsPath;
settings = settings ?? Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
File.WriteAllText(path, JsonSerialize(settings));
return true;
}
catch (Exception ex)
{
Log?.Error(ex);
return false;
}
}
public static Vehicle CreateVehicle(Model model, Vector3 position, float heading = 0f)
{
if (!model.IsLoaded) return null;
return (Vehicle)Entity.FromHandle(Call<int>(CREATE_VEHICLE, model.Hash, position.X,
position.Y, position.Z, heading, false, false));
}
public static Ped CreatePed(Model model, Vector3 position, float heading = 0f)
{
if (!model.IsLoaded) return null;
return (Ped)Entity.FromHandle(Call<int>(CREATE_PED, 26, model.Hash, position.X, position.Y,
position.Z, heading, false, false));
}
public static void SetOnFire(this Entity e, bool toggle)
{
if (toggle)
Call(START_ENTITY_FIRE, e.Handle);
else
Call(STOP_ENTITY_FIRE, e.Handle);
}
public static void SetFrozen(this Entity e, bool toggle)
{
Call(FREEZE_ENTITY_POSITION, e, toggle);
}
public static SyncedPed GetSyncEntity(this Ped p)
{
if (p == null) return null;
var c = EntityPool.GetPedByHandle(p.Handle);
if (c == null) EntityPool.Add(c = new SyncedPed(p));
return c;
}
public static SyncedVehicle GetSyncEntity(this Vehicle veh)
{
if (veh == null) return null;
var v = EntityPool.GetVehicleByHandle(veh.Handle);
if (v == null) EntityPool.Add(v = new SyncedVehicle(veh));
return v;
}
/// <summary>
/// Get the current radio index for player, returns 255 if there's none
/// </summary>
/// <returns></returns>
public static byte GetPlayerRadioIndex()
{
return (byte)Call<int>(GET_PLAYER_RADIO_STATION_INDEX);
}
public static void SetPlayerRadioIndex(int playerVeh, int index)
{
if (playerVeh == 0)
playerVeh = Call<int>(GET_VEHICLE_PED_IS_IN, P.Handle, false);
if (index == byte.MaxValue)
Call(SET_VEH_RADIO_STATION, playerVeh, "OFF");
else
Call(SET_RADIO_TO_STATION_INDEX, index);
}
public static EntityPopulationType GetPopulationType(int handle)
=> (EntityPopulationType)Call<int>(GET_ENTITY_POPULATION_TYPE, handle);
public static unsafe void DeleteEntity(int handle)
{
Call(SET_ENTITY_AS_MISSION_ENTITY, handle, false, true);
Call(DELETE_ENTITY, &handle);
}
[LibraryImport("kernel32.dll")]
public static partial ulong GetTickCount64();
[LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
public static partial IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string lpModuleName);
#region -- POINTER --
private static int _steeringAngleOffset { get; set; }
public static unsafe void NativeMemory()
{
IntPtr address;
address = Game.FindPattern("\x74\x0A\xF3\x0F\x11\xB3\x1C\x09\x00\x00\xEB\x25", "xxxxxx????xx");
if (address != IntPtr.Zero) _steeringAngleOffset = *(int*)(address + 6) + 8;
}
public static unsafe void CustomSteeringAngle(this Vehicle veh, float value)
{
var address = new IntPtr((long)veh.MemoryAddress);
if (address == IntPtr.Zero || _steeringAngleOffset == 0) return;
*(float*)(address + _steeringAngleOffset).ToPointer() = value;
}
#endregion
#region MATH
public static Vector3 LinearVectorLerp(Vector3 start, Vector3 end, ulong currentTime, int duration)
{
return new Vector3
{
X = LinearFloatLerp(start.X, end.X, currentTime, duration),
Y = LinearFloatLerp(start.Y, end.Y, currentTime, duration),
Z = LinearFloatLerp(start.Z, end.Z, currentTime, duration)
};
}
public static float LinearFloatLerp(float start, float end, ulong currentTime, int duration)
{
return (end - start) * currentTime / duration + start;
}
public static float Lerp(float from, float to, float fAlpha)
{
return from * (1.0f - fAlpha) + to * fAlpha; //from + (to - from) * fAlpha
}
public static Vector3 RotationToDirection(Vector3 rotation)
{
var z = MathExtensions.DegToRad(rotation.Z);
var x = MathExtensions.DegToRad(rotation.X);
var num = Math.Abs(Math.Cos(x));
return new Vector3
{
X = (float)(-Math.Sin(z) * num),
Y = (float)(Math.Cos(z) * num),
Z = (float)Math.Sin(x)
};
}
#endregion
}
}

View File

@ -1,221 +0,0 @@
using System;
using System.Collections.Generic;
using GTA;
using GTA.Native;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class VehicleExtensions
{
#region VEHICLE
public static VehicleDataFlags GetVehicleFlags(this SyncedVehicle v)
{
var veh = v.MainVehicle;
VehicleDataFlags flags = 0;
if (veh.IsEngineRunning) flags |= VehicleDataFlags.IsEngineRunning;
if (veh.AreLightsOn) flags |= VehicleDataFlags.AreLightsOn;
if (veh.BrakePower >= 0.01f) flags |= VehicleDataFlags.AreBrakeLightsOn;
if (veh.AreHighBeamsOn) flags |= VehicleDataFlags.AreHighBeamsOn;
if (veh.IsSirenActive) flags |= VehicleDataFlags.IsSirenActive;
if (veh.IsDead) flags |= VehicleDataFlags.IsDead;
if (Call<bool>(IS_HORN_ACTIVE, veh.Handle)) flags |= VehicleDataFlags.IsHornActive;
if (v.IsSubmarineCar && Call<bool>(IS_VEHICLE_IN_SUBMARINE_MODE, veh.Handle))
flags |= VehicleDataFlags.IsTransformed;
if (v.IsAircraft) flags |= VehicleDataFlags.IsAircraft;
if (v.IsDeluxo && veh.IsDeluxoHovering()) flags |= VehicleDataFlags.IsDeluxoHovering;
if (v.HasRoof) flags |= VehicleDataFlags.HasRoof;
if (v.HasRocketBoost && veh.IsRocketBoostActive) flags |= VehicleDataFlags.IsRocketBoostActive;
if (v.HasParachute && veh.IsParachuteDeployed) flags |= VehicleDataFlags.IsParachuteActive;
if (veh.IsOnFire) flags |= VehicleDataFlags.IsOnFire;
return flags;
}
public static (int, int)[] GetVehicleMods(this SyncedVehicle sv, out byte togglesMask)
{
var modsArr = sv.MainVehicle.Mods.ToArray();
var result = new (int, int)[modsArr.Length];
for (int i = 0; i < modsArr.Length; i++)
{
result[i] = ((int)modsArr[i].Type, modsArr[i].Index);
}
togglesMask = 0;
for (int i = 0; i < 6; i++)
{
if (Call<bool>(IS_TOGGLE_MOD_ON, sv.MainVehicle.Handle, i + 17))
{
togglesMask |= (byte)(1 << i);
}
}
return result;
}
public static ushort GetVehicleExtras(this SyncedVehicle sv)
{
ushort result = 0;
for (int i = 1; i < 15; i++)
{
var flag = (ushort)(1 << i);
var hasExtra = (sv.AvalibleExtras & (ushort)(1 << i)) != 0;
if (hasExtra && Call<bool>(IS_VEHICLE_EXTRA_TURNED_ON, sv.MainVehicle.Handle, i))
result |= flag;
}
return result;
}
public static VehicleDamageModel GetVehicleDamageModel(this Vehicle veh)
{
// Broken windows
byte brokenWindows = 0;
for (var i = 0; i < 8; i++)
if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
brokenWindows |= (byte)(1 << i);
// Broken doors
byte brokenDoors = 0;
byte openedDoors = 0;
foreach (var door in veh.Doors)
if (door.IsBroken)
brokenDoors |= (byte)(1 << (byte)door.Index);
else if (door.IsOpen) openedDoors |= (byte)(1 << (byte)door.Index);
// Bursted tires
short burstedTires = 0;
foreach (var wheel in veh.Wheels.GetAllWheels())
if (wheel.IsBursted)
burstedTires |= (short)(1 << (int)wheel.BoneId);
return new VehicleDamageModel
{
BrokenDoors = brokenDoors,
OpenedDoors = openedDoors,
BrokenWindows = brokenWindows,
BurstedTires = burstedTires,
LeftHeadLightBroken = (byte)(veh.IsLeftHeadLightBroken ? 1 : 0),
RightHeadLightBroken = (byte)(veh.IsRightHeadLightBroken ? 1 : 0)
};
}
public static void SetDamageModel(this Vehicle veh, VehicleDamageModel model, bool leavedoors = true)
{
for (var i = 0; i < 8; i++)
{
var door = veh.Doors[(VehicleDoorIndex)i];
if ((model.BrokenDoors & (byte)(1 << i)) != 0)
{
if (!door.IsBroken) door.Break(leavedoors);
continue;
}
if (door.IsBroken)
{
// The vehicle can only fix a door if the vehicle was completely fixed
veh.Repair();
return;
}
if ((model.OpenedDoors & (byte)(1 << i)) != 0)
{
if (!door.IsOpen && !door.IsBroken) door.Open();
}
else if (door.IsOpen)
{
if (!door.IsBroken) door.Close();
}
if ((model.BrokenWindows & (byte)(1 << i)) != 0)
veh.Windows[(VehicleWindowIndex)i].Smash();
else if (!veh.Windows[(VehicleWindowIndex)i].IsIntact) veh.Windows[(VehicleWindowIndex)i].Repair();
}
foreach (var wheel in veh.Wheels)
if ((model.BurstedTires & (short)(1 << (int)wheel.BoneId)) != 0)
{
if (!wheel.IsBursted)
{
wheel.Puncture();
wheel.Burst();
}
}
else if (wheel.IsBursted)
{
wheel.Fix();
}
veh.IsLeftHeadLightBroken = model.LeftHeadLightBroken > 0;
veh.IsRightHeadLightBroken = model.RightHeadLightBroken > 0;
}
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{
Call(SET_SPECIAL_FLIGHT_MODE_TARGET_RATIO, deluxo, hover ? 1f : 0f);
}
public static bool IsDeluxoHovering(this Vehicle deluxo)
{
return Math.Abs(deluxo.Bones[27].ForwardVector.GetCosTheta(deluxo.ForwardVector) - 1) > 0.05;
}
public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
{
Call(SET_HOVER_MODE_WING_RATIO, v, ratio);
}
public static float GetDeluxoWingRatio(this Vehicle v)
{
return v.Bones[99].Position.DistanceTo(v.Bones[92].Position) - 1.43f;
}
public static float GetNozzleAngel(this Vehicle plane)
{
return Call<float>(GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
}
public static bool HasNozzle(this Vehicle v)
{
switch (v.Model.Hash)
{
// Hydra
case 970385471:
return true;
// Avenger
case -2118308144:
return true;
// Tula
case 1043222410:
return true;
// Avenger
case 408970549:
return true;
}
return false;
}
public static void SetNozzleAngel(this Vehicle plane, float ratio)
{
Call(SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
}
#endregion
}
}

View File

@ -1,196 +0,0 @@
using System.Collections.Generic;
using System.IO;
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal class WeaponFix
{
public Dictionary<uint, string> Bullet = new Dictionary<uint, string>();
public Dictionary<uint, string> Lazer = new Dictionary<uint, string>();
public Dictionary<uint, string> Others = new Dictionary<uint, string>();
}
internal static class WeaponUtil
{
public static Dictionary<uint, VehicleWeaponInfo> VehicleWeapons = new Dictionary<uint, VehicleWeaponInfo>();
public static WeaponFix WeaponFix;
public static Dictionary<uint, WeaponInfo> Weapons;
static WeaponUtil()
{
// Parse and load to memory
foreach (var w in JsonDeserialize<VehicleWeaponInfo[]>(
File.ReadAllText(VehicleWeaponDataPath))) VehicleWeapons.Add(w.Hash, w);
Weapons = JsonDeserialize<Dictionary<uint, WeaponInfo>>(
File.ReadAllText(WeaponInfoDataPath));
if (File.Exists(WeaponFixDataPath))
WeaponFix = JsonDeserialize<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
else
Log.Warning("Weapon fix data not found");
}
public static void DumpWeaponFix(string path)
{
var P = Game.Player.Character;
var pos = P.Position + Vector3.WorldUp * 3;
var types = new HashSet<int> { 3 };
P.IsInvincible = true;
var fix = new WeaponFix();
foreach (var w in Weapons)
{
Console.PrintInfo("Testing " + w.Value.Name);
if (w.Value.FireType != "PROJECTILE")
{
var asset = new WeaponAsset(w.Key);
asset.Request(1000);
World.ShootBullet(pos, pos + Vector3.WorldUp, P, asset, 0, 1000);
if (!Call<bool>(IS_BULLET_IN_AREA, pos.X, pos.Y, pos.Z, 10f, true) &&
!Call<bool>(IS_PROJECTILE_IN_AREA, pos.X - 10, pos.Y - 10, pos.Z - 10, pos.X + 10,
pos.Y + 10, pos.Z + 10, true))
switch (w.Value.DamageType)
{
case "BULLET":
fix.Bullet.Add(w.Key, w.Value.Name);
break;
case "EXPLOSIVE":
fix.Lazer.Add(w.Key, w.Value.Name);
break;
default:
fix.Others.Add(w.Key, w.Value.Name);
break;
}
foreach (var p in World.GetAllProjectiles()) p.Delete();
Script.Wait(50);
}
}
File.WriteAllText(path, JsonSerialize(fix));
P.IsInvincible = false;
}
public static uint GetWeaponFix(uint hash)
{
if (WeaponFix.Bullet.TryGetValue(hash, out _)) return 0x461DDDB0;
if (WeaponFix.Lazer.TryGetValue(hash, out _)) return 0xE2822A29;
return hash;
}
public static Dictionary<uint, bool> GetWeaponComponents(this Weapon weapon)
{
Dictionary<uint, bool> result = null;
if (weapon.Components.Count > 0)
{
result = new Dictionary<uint, bool>();
foreach (var comp in weapon.Components) result.Add((uint)comp.ComponentHash, comp.Active);
}
return result;
}
public static EntityBone GetMuzzleBone(this Ped p)
{
return p.Weapons.CurrentWeaponObject?.Bones["gun_muzzle"];
}
public static float GetWeaponDamage(this Ped P, uint hash)
{
var comp = P.Weapons.Current.Components.GetSuppressorComponent();
return Call<float>(GET_WEAPON_DAMAGE, hash,
comp.Active ? comp.ComponentHash : WeaponComponentHash.Invalid);
}
public static int GetMuzzleIndex(this Vehicle v, VehicleWeaponHash hash)
{
if (VehicleWeapons.TryGetValue((uint)v.Model.Hash, out var veh) &&
veh.Weapons.TryGetValue((uint)hash, out var wp))
return (int)wp.Bones[CoreUtils.RandInt(0, wp.Bones.Length)].BoneIndex;
return -1;
}
public static EntityBone GetMuzzleBone(this Vehicle v, VehicleWeaponHash hash)
{
if ((uint)hash == 1422046295) hash = VehicleWeaponHash.WaterCannon;
var i = v.GetMuzzleIndex(hash);
if (i == -1) return null;
return v.Bones[i];
}
public static bool IsUsingProjectileWeapon(this Ped p)
{
var vp = p.VehicleWeapon;
return Weapons.TryGetValue(vp != VehicleWeaponHash.Invalid ? (uint)vp : (uint)p.Weapons.Current.Hash,
out var info)
&& info.FireType == "PROJECTILE";
}
public static string GetFlashFX(this WeaponHash w, bool veh)
{
if (veh)
switch ((VehicleWeaponHash)w)
{
case VehicleWeaponHash.Tank:
return "muz_tank";
default: return "muz_buzzard";
}
switch (w.GetWeaponGroup())
{
case WeaponGroup.SMG:
return "muz_smg";
case WeaponGroup.Shotgun:
return "muz_smg";
case WeaponGroup.AssaultRifle:
return "muz_assault_rifle";
case WeaponGroup.Pistol:
return "muz_pistol";
case WeaponGroup.Stungun:
return "muz_stungun";
case WeaponGroup.Heavy:
switch (w)
{
case WeaponHash.Minigun:
return "muz_minigun";
case WeaponHash.RPG:
return "muz_rpg";
default:
return "muz_minigun";
}
case WeaponGroup.Sniper:
return "muz_alternate_star";
case WeaponGroup.PetrolCan:
return "weap_petrol_can";
case WeaponGroup.FireExtinguisher:
return "weap_extinguisher";
default:
return "muz_assault_rifle";
}
}
public static WeaponGroup GetWeaponGroup(this WeaponHash hash)
{
return Call<WeaponGroup>(GET_WEAPONTYPE_GROUP, hash);
}
}
}

View File

@ -1,256 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GTA;
using GTA.Native;
using GTA.UI;
using RageCoop.Client.Menus;
namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
[ScriptAttributes(Author = "RageCoop", 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>>();
private static bool _trafficEnabled;
/// <summary>
/// Don't use it!
/// </summary>
public WorldThread()
{
Instance = this;
Aborted += (e) => { DoQueuedActions(); ChangeTraffic(true); };
}
protected override void OnStart()
{
base.OnStart();
while (Game.IsLoading)
Yield();
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
}
protected override void OnTick()
{
base.OnTick();
if (_sleeping)
{
Game.Pause(true);
while (_sleeping)
{
// Don't wait longer than 5 seconds or the game will crash
Thread.Sleep(4500);
Yield();
}
Game.Pause(false);
}
if (Game.IsLoading) return;
try
{
CoopMenu.MenuPool.Process();
DoQueuedActions();
}
catch (Exception ex)
{
Log.Error(ex);
}
if (!Networking.IsOnServer) return;
Game.DisableControlThisFrame(Control.FrontendPause);
if (Settings.DisableAlternatePause) Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
// Sets a value that determines how aggressive the ocean waves will be.
// Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
Call(SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
if (Settings.ShowEntityOwnerName)
unsafe
{
int handle;
if (Call<bool>(GET_ENTITY_PLAYER_IS_FREE_AIMING_AT, 0, &handle))
{
var entity = Entity.FromHandle(handle);
if (entity != null)
{
var owner = "invalid";
if (entity.EntityType == EntityType.Vehicle)
owner = (entity as Vehicle).GetSyncEntity()?.Owner?.Username ?? "unknown";
if (entity.EntityType == EntityType.Ped)
owner = (entity as Ped).GetSyncEntity()?.Owner?.Username ?? "unknown";
Screen.ShowHelpTextThisFrame("Entity owner: " + owner);
}
}
}
if (!_trafficEnabled)
{
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
Call(SET_PED_POPULATION_BUDGET, 0);
Call(SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
Call(SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
}
}
public static void Traffic(bool enable)
{
ChangeTraffic(enable);
_trafficEnabled = enable;
}
private static void ChangeTraffic(bool enable)
{
if (enable)
{
Call(REMOVE_SCENARIO_BLOCKING_AREAS);
Call(SET_CREATE_RANDOM_COPS, true);
Call(SET_RANDOM_TRAINS, true);
Call(SET_RANDOM_BOATS, true);
Call(SET_GARBAGE_TRUCKS, true);
Call(SET_PED_POPULATION_BUDGET, 3); // 0 - 3
Call(SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
Call(SET_ALL_VEHICLE_GENERATORS_ACTIVE);
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
Call(SET_NUMBER_OF_PARKED_VEHICLES, -1);
Call(SET_DISTANT_CARS_ENABLED, true);
Call(DISABLE_VEHICLE_DISTANTLIGHTS, false);
}
else if (Networking.IsOnServer)
{
Call(ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f,
1000.0f, 0, 1, 1, 1);
Call(SET_CREATE_RANDOM_COPS, false);
Call(SET_RANDOM_TRAINS, false);
Call(SET_RANDOM_BOATS, false);
Call(SET_GARBAGE_TRUCKS, false);
Call(DELETE_ALL_TRAINS);
Call(SET_PED_POPULATION_BUDGET, 0);
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Call(SET_FAR_DRAW_VEHICLES, false);
Call(SET_NUMBER_OF_PARKED_VEHICLES, 0);
Call(SET_DISTANT_CARS_ENABLED, false);
Call(DISABLE_VEHICLE_DISTANTLIGHTS, true);
foreach (var ped in World.GetAllPeds())
{
if (ped == Game.Player.Character) continue;
var c = EntityPool.GetPedByHandle(ped.Handle);
if (c == null || (c.IsLocal && ped.Handle != Game.Player.Character.Handle &&
ped.PopulationType != EntityPopulationType.Mission))
{
Log.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
ped.CurrentVehicle?.Delete();
ped.Kill();
ped.Delete();
}
}
foreach (var veh in World.GetAllVehicles())
{
var v = veh.GetSyncEntity();
if (v.MainVehicle == Game.Player.LastVehicle ||
v.MainVehicle == Game.Player.Character.CurrentVehicle)
// Don't delete player's vehicle
continue;
if (v == null || (v.IsLocal && veh.PopulationType != EntityPopulationType.Mission))
// Log.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
veh.Delete();
}
}
}
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)
{
Log.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();
}
}
private static bool _sleeping;
[ConsoleCommand("Put the game to sleep state by blocking main thread, press any key in the debug console to resume")]
public static void Sleep()
{
if (_sleeping)
throw new InvalidOperationException("Already in sleep state");
_sleeping = true;
Task.Run(() =>
{
System.Console.WriteLine("Press any key to put the game out of sleep state");
System.Console.ReadKey();
System.Console.WriteLine("Game resumed");
_sleeping = false;
});
}
}
}

View File

@ -1,20 +0,0 @@
<?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>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,245 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using GTA.Math;
namespace RageCoop.Core
{
public unsafe abstract class Buffer
{
/// <summary>
/// Size of this buffer in memory
/// </summary>
public int Size { get; protected set; }
/// <summary>
/// The current read/write index
/// </summary>
public int Position { get; protected set; }
/// <summary>
/// Pointer to the start of this buffer
/// </summary>
public byte* Address { get; protected set; }
/// <summary>
/// Ensure memory safety and advance position by specified number of bytes
/// </summary>
/// <param name="cbSize"></param>
/// <returns>Pointer to the current position in the buffer</returns>
protected abstract byte* Alloc(int cbSize);
protected T* Alloc<T>(int count = 1) where T : unmanaged
=> (T*)Alloc(count * sizeof(T));
/// <summary>
/// Reset position to the start of this buffer
/// </summary>
public void Reset()
{
Position = 0;
}
}
public unsafe sealed class BufferWriter : Buffer
{
/// <summary>
/// Gets a thread local instance of this writer
/// </summary>
public static readonly ThreadLocal<BufferWriter> ThreadLocal = new(() => new(4096));
public BufferWriter(int size)
{
Resize(size);
}
public void Resize(int size)
{
if (size < Size)
{
Size = size;
return;
}
var newAddr = (byte*)Marshal.AllocHGlobal(size);
if (Address != null)
{
System.Buffer.MemoryCopy(Address, newAddr, size, Size);
Marshal.FreeHGlobal((IntPtr)Address);
}
Size = size;
Address = newAddr;
}
protected override byte* Alloc(int cbSize)
{
var index = Position;
Position += cbSize;
// Resize the buffer by at least 50% if there's no sufficient space
if (Position > Size)
Resize(Math.Max(Position + 1, (int)(Size * 1.5f)));
return Address + index;
}
public void Write<T>(ref T value) where T : unmanaged
{
var addr = Alloc<T>();
*addr = value;
}
/// <summary>
/// For passing struct smaller than word size (4/8 bytes on 32/64 bit system)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
public void WriteVal<T>(T value) where T : unmanaged
{
var addr = Alloc<T>();
*addr = value;
}
public void Write(ReadOnlySpan<char> str)
{
// Prefixed by its size in bytes
var cbBody = Encoding.UTF8.GetByteCount(str);
WriteVal(cbBody);
// Allocate and write string body
var pBody = Alloc(cbBody);
Encoding.UTF8.GetBytes(str, new(pBody, cbBody));
}
public void Write<T>(ReadOnlySpan<T> source) where T : unmanaged
{
var len = source.Length;
fixed (T* pSource = source)
{
System.Buffer.MemoryCopy(pSource, Alloc(sizeof(T) * len), len, len);
}
}
public void Write<T>(Span<T> source) where T : unmanaged => Write((ReadOnlySpan<T>)source);
/// <summary>
/// Write an array, prefix the data with its length so it can latter be read using <see cref="BufferReader.ReadArray{T}"/>
/// </summary>
public void WriteArray<T>(T[] values) where T : unmanaged
{
var len = values.Length;
WriteVal(len);
fixed (T* pFrom = values)
{
System.Buffer.MemoryCopy(pFrom, Alloc(sizeof(T) * len), len, len);
}
}
/// <summary>
/// Allocate a byte array on managed heap and copy the data of specified size to it
/// </summary>
/// <param name="cbSize"></param>
/// <returns>The newly created managed byte array</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public byte[] ToByteArray(int cbSize)
{
if (cbSize > Size)
throw new ArgumentOutOfRangeException(nameof(cbSize));
var result = new byte[cbSize];
fixed (byte* pResult = result)
{
System.Buffer.MemoryCopy(Address, pResult, cbSize, cbSize);
}
return result;
}
/// <summary>
/// Free the associated memory allocated on the unmanaged heap
/// </summary>
public void Free() => Marshal.FreeHGlobal((IntPtr)Address);
}
public unsafe sealed class BufferReader : Buffer
{
/// <summary>
/// Gets a thread local instance of this reader
/// </summary>
public static readonly ThreadLocal<BufferReader> ThreadLocal = new(() => new());
/// <summary>
/// Initialize an empty instance, needs to call <see cref="Initialise(byte*, int)"/> before reading data
/// </summary>
public BufferReader()
{
}
public BufferReader(byte* address, int size) => Initialise(address, size);
public void Initialise(byte* address, int size)
{
Address = address;
Size = size;
Reset();
}
protected override byte* Alloc(int cbSize)
{
if (Address == null)
throw new NullReferenceException("Address is null");
var index = Position;
Position += cbSize;
if (Position > Size)
throw new InvalidOperationException("Attempting to read beyond the existing buffer");
return Address + index;
}
public T ReadVal<T>() where T : unmanaged => *Alloc<T>();
public void Read<T>(out T result) where T : unmanaged
=> result = *Alloc<T>();
public void Read(out string str)
{
var cbBody = ReadVal<int>();
str = Encoding.UTF8.GetString(Alloc(cbBody), cbBody);
}
/// <summary>
/// Read a span of type <typeparamref name="T"/> from current position to <paramref name="destination"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="destination"></param>
public void Read<T>(Span<T> destination) where T : unmanaged
{
var len = destination.Length;
fixed (T* pTo = destination)
{
System.Buffer.MemoryCopy(Alloc(len * sizeof(T)), pTo, len, len);
}
}
/// <summary>
/// Reads an array previously written using <see cref="BufferWriter.WriteArray{T}(T[])"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T[] ReadArray<T>() where T : unmanaged
{
var len = ReadVal<int>();
var from = Alloc<T>(len);
var result = new T[len];
fixed (T* pTo = result)
{
System.Buffer.MemoryCopy(from, pTo, len, len);
}
return result;
}
}
}

View File

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GTA.Math;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
internal struct LQuaternion
{
public LQuaternion(float x, float y, float z, float w)
{
X = x; Y = y; Z = z; W = w;
}
public float X, Y, Z, W;
public static implicit operator LQuaternion(Quaternion q) => new(q.X, q.Y, q.Z, q.W);
public static implicit operator Quaternion(LQuaternion q) => new(q.X, q.Y, q.Z, q.W);
}
}

View File

@ -1,967 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA.Math;
namespace RageCoop.Core.CompactVectors
{
internal struct LQuaternion : IEquatable<LQuaternion>
{
/// <summary>
/// Gets or sets the X component of the quaternion.
/// </summary>
/// <value>The X component of the quaternion.</value>
public float X;
/// <summary>
/// Gets or sets the Y component of the quaternion.
/// </summary>
/// <value>The Y component of the quaternion.</value>
public float Y;
/// <summary>
/// Gets or sets the Z component of the quaternion.
/// </summary>
/// <value>The Z component of the quaternion.</value>
public float Z;
/// <summary>
/// Gets or sets the W component of the quaternion.
/// </summary>
/// <value>The W component of the quaternion.</value>
public float W;
/// <summary>
/// Initializes a new instance of the <see cref="LQuaternion"/> structure.
/// </summary>
/// <param name="x">The X component of the quaternion.</param>
/// <param name="y">The Y component of the quaternion.</param>
/// <param name="z">The Z component of the quaternion.</param>
/// <param name="w">The W component of the quaternion.</param>
public LQuaternion(float x, float y, float z, float w) : this()
{
X = x;
Y = y;
Z = z;
W = w;
}
/// <summary>
/// Initializes a new instance of the <see cref="LQuaternion"/> structure.
/// </summary>
/// <param name="axis">The axis of rotation.</param>
/// <param name="angle">The angle of rotation in radians.</param>
public LQuaternion(Vector3 axis, float angle) : this()
{
axis = Vector3.Normalize(axis);
float half = angle * 0.5f;
float sin = (float)(System.Math.Sin((double)(half)));
float cos = (float)(System.Math.Cos((double)(half)));
X = axis.X * sin;
Y = axis.Y * sin;
Z = axis.Z * sin;
W = cos;
}
/// <summary>
/// A <see cref="LQuaternion"/> with all of its components set to zero.
/// </summary>
public static LQuaternion Zero => new LQuaternion();
/// <summary>
/// A <see cref="LQuaternion"/> with all of its components set to one.
/// </summary>
public static LQuaternion One => new LQuaternion(1.0f, 1.0f, 1.0f, 1.0f);
/// <summary>
/// The identity <see cref="LQuaternion"/> (0, 0, 0, 1).
/// </summary>
public static LQuaternion Identity => new LQuaternion(0.0f, 0.0f, 0.0f, 1.0f);
/// <summary>
/// Gets the axis components of the quaternion.
/// </summary>
public Vector3 Axis
{
get
{
if (Length() != 1.0f)
{
return Vector3.Zero;
}
float length = 1.0f - (W * W);
if (length == 0f)
{
return Vector3.UnitX;
}
float inv = 1.0f / (float)System.Math.Sqrt(length);
return new Vector3(X * inv, Y * inv, Z * inv);
}
}
/// <summary>
/// Gets the angle of the quaternion.
/// </summary>
public float Angle => ((System.Math.Abs(W) <= 1.0f) ? 2.0f * (float)(System.Math.Acos(W)) : 0.0f);
/// <summary>
/// Calculates the length of the quaternion.
/// </summary>
/// <returns>The length of the quaternion.</returns>
public float Length() => (float)System.Math.Sqrt((X * X) + (Y * Y) + (Z * Z) + (W * W));
/// <summary>
/// Calculates the squared length of the quaternion.
/// </summary>
/// <returns>The squared length of the quaternion.</returns>
public float LengthSquared() => (X * X) + (Y * Y) + (Z * Z) + (W * W);
/// <summary>
/// Converts the quaternion into a unit quaternion.
/// </summary>
public void Normalize()
{
float length = Length();
if (length != 0f)
{
float inverse = 1.0f / length;
X *= inverse;
Y *= inverse;
Z *= inverse;
W *= inverse;
}
}
/// <summary>
/// Conjugates the quaternion.
/// </summary>
public void Conjugate()
{
X = -X;
Y = -Y;
Z = -Z;
}
/// <summary>
/// Conjugates and renormalizes the quaternion.
/// </summary>
public void Invert()
{
float lengthSq = LengthSquared();
if (lengthSq != 0f)
{
lengthSq = 1.0f / lengthSq;
X = -X * lengthSq;
Y = -Y * lengthSq;
Z = -Z * lengthSq;
W = W * lengthSq;
}
}
/// <summary>
/// Reverses the direction of a given quaternion.
/// </summary>
/// <param name="quaternion">The quaternion to negate.</param>
/// <returns>A quaternion facing in the opposite direction.</returns>
public static LQuaternion Negate(LQuaternion quaternion)
{
LQuaternion result = Zero;
result.X = -quaternion.X;
result.Y = -quaternion.Y;
result.Z = -quaternion.Z;
result.W = -quaternion.W;
return result;
}
/// <summary>
/// Adds two quaternions.
/// </summary>
/// <param name="left">The first quaternion to add.</param>
/// <param name="right">The second quaternion to add.</param>
/// <returns>The sum of the two quaternions.</returns>
public static LQuaternion Add(LQuaternion left, LQuaternion right)
{
LQuaternion result = Zero;
result.X = left.X + right.X;
result.Y = left.Y + right.Y;
result.Z = left.Z + right.Z;
result.W = left.W + right.W;
return result;
}
/// <summary>
/// Subtracts two quaternions.
/// </summary>
/// <param name="left">The first quaternion to subtract.</param>
/// <param name="right">The second quaternion to subtract.</param>
/// <returns>The difference of the two quaternions.</returns>
public static LQuaternion Subtract(LQuaternion left, LQuaternion right)
{
LQuaternion result = Zero;
result.X = left.X - right.X;
result.Y = left.Y - right.Y;
result.Z = left.Z - right.Z;
result.W = left.W - right.W;
return result;
}
/// <summary>
/// Multiplies two Quaternions together.
/// </summary>
/// <param name="left">The Quaternion on the left side of the multiplication.</param>
/// <param name="right">The Quaternion on the right side of the multiplication.</param>
/// <returns>The result of the multiplication.</returns>
public static LQuaternion Multiply(LQuaternion left, LQuaternion right)
{
LQuaternion quaternion;
float lx = left.X;
float ly = left.Y;
float lz = left.Z;
float lw = left.W;
float rx = right.X;
float ry = right.Y;
float rz = right.Z;
float rw = right.W;
quaternion.X = (lx * rw + rx * lw) + (ly * rz) - (lz * ry);
quaternion.Y = (ly * rw + ry * lw) + (lz * rx) - (lx * rz);
quaternion.Z = (lz * rw + rz * lw) + (lx * ry) - (ly * rx);
quaternion.W = (lw * rw) - (lx * rx + ly * ry + lz * rz);
return quaternion;
}
/// <summary>
/// Scales a quaternion by the given value.
/// </summary>
/// <param name="quaternion">The quaternion to scale.</param>
/// <param name="scale">The amount by which to scale the quaternion.</param>
/// <returns>The scaled quaternion.</returns>
public static LQuaternion Multiply(LQuaternion quaternion, float scale)
{
LQuaternion result = Zero;
result.X = quaternion.X * scale;
result.Y = quaternion.Y * scale;
result.Z = quaternion.Z * scale;
result.W = quaternion.W * scale;
return result;
}
/// <summary>
/// Divides a quaternion by another.
/// </summary>
/// <param name="left">The first quaternion to divide.</param>
/// <param name="right">The second quaternion to divide.</param>
/// <returns>The divided quaternion.</returns>
public static LQuaternion Divide(LQuaternion left, LQuaternion right)
{
return Multiply(left, Invert(right));
}
/// <summary>
/// Converts the quaternion into a unit quaternion.
/// </summary>
/// <param name="quaternion">The quaternion to normalize.</param>
/// <returns>The normalized quaternion.</returns>
public static LQuaternion Normalize(LQuaternion quaternion)
{
quaternion.Normalize();
return quaternion;
}
/// <summary>
/// Creates the conjugate of a specified Quaternion.
/// </summary>
/// <param name="value">The Quaternion of which to return the conjugate.</param>
/// <returns>A new Quaternion that is the conjugate of the specified one.</returns>
public static LQuaternion Conjugate(LQuaternion value)
{
LQuaternion ans;
ans.X = -value.X;
ans.Y = -value.Y;
ans.Z = -value.Z;
ans.W = value.W;
return ans;
}
/// <summary>
/// Conjugates and renormalizes the quaternion.
/// </summary>
/// <param name="quaternion">The quaternion to conjugate and re-normalize.</param>
/// <returns>The conjugated and renormalized quaternion.</returns>
public static LQuaternion Invert(LQuaternion quaternion)
{
LQuaternion result = Zero;
float lengthSq = 1.0f / ((quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) + (quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W));
result.X = -quaternion.X * lengthSq;
result.Y = -quaternion.Y * lengthSq;
result.Z = -quaternion.Z * lengthSq;
result.W = quaternion.W * lengthSq;
return result;
}
/// <summary>
/// Calculates the dot product of two quaternions.
/// </summary>
/// <param name="left">First source quaternion.</param>
/// <param name="right">Second source quaternion.</param>
/// <returns>The dot product of the two quaternions.</returns>
public static float Dot(LQuaternion left, LQuaternion right) => (left.X * right.X) + (left.Y * right.Y) + (left.Z * right.Z) + (left.W * right.W);
/// <summary>
/// Performs a linear interpolation between two quaternion.
/// </summary>
/// <param name="start">Start quaternion.</param>
/// <param name="end">End quaternion.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
/// <returns>The linear interpolation of the two quaternions.</returns>
/// <remarks>
/// This method performs the linear interpolation based on the following formula.
/// <code>start + (end - start) * amount</code>
/// Passing <paramref name="amount"/> a value of 0 will cause <paramref name="start"/> to be returned; a value of 1 will cause <paramref name="end"/> to be returned.
/// </remarks>
public static LQuaternion Lerp(LQuaternion start, LQuaternion end, float amount)
{
LQuaternion result = Zero;
float inverse = 1.0f - amount;
float dot = (start.X * end.X) + (start.Y * end.Y) + (start.Z * end.Z) + (start.W * end.W);
if (dot >= 0.0f)
{
result.X = (inverse * start.X) + (amount * end.X);
result.Y = (inverse * start.Y) + (amount * end.Y);
result.Z = (inverse * start.Z) + (amount * end.Z);
result.W = (inverse * start.W) + (amount * end.W);
}
else
{
result.X = (inverse * start.X) - (amount * end.X);
result.Y = (inverse * start.Y) - (amount * end.Y);
result.Z = (inverse * start.Z) - (amount * end.Z);
result.W = (inverse * start.W) - (amount * end.W);
}
float invLength = 1.0f / result.Length();
result.X *= invLength;
result.Y *= invLength;
result.Z *= invLength;
result.W *= invLength;
return result;
}
/// <summary>
/// Interpolates between two quaternions, using spherical linear interpolation..
/// </summary>
/// <param name="start">Start quaternion.</param>
/// <param name="end">End quaternion.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
/// <returns>The spherical linear interpolation of the two quaternions.</returns>
public static LQuaternion Slerp(LQuaternion start, LQuaternion end, float amount)
{
LQuaternion result = Zero;
float kEpsilon = (float)(1.192093E-07);
float opposite;
float inverse;
float dot = Dot(start, end);
if (System.Math.Abs(dot) > (1.0f - kEpsilon))
{
inverse = 1.0f - amount;
opposite = amount * System.Math.Sign(dot);
}
else
{
float acos = (float)System.Math.Acos(System.Math.Abs(dot));
float invSin = (float)(1.0 / System.Math.Sin(acos));
inverse = (float)(System.Math.Sin((1.0f - amount) * acos) * invSin);
opposite = (float)(System.Math.Sin(amount * acos) * invSin * System.Math.Sign(dot));
}
result.X = (inverse * start.X) + (opposite * end.X);
result.Y = (inverse * start.Y) + (opposite * end.Y);
result.Z = (inverse * start.Z) + (opposite * end.Z);
result.W = (inverse * start.W) + (opposite * end.W);
return result;
}
/// <summary>
/// Interpolates between two quaternions, using spherical linear interpolation. The parameter /t/ is not clamped.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="t"></param>
public static LQuaternion SlerpUnclamped(LQuaternion a, LQuaternion b, float t)
{
if (a.LengthSquared() == 0.0f)
{
if (b.LengthSquared() == 0.0f)
{
return Identity;
}
return b;
}
else if (b.LengthSquared() == 0.0f)
{
return a;
}
float cosHalfAngle = a.W * b.W + Vector3.Dot(a.Axis, b.Axis);
if (cosHalfAngle >= 1.0f || cosHalfAngle <= -1.0f)
{
return a;
}
else if (cosHalfAngle < 0.0f)
{
b.X = -b.X;
b.Y = -b.Y;
b.Z = -b.Z;
b.W = -b.W;
cosHalfAngle = -cosHalfAngle;
}
float blendA;
float blendB;
if (cosHalfAngle < 0.99f)
{
float halfAngle = (float)System.Math.Acos(cosHalfAngle);
float sinHalfAngle = (float)System.Math.Sin(halfAngle);
float oneOverSinHalfAngle = 1.0f / sinHalfAngle;
blendA = (float)System.Math.Sin(halfAngle * (1.0f - t)) * oneOverSinHalfAngle;
blendB = (float)System.Math.Sin(halfAngle * t) * oneOverSinHalfAngle;
}
else
{
blendA = 1.0f - t;
blendB = t;
}
LQuaternion result = new LQuaternion(blendA * a.Axis + blendB * b.Axis, blendA * a.W + blendB * b.W);
if (result.LengthSquared() > 0.0f)
{
return Normalize(result);
}
else
{
return Identity;
}
}
/// <summary>
/// Creates a rotation with the specified <paramref name="forward"/> and <see cref="Vector3.WorldUp"/> directions.
/// </summary>
public static LQuaternion LookRotation(Vector3 forward) => LookRotation(forward, Vector3.WorldUp);
/// <summary>
/// Creates a rotation with the specified <paramref name="forward"/> and <paramref name="up"/> directions.
/// </summary>
public static LQuaternion LookRotation(Vector3 forward, Vector3 up) => DirectionVectors(Vector3.Cross(forward, up), forward, up);
/// <summary>
/// Creates a rotation which rotates from fromDirection to toDirection.
/// </summary>
public static LQuaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection)
{
float NormAB = (float)(System.Math.Sqrt(fromDirection.LengthSquared() * fromDirection.LengthSquared()));
float w = NormAB + Vector3.Dot(fromDirection, toDirection);
LQuaternion Result;
if (w >= 1e-6f * NormAB)
{
Result = new LQuaternion(Vector3.Cross(fromDirection, toDirection), w);
}
else
{
w = 0.0f;
Result = System.Math.Abs(fromDirection.X) > System.Math.Abs(fromDirection.Y)
? new LQuaternion(-fromDirection.Z, 0.0f, fromDirection.X, w)
: new LQuaternion(0.0f, -fromDirection.Z, fromDirection.Y, w);
}
Result.Normalize();
return Result;
}
/// <summary>
/// Rotates a rotation from towards to.
/// </summary>
/// <param name="from">From Quaternion.</param>
/// <param name="to">To Quaternion.</param>
/// <param name ="maxDegreesDelta"></param>
public static LQuaternion RotateTowards(LQuaternion from, LQuaternion to, float maxDegreesDelta)
{
float angle = AngleBetween(from, to);
if (angle == 0.0f)
{
return to;
}
float t = System.Math.Min(1.0f, maxDegreesDelta / angle);
return SlerpUnclamped(from, to, t);
}
/// <summary>
/// Returns the angle in degrees between two rotations a and b.
/// </summary>
/// <param name="a">The first quaternion to calculate angle.</param>
/// <param name="b">The second quaternion to calculate angle.</param>
/// <returns>The angle in degrees between two rotations a and b.</returns>
public static float AngleBetween(LQuaternion a, LQuaternion b)
{
float dot = Dot(a, b);
return (float)((System.Math.Acos(System.Math.Min(System.Math.Abs(dot), 1.0f)) * 2.0 * (180.0f / System.Math.PI)));
}
/// <summary>
/// Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).
/// </summary>
/// <param name="zaxis">Z degrees.</param>
/// <param name ="xaxis">X degrees.</param>
/// <param name ="yaxis">Y degrees.</param>
public static LQuaternion Euler(float zaxis, float xaxis, float yaxis)
{
float Deg2Rad = (float)((System.Math.PI / 180.0));
return RotationYawPitchRoll(zaxis * Deg2Rad, xaxis * Deg2Rad, yaxis * Deg2Rad);
}
/// <summary>
/// Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).
/// </summary>
/// <param name="euler">Euler angles in degrees. euler.X = around X axis, euler.Y = around Y axis, euler.Z = around Z axis</param>
public static LQuaternion Euler(Vector3 euler)
{
Vector3 eulerRad = euler * (float)((System.Math.PI / 180.0));
return RotationYawPitchRoll(eulerRad.Z, eulerRad.X, eulerRad.Y);
}
/// <summary>
/// Creates a quaternion given a rotation and an axis.
/// </summary>
/// <param name="axis">The axis of rotation.</param>
/// <param name="angle">The angle of rotation in radians.</param>
/// <returns>The newly created quaternion.</returns>
public static LQuaternion RotationAxis(Vector3 axis, float angle)
{
LQuaternion result = Zero;
axis = Vector3.Normalize(axis);
float half = angle * 0.5f;
float sin = (float)(System.Math.Sin((double)(half)));
float cos = (float)(System.Math.Cos((double)(half)));
result.X = axis.X * sin;
result.Y = axis.Y * sin;
result.Z = axis.Z * sin;
result.W = cos;
return result;
}
/// <summary>
/// Creates a quaternion given a rotation matrix.
/// </summary>
/// <param name="matrix">The rotation matrix.</param>
/// <returns>The newly created quaternion.</returns>
public static LQuaternion RotationMatrix(Matrix matrix)
{
LQuaternion result = Zero;
float sqrt;
float half;
float scale = matrix.M11 + matrix.M22 + matrix.M33;
if (scale > 0.0f)
{
sqrt = (float)System.Math.Sqrt(scale + 1.0f);
result.W = sqrt * 0.5f;
sqrt = 0.5f / sqrt;
result.X = (matrix.M23 - matrix.M32) * sqrt;
result.Y = (matrix.M31 - matrix.M13) * sqrt;
result.Z = (matrix.M12 - matrix.M21) * sqrt;
}
else if ((matrix.M11 >= matrix.M22) && (matrix.M11 >= matrix.M33))
{
sqrt = (float)System.Math.Sqrt(1.0f + matrix.M11 - matrix.M22 - matrix.M33);
half = 0.5f / sqrt;
result.X = 0.5f * sqrt;
result.Y = (matrix.M12 + matrix.M21) * half;
result.Z = (matrix.M13 + matrix.M31) * half;
result.W = (matrix.M23 - matrix.M32) * half;
}
else if (matrix.M22 > matrix.M33)
{
sqrt = (float)System.Math.Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33);
half = 0.5f / sqrt;
result.X = (matrix.M21 + matrix.M12) * half;
result.Y = 0.5f * sqrt;
result.Z = (matrix.M32 + matrix.M23) * half;
result.W = (matrix.M31 - matrix.M13) * half;
}
else
{
sqrt = (float)System.Math.Sqrt(1.0f + matrix.M33 - matrix.M11 - matrix.M22);
half = 0.5f / sqrt;
result.X = (matrix.M31 + matrix.M13) * half;
result.Y = (matrix.M32 + matrix.M23) * half;
result.Z = 0.5f * sqrt;
result.W = (matrix.M12 - matrix.M21) * half;
}
return result;
}
/// <summary>
/// Creates a Quaternion from the given yaw, pitch, and roll, in radians.
/// </summary>
/// <param name="yaw">The yaw angle, in radians, around the Z-axis.</param>
/// <param name="pitch">The pitch angle, in radians, around the X-axis.</param>
/// <param name="roll">The roll angle, in radians, around the Y-axis.</param>
/// <returns>The newly created quaternion.</returns>
public static LQuaternion RotationYawPitchRoll(float yaw, float pitch, float roll)
{
LQuaternion result = Zero;
float halfYaw = yaw * 0.5f;
float sinYaw = (float)(System.Math.Sin((double)(halfYaw)));
float cosYaw = (float)(System.Math.Cos((double)(halfYaw)));
float halfPitch = pitch * 0.5f;
float sinPitch = (float)(System.Math.Sin((double)(halfPitch)));
float cosPitch = (float)(System.Math.Cos((double)(halfPitch)));
float halfRoll = roll * 0.5f;
float sinRoll = (float)(System.Math.Sin((double)(halfRoll)));
float cosRoll = (float)(System.Math.Cos((double)(halfRoll)));
result.X = (cosRoll * sinPitch * cosYaw) + (sinRoll * cosPitch * sinYaw);
result.Y = (sinRoll * cosPitch * cosYaw) - (cosRoll * sinPitch * sinYaw);
result.Z = (cosRoll * cosPitch * sinYaw) - (sinRoll * sinPitch * cosYaw);
result.W = (cosRoll * cosPitch * cosYaw) + (sinRoll * sinPitch * sinYaw);
return result;
}
/// <summary>
/// Creates a Quaternion from the given relative x, y, z axis
/// </summary>
/// The Vectors need to be perpendicular to each other
/// <param name="rightVector">Relative X axis</param>
/// <param name="forwardVector">Relative Y axis</param>
/// <param name="upVector">Relative Z axis</param>
/// <returns>The newly created quaternion.</returns>
public static LQuaternion DirectionVectors(Vector3 rightVector, Vector3 forwardVector, Vector3 upVector)
{
rightVector.Normalize();
forwardVector.Normalize();
upVector.Normalize();
Matrix rotationMatrix = new Matrix();
rotationMatrix[0, 0] = rightVector.X;
rotationMatrix[0, 1] = rightVector.Y;
rotationMatrix[0, 2] = rightVector.Z;
rotationMatrix[1, 0] = forwardVector.X;
rotationMatrix[1, 1] = forwardVector.Y;
rotationMatrix[1, 2] = forwardVector.Z;
rotationMatrix[2, 0] = upVector.X;
rotationMatrix[2, 1] = upVector.Y;
rotationMatrix[2, 2] = upVector.Z;
return RotationMatrix(rotationMatrix);
}
/// <summary>
/// Get direction vectors from the given quaternion
/// </summary>
/// <param name="quaternion">The quaternion</param>
/// <param name="rightVector">RightVector = relative x axis</param>
/// <param name="forwardVector">ForwardVector = relative y axis</param>
/// <param name="upVector">UpVector = relative z axis</param>
public static void GetDirectionVectors(LQuaternion quaternion, out Vector3 rightVector, out Vector3 forwardVector, out Vector3 upVector)
{
quaternion.Normalize();
rightVector = quaternion * Vector3.WorldEast;
forwardVector = quaternion * Vector3.WorldNorth;
upVector = quaternion * Vector3.WorldUp;
}
/// <summary>
/// Reverses the direction of a given quaternion.
/// </summary>
/// <param name="quaternion">The quaternion to negate.</param>
/// <returns>A quaternion facing in the opposite direction.</returns>
public static LQuaternion operator -(LQuaternion quaternion)
{
LQuaternion result = Zero;
result.X = -quaternion.X;
result.Y = -quaternion.Y;
result.Z = -quaternion.Z;
result.W = -quaternion.W;
return result;
}
/// <summary>
/// Adds two quaternions.
/// </summary>
/// <param name="left">The first quaternion to add.</param>
/// <param name="right">The second quaternion to add.</param>
/// <returns>The sum of the two quaternions.</returns>
public static LQuaternion operator +(LQuaternion left, LQuaternion right)
{
LQuaternion result = Zero;
result.X = left.X + right.X;
result.Y = left.Y + right.Y;
result.Z = left.Z + right.Z;
result.W = left.W + right.W;
return result;
}
/// <summary>
/// Subtracts two quaternions.
/// </summary>
/// <param name="left">The first quaternion to subtract.</param>
/// <param name="right">The second quaternion to subtract.</param>
/// <returns>The difference of the two quaternions.</returns>
public static LQuaternion operator -(LQuaternion left, LQuaternion right)
{
LQuaternion result = Zero;
result.X = left.X - right.X;
result.Y = left.Y - right.Y;
result.Z = left.Z - right.Z;
result.W = left.W - right.W;
return result;
}
/// <summary>
/// Multiplies a quaternion by another.
/// </summary>
/// <param name="left">The first quaternion to multiply.</param>
/// <param name="right">The second quaternion to multiply.</param>
/// <returns>The multiplied quaternion.</returns>
public static LQuaternion operator *(LQuaternion left, LQuaternion right)
{
LQuaternion quaternion = Zero;
float lx = left.X;
float ly = left.Y;
float lz = left.Z;
float lw = left.W;
float rx = right.X;
float ry = right.Y;
float rz = right.Z;
float rw = right.W;
quaternion.X = (lx * rw + rx * lw) + (ly * rz) - (lz * ry);
quaternion.Y = (ly * rw + ry * lw) + (lz * rx) - (lx * rz);
quaternion.Z = (lz * rw + rz * lw) + (lx * ry) - (ly * rx);
quaternion.W = (lw * rw) - (lx * rx + ly * ry + lz * rz);
return quaternion;
}
/// <summary>
/// Scales a quaternion by the given value.
/// </summary>
/// <param name="quaternion">The quaternion to scale.</param>
/// <param name="scale">The amount by which to scale the quaternion.</param>
/// <returns>The scaled quaternion.</returns>
public static LQuaternion operator *(LQuaternion quaternion, float scale)
{
LQuaternion result = Zero;
result.X = quaternion.X * scale;
result.Y = quaternion.Y * scale;
result.Z = quaternion.Z * scale;
result.W = quaternion.W * scale;
return result;
}
/// <summary>
/// Scales a quaternion by the given value.
/// </summary>
/// <param name="quaternion">The quaternion to scale.</param>
/// <param name="scale">The amount by which to scale the quaternion.</param>
/// <returns>The scaled quaternion.</returns>
public static LQuaternion operator *(float scale, LQuaternion quaternion)
{
LQuaternion result = Zero;
result.X = quaternion.X * scale;
result.Y = quaternion.Y * scale;
result.Z = quaternion.Z * scale;
result.W = quaternion.W * scale;
return result;
}
/// <summary>
/// Divides a Quaternion by another Quaternion.
/// </summary>
/// <param name="left">The source Quaternion.</param>
/// <param name="right">The divisor.</param>
/// <returns>The result of the division.</returns>
public static LQuaternion operator /(LQuaternion left, LQuaternion right)
{
LQuaternion quaternion = Zero;
float lx = left.X;
float ly = left.Y;
float lz = left.Z;
float lw = left.W;
// Inverse part.
float ls = right.X * right.X + right.Y * right.Y +
right.Z * right.Z + right.W * right.W;
float invNorm = 1.0f / ls;
float rx = -right.X * invNorm;
float ry = -right.Y * invNorm;
float rz = -right.Z * invNorm;
float rw = right.W * invNorm;
// Multiply part.
quaternion.X = (lx * rw + rx * lw) + (ly * rz) - (lz * ry);
quaternion.Y = (ly * rw + ry * lw) + (lz * rx) - (lx * rz);
quaternion.Z = (lz * rw + rz * lw) + (lx * ry) - (ly * rx);
quaternion.W = (lw * rw) - (lx * rx + ly * ry + lz * rz);
return quaternion;
}
/// <summary>
/// Tests for equality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><see langword="true" /> if <paramref name="left"/> has the same value as <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
public static bool operator ==(LQuaternion left, LQuaternion right) => Equals(left, right);
/// <summary>
/// Tests for inequality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><see langword="true" /> if <paramref name="left"/> has a different value than <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
public static bool operator !=(LQuaternion left, LQuaternion right) => !Equals(left, right);
#region RotateTransformOperators
/// <summary>
/// Rotates the point with rotation.
/// </summary>
/// <param name="rotation">The quaternion to rotate the vector.</param>
/// <param name="point">The vector to be rotated.</param>
/// <returns>The vector after rotation.</returns>
public static Vector3 operator *(LQuaternion rotation, Vector3 point)
{
float q0 = rotation.W;
float q0Square = rotation.W * rotation.W;
Vector3 q = new Vector3(rotation.X, rotation.Y, rotation.Z);
return ((q0Square - q.LengthSquared()) * point) + (2 * Vector3.Dot(q, point) * q) + (2 * q0 * Vector3.Cross(q, point));
}
/// <summary>
/// Rotates the point with rotation.
/// </summary>
/// <param name="rotation">The quaternion to rotate the vector.</param>
/// <param name="point">The vector to be rotated.</param>
/// <returns>The vector after rotation.</returns>
public static Vector3 RotateTransform(LQuaternion rotation, Vector3 point) => rotation * point;
/// <summary>
/// Rotates the point with rotation.
/// </summary>
/// <param name="rotation">The quaternion to rotate the vector.</param>
/// <param name="point">The vector to be rotated.</param>
/// <param name="center">The vector representing the origin of the new coordinate system.</param>
/// <returns>The vector after rotation in the original coordinate system.</returns>
public static Vector3 RotateTransform(LQuaternion rotation, Vector3 point, Vector3 center)
{
Vector3 PointNewCenter = Vector3.Subtract(point, center);
Vector3 TransformedPoint = RotateTransform(rotation, PointNewCenter);
return Vector3.Add(TransformedPoint, center);
}
/// <summary>
/// Rotates the point with rotation.
/// </summary>
/// <param name="point">The vector to be rotated.</param>
/// <returns>The vector after rotation.</returns>
public Vector3 RotateTransform(Vector3 point) => RotateTransform(this, point);
/// <summary>
/// Rotates the point with rotation.
/// </summary>
/// <param name="point">The vector to be rotated.</param>
/// <param name="center">The vector representing the origin of the new coordinate system.</param>
/// <returns>The vector after rotation in the original coordinate system.</returns>
public Vector3 RotateTransform(Vector3 point, Vector3 center) => RotateTransform(this, point, center);
#endregion RotateTransformOperators
/// <summary>
/// Converts the value of the object to its equivalent string representation.
/// </summary>
/// <returns>The string representation of the value of this instance.</returns>
public override string ToString()
{
return String.Format(CultureInfo.CurrentCulture, "X:{0} Y:{1} Z:{2} W:{3}", X.ToString(), Y.ToString(), Z.ToString(), W.ToString());
}
/// <summary>
/// Converts the value of the object to its equivalent string representation.
/// </summary>
/// <param name="format">The format.</param>
/// <returns>The string representation of the value of this instance.</returns>
public string ToString(string format)
{
return String.Format(CultureInfo.InvariantCulture, "X:{0} Y:{1} Z:{2} W:{3}", X.ToString(format), Y.ToString(format), Z.ToString(format), W.ToString(format));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode() => X.GetHashCode() + Y.GetHashCode() + Z.GetHashCode() + W.GetHashCode();
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a specified object.
/// </summary>
/// <param name="obj">Object to make the comparison with.</param>
/// <returns><see langword="true" /> if the current instance is equal to the specified object; <see langword="false" /> otherwise.</returns>
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
return Equals((LQuaternion)obj);
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to the specified object.
/// </summary>
/// <param name="other">Object to make the comparison with.</param>
/// <returns><see langword="true" /> if the current instance is equal to the specified object; <see langword="false" /> otherwise.</returns>
public bool Equals(LQuaternion other) => (X == other.X && Y == other.Y && Z == other.Z && W == other.W);
public static implicit operator Quaternion(LQuaternion q) => new(q.X, q.Y, q.Z, q.W);
public static implicit operator LQuaternion(Quaternion q) => new(q.X, q.Y, q.Z, q.W);
}
}

View File

@ -1,512 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using GTA.Math;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
internal struct LVector2 : IEquatable<LVector2>
{
/// <summary>
/// Gets or sets the X component of the vector.
/// </summary>
/// <value>The X component of the vector.</value>
public float X;
/// <summary>
/// Gets or sets the Y component of the vector.
/// </summary>
/// <value>The Y component of the vector.</value>
public float Y;
/// <summary>
/// Initializes a new instance of the <see cref="LVector2"/> class.
/// </summary>
/// <param name="x">Initial value for the X component of the vector.</param>
/// <param name="y">Initial value for the Y component of the vector.</param>
public LVector2(float x, float y)
{
X = x;
Y = y;
}
/// <summary>
/// Returns this vector with a magnitude of 1.
/// </summary>
public LVector2 Normalized => Normalize(new LVector2(X, Y));
/// <summary>
/// Returns a null vector. (0,0)
/// </summary>
public static LVector2 Zero => new LVector2(0.0f, 0.0f);
/// <summary>
/// The X unit <see cref="LVector2"/> (1, 0).
/// </summary>
public static LVector2 UnitX => new LVector2(1.0f, 0.0f);
/// <summary>
/// The Y unit <see cref="LVector2"/> (0, 1).
/// </summary>
public static LVector2 UnitY => new LVector2(0.0f, 1.0f);
/// <summary>
/// Returns the up vector. (0,1)
/// </summary>
public static LVector2 Up => new LVector2(0.0f, 1.0f);
/// <summary>
/// Returns the down vector. (0,-1)
/// </summary>
public static LVector2 Down => new LVector2(0.0f, -1.0f);
/// <summary>
/// Returns the right vector. (1,0)
/// </summary>
public static LVector2 Right => new LVector2(1.0f, 0.0f);
/// <summary>
/// Returns the left vector. (-1,0)
/// </summary>
public static LVector2 Left => new LVector2(-1.0f, 0.0f);
/// <summary>
/// Gets or sets the component at the specified index.
/// </summary>
/// <value>The value of the X or Y component, depending on the index.</value>
/// <param name="index">The index of the component to access. Use 0 for the X component and 1 for the Y component.</param>
/// <returns>The value of the component at the specified index.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when the <paramref name="index"/> is out of the range [0, 1].</exception>
public float this[int index]
{
get
{
switch (index)
{
case 0:
return X;
case 1:
return Y;
}
throw new ArgumentOutOfRangeException("index", "Indices for Vector2 run from 0 to 1, inclusive.");
}
set
{
switch (index)
{
case 0:
X = value;
break;
case 1:
Y = value;
break;
default:
throw new ArgumentOutOfRangeException("index", "Indices for Vector2 run from 0 to 1, inclusive.");
}
}
}
/// <summary>
/// Calculates the length of the vector.
/// </summary>
/// <returns>The length of the vector.</returns>
public float Length()
{
return (float)System.Math.Sqrt((X * X) + (Y * Y));
}
/// <summary>
/// Calculates the squared length of the vector.
/// </summary>
/// <returns>The squared length of the vector.</returns>
public float LengthSquared()
{
return (X * X) + (Y * Y);
}
/// <summary>
/// Converts the vector into a unit vector.
/// </summary>
public void Normalize()
{
float length = Length();
if (length == 0)
{
return;
}
float num = 1 / length;
X *= num;
Y *= num;
}
/// <summary>
/// Calculates the distance between two vectors.
/// </summary>
/// <param name="position">The second vector to calculate the distance to.</param>
/// <returns>The distance to the other vector.</returns>
public float DistanceTo(LVector2 position)
{
return (position - this).Length();
}
/// <summary>
/// Calculates the squared distance between two vectors.
/// </summary>
/// <param name="position">The second vector to calculate the squared distance to.</param>
/// <returns>The squared distance to the other vector.</returns>
public float DistanceToSquared(LVector2 position)
{
return DistanceSquared(position, this);
}
/// <summary>
/// Calculates the distance between two vectors.
/// </summary>
/// <param name="position1">The first vector to calculate the distance to the second vector.</param>
/// <param name="position2">The second vector to calculate the distance to the first vector.</param>
/// <returns>The distance between the two vectors.</returns>
public static float Distance(LVector2 position1, LVector2 position2)
{
return (position1 - position2).Length();
}
/// <summary>
/// Calculates the squared distance between two vectors.
/// </summary>
/// <param name="position1">The first vector to calculate the squared distance to the second vector.</param>
/// <param name="position2">The second vector to calculate the squared distance to the first vector.</param>
/// <returns>The squared distance between the two vectors.</returns>
public static float DistanceSquared(LVector2 position1, LVector2 position2)
{
return (position1 - position2).LengthSquared();
}
/// <summary>
/// Returns the angle in degrees between from and to.
/// The angle returned is always the acute angle between the two vectors.
/// </summary>
public static float Angle(LVector2 from, LVector2 to)
{
return System.Math.Abs(SignedAngle(from, to));
}
/// <summary>
/// Returns the signed angle in degrees between from and to.
/// </summary>
public static float SignedAngle(LVector2 from, LVector2 to)
{
return (float)((System.Math.Atan2(to.Y, to.X) - System.Math.Atan2(from.Y, from.X)) * (180.0 / System.Math.PI));
}
/// <summary>
/// Converts a vector to a heading.
/// </summary>
public float ToHeading()
{
return (float)((System.Math.Atan2(X, -Y) + System.Math.PI) * (180.0 / System.Math.PI));
}
/// <summary>
/// Returns a new normalized vector with random X and Y components.
/// </summary>
public static LVector2 RandomXY()
{
LVector2 v;
double radian = CoreUtils.SafeRandom.NextDouble() * 2 * System.Math.PI;
v.X = (float)(System.Math.Cos(radian));
v.Y = (float)(System.Math.Sin(radian));
v.Normalize();
return v;
}
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="left">The first vector to add.</param>
/// <param name="right">The second vector to add.</param>
/// <returns>The sum of the two vectors.</returns>
public static LVector2 Add(LVector2 left, LVector2 right) => new LVector2(left.X + right.X, left.Y + right.Y);
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="left">The first vector to subtract.</param>
/// <param name="right">The second vector to subtract.</param>
/// <returns>The difference of the two vectors.</returns>
public static LVector2 Subtract(LVector2 left, LVector2 right) => new LVector2(left.X - right.X, left.Y - right.Y);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="value">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector2 Multiply(LVector2 value, float scale) => new LVector2(value.X * scale, value.Y * scale);
/// <summary>
/// Multiplies a vector with another by performing component-wise multiplication.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <returns>The multiplied vector.</returns>
public static LVector2 Multiply(LVector2 left, LVector2 right) => new LVector2(left.X * right.X, left.Y * right.Y);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="value">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector2 Divide(LVector2 value, float scale) => new LVector2(value.X / scale, value.Y / scale);
/// <summary>
/// Reverses the direction of a given vector.
/// </summary>
/// <param name="value">The vector to negate.</param>
/// <returns>A vector facing in the opposite direction.</returns>
public static LVector2 Negate(LVector2 value) => new LVector2(-value.X, -value.Y);
/// <summary>
/// Restricts a value to be within a specified range.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static LVector2 Clamp(LVector2 value, LVector2 min, LVector2 max)
{
float x = value.X;
x = (x > max.X) ? max.X : x;
x = (x < min.X) ? min.X : x;
float y = value.Y;
y = (y > max.Y) ? max.Y : y;
y = (y < min.Y) ? min.Y : y;
return new LVector2(x, y);
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
/// <returns>The linear interpolation of the two vectors.</returns>
/// <remarks>
/// This method performs the linear interpolation based on the following formula.
/// <code>start + (end - start) * amount</code>
/// Passing <paramref name="amount"/> a value of 0 will cause <paramref name="start"/> to be returned; a value of 1 will cause <paramref name="end"/> to be returned.
/// </remarks>
public static LVector2 Lerp(LVector2 start, LVector2 end, float amount)
{
LVector2 vector;
vector.X = start.X + ((end.X - start.X) * amount);
vector.Y = start.Y + ((end.Y - start.Y) * amount);
return vector;
}
/// <summary>
/// Converts the vector into a unit vector.
/// </summary>
/// <param name="vector">The vector to normalize.</param>
/// <returns>The normalized vector.</returns>
public static LVector2 Normalize(LVector2 vector)
{
vector.Normalize();
return vector;
}
/// <summary>
/// Calculates the dot product of two vectors.
/// </summary>
/// <param name="left">First source vector.</param>
/// <param name="right">Second source vector.</param>
/// <returns>The dot product of the two vectors.</returns>
public static float Dot(LVector2 left, LVector2 right) => (left.X * right.X + left.Y * right.Y);
/// <summary>
/// Returns the reflection of a vector off a surface that has the specified normal.
/// </summary>
/// <param name="vector">The source vector.</param>
/// <param name="normal">Normal of the surface.</param>
/// <returns>The reflected vector.</returns>
/// <remarks>Reflect only gives the direction of a reflection off a surface, it does not determine
/// whether the original vector was close enough to the surface to hit it.</remarks>
public static LVector2 Reflect(LVector2 vector, LVector2 normal)
{
LVector2 result;
float dot = ((vector.X * normal.X) + (vector.Y * normal.Y));
result.X = vector.X - ((2.0f * dot) * normal.X);
result.Y = vector.Y - ((2.0f * dot) * normal.Y);
return result;
}
/// <summary>
/// Returns a vector containing the smallest components of the specified vectors.
/// </summary>
/// <param name="left">The first source vector.</param>
/// <param name="right">The second source vector.</param>
/// <returns>A vector containing the smallest components of the source vectors.</returns>
public static LVector2 Minimize(LVector2 left, LVector2 right)
{
LVector2 vector;
vector.X = (left.X < right.X) ? left.X : right.X;
vector.Y = (left.Y < right.Y) ? left.Y : right.Y;
return vector;
}
/// <summary>
/// Returns a vector containing the largest components of the specified vectors.
/// </summary>
/// <param name="left">The first source vector.</param>
/// <param name="right">The second source vector.</param>
/// <returns>A vector containing the largest components of the source vectors.</returns>
public static LVector2 Maximize(LVector2 left, LVector2 right)
{
LVector2 vector;
vector.X = (left.X > right.X) ? left.X : right.X;
vector.Y = (left.Y > right.Y) ? left.Y : right.Y;
return vector;
}
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="left">The first vector to add.</param>
/// <param name="right">The second vector to add.</param>
/// <returns>The sum of the two vectors.</returns>
public static LVector2 operator +(LVector2 left, LVector2 right) => new LVector2(left.X + right.X, left.Y + right.Y);
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="left">The first vector to subtract.</param>
/// <param name="right">The second vector to subtract.</param>
/// <returns>The difference of the two vectors.</returns>
public static LVector2 operator -(LVector2 left, LVector2 right) => new LVector2(left.X - right.X, left.Y - right.Y);
/// <summary>
/// Reverses the direction of a given vector.
/// </summary>
/// <param name="value">The vector to negate.</param>
/// <returns>A vector facing in the opposite direction.</returns>
public static LVector2 operator -(LVector2 value) => new LVector2(-value.X, -value.Y);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="vector">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector2 operator *(LVector2 vector, float scale) => new LVector2(vector.X * scale, vector.Y * scale);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="vector">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector2 operator *(float scale, LVector2 vector) => new LVector2(vector.X * scale, vector.Y * scale);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="vector">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector2 operator /(LVector2 vector, float scale) => new LVector2(vector.X / scale, vector.Y / scale);
/// <summary>
/// Tests for equality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><see langword="true" /> if <paramref name="left"/> has the same value as <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
public static bool operator ==(LVector2 left, LVector2 right) => Equals(left, right);
/// <summary>
/// Tests for inequality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><see langword="true" /> if <paramref name="left"/> has a different value than <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
public static bool operator !=(LVector2 left, LVector2 right) => !Equals(left, right);
/// <summary>
/// Converts a Vector2 to a Vector3 implicitly.
/// </summary>
public static implicit operator LVector3(LVector2 vector) => new LVector3(vector.X, vector.Y, 0);
/// <summary>
/// Converts the value of the object to its equivalent string representation.
/// </summary>
/// <returns>The string representation of the value of this instance.</returns>
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "X:{0} Y:{1}", X, Y);
}
/// <summary>
/// Converts the value of the object to its equivalent string representation.
/// </summary>
/// <param name="format">The format.</param>
/// <returns>The string representation of the value of this instance.</returns>
public string ToString(string format)
{
if (format == null)
{
return ToString();
}
return string.Format(CultureInfo.CurrentCulture, "X:{0} Y:{1}", X.ToString(format, CultureInfo.CurrentCulture), Y.ToString(format, CultureInfo.CurrentCulture));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
unchecked
{
return (X.GetHashCode() * 397) ^ Y.GetHashCode();
}
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a specified object.
/// </summary>
/// <param name="obj">Object to make the comparison with.</param>
/// <returns><see langword="true" /> if the current instance is equal to the specified object; otherwise, <see langword="false" />.</returns>
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
return Equals((LVector2)obj);
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to the specified object.
/// </summary>
/// <param name="other">Object to make the comparison with.</param>
/// <returns><see langword="true" /> if the current instance is equal to the specified object; <see langword="false" /> otherwise.</returns>
public bool Equals(LVector2 other) => (X == other.X && Y == other.Y);
public static implicit operator LVector2(Vector2 v) => new(v.X, v.Y);
public static implicit operator Vector2(LVector2 v) => new(v.X, v.Y);
}
}

View File

@ -1,731 +0,0 @@
//
// Copyright (C) 2007-2010 SlimDX Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
// OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using GTA.Math;
using System.Globalization;
using System.Runtime.InteropServices;
namespace RageCoop.Core
{
[Serializable]
internal struct LVector3 : IEquatable<LVector3>
{
/// <summary>
/// Gets or sets the X component of the vector.
/// </summary>
/// <value>The X component of the vector.</value>
public float X;
/// <summary>
/// Gets or sets the Y component of the vector.
/// </summary>
/// <value>The Y component of the vector.</value>
public float Y;
/// <summary>
/// Gets or sets the Z component of the vector.
/// </summary>
/// <value>The Z component of the vector.</value>
public float Z;
/// <summary>
/// Initializes a new instance of the <see cref="LVector3"/> class.
/// </summary>
/// <param name="x">Initial value for the X component of the vector.</param>
/// <param name="y">Initial value for the Y component of the vector.</param>
/// <param name="z">Initial value for the Z component of the vector.</param>
public LVector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
internal LVector3(float[] values) : this(values[0], values[1], values[2])
{
}
/// <summary>
/// Returns this vector with a magnitude of 1.
/// </summary>
public LVector3 Normalized => Normalize(new LVector3(X, Y, Z));
/// <summary>
/// Returns a null vector. (0,0,0)
/// </summary>
public static LVector3 Zero => new(0.0f, 0.0f, 0.0f);
/// <summary>
/// The X unit <see cref="LVector3"/> (1, 0, 0).
/// </summary>
public static LVector3 UnitX => new(1.0f, 0.0f, 0.0f);
/// <summary>
/// The Y unit <see cref="LVector3"/> (0, 1, 0).
/// </summary>
public static LVector3 UnitY => new(0.0f, 1.0f, 0.0f);
/// <summary>
/// The Z unit <see cref="LVector3"/> (0, 0, 1).
/// </summary>
public static LVector3 UnitZ => new(0.0f, 0.0f, 1.0f);
/// <summary>
/// Returns the world Up vector. (0,0,1)
/// </summary>
public static LVector3 WorldUp => new(0.0f, 0.0f, 1.0f);
/// <summary>
/// Returns the world Down vector. (0,0,-1)
/// </summary>
public static LVector3 WorldDown => new(0.0f, 0.0f, -1.0f);
/// <summary>
/// Returns the world North vector. (0,1,0)
/// </summary>
public static LVector3 WorldNorth => new(0.0f, 1.0f, 0.0f);
/// <summary>
/// Returns the world South vector. (0,-1,0)
/// </summary>
public static LVector3 WorldSouth => new(0.0f, -1.0f, 0.0f);
/// <summary>
/// Returns the world East vector. (1,0,0)
/// </summary>
public static LVector3 WorldEast => new(1.0f, 0.0f, 0.0f);
/// <summary>
/// Returns the world West vector. (-1,0,0)
/// </summary>
public static LVector3 WorldWest => new(-1.0f, 0.0f, 0.0f);
/// <summary>
/// Returns the relative Right vector. (1,0,0)
/// </summary>
public static LVector3 RelativeRight => new(1.0f, 0.0f, 0.0f);
/// <summary>
/// Returns the relative Left vector. (-1,0,0)
/// </summary>
public static LVector3 RelativeLeft => new(-1.0f, 0.0f, 0.0f);
/// <summary>
/// Returns the relative Front vector. (0,1,0)
/// </summary>
public static LVector3 RelativeFront => new(0.0f, 1.0f, 0.0f);
/// <summary>
/// Returns the relative Back vector. (0,-1,0)
/// </summary>
public static LVector3 RelativeBack => new(0.0f, -1.0f, 0.0f);
/// <summary>
/// Returns the relative Top vector. (0,0,1)
/// </summary>
public static LVector3 RelativeTop => new(0.0f, 0.0f, 1.0f);
/// <summary>
/// Returns the relative Bottom vector as used. (0,0,-1)
/// </summary>
public static LVector3 RelativeBottom => new(0.0f, 0.0f, -1.0f);
/// <summary>
/// Gets or sets the component at the specified index.
/// </summary>
/// <value>The value of the X, Y or Z component, depending on the index.</value>
/// <param name="index">The index of the component to access. Use 0 for the X component, 1 for the Y component and 2 for the Z component.</param>
/// <returns>The value of the component at the specified index.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when the <paramref name="index"/> is out of the range [0, 2].</exception>
public float this[int index]
{
get
{
switch (index)
{
case 0:
return X;
case 1:
return Y;
case 2:
return Z;
}
throw new ArgumentOutOfRangeException("index", "Indices for Vector3 run from 0 to 2, inclusive.");
}
set
{
switch (index)
{
case 0:
X = value;
break;
case 1:
Y = value;
break;
case 2:
Z = value;
break;
default:
throw new ArgumentOutOfRangeException("index",
"Indices for Vector3 run from 0 to 2, inclusive.");
}
}
}
/// <summary>
/// Calculates the length of the vector.
/// </summary>
/// <returns>The length of the vector.</returns>
public float Length() => (float)(System.Math.Sqrt((X * X) + (Y * Y) + (Z * Z)));
/// <summary>
/// Calculates the squared length of the vector.
/// </summary>
/// <returns>The squared length of the vector.</returns>
public float LengthSquared() => (X * X) + (Y * Y) + (Z * Z);
/// <summary>
/// Converts the vector into a unit vector.
/// </summary>
public void Normalize()
{
float length = Length();
if (length == 0)
{
return;
}
float num = 1 / length;
X *= num;
Y *= num;
Z *= num;
}
/// <summary>
/// Calculates the distance between two vectors.
/// </summary>
/// <param name="position">The second vector to calculate the distance to.</param>
/// <returns>The distance to the other vector.</returns>
public float DistanceTo(LVector3 position) => (position - this).Length();
/// <summary>
/// Calculates the squared distance between two vectors.
/// </summary>
/// <param name="position">The second vector to calculate the distance to.</param>
/// <returns>The distance to the other vector.</returns>
public float DistanceToSquared(LVector3 position) => DistanceSquared(position, this);
/// <summary>
/// Calculates the distance between two vectors, ignoring the Z-component.
/// </summary>
/// <param name="position">The second vector to calculate the distance to.</param>
/// <returns>The distance to the other vector.</returns>
public float DistanceTo2D(LVector3 position)
{
var lhs = new LVector3(X, Y, 0.0f);
var rhs = new LVector3(position.X, position.Y, 0.0f);
return Distance(lhs, rhs);
}
/// <summary>
/// Calculates the squared distance between two vectors, ignoring the Z-component.
/// </summary>
/// <param name="position">The second vector to calculate the squared distance to.</param>
/// <returns>The distance to the other vector.</returns>
public float DistanceToSquared2D(LVector3 position)
{
var lhs = new LVector3(X, Y, 0.0f);
var rhs = new LVector3(position.X, position.Y, 0.0f);
return DistanceSquared(lhs, rhs);
}
/// <summary>
/// Calculates the distance between two vectors.
/// </summary>
/// <param name="position1">The first vector to calculate the distance to the second vector.</param>
/// <param name="position2">The second vector to calculate the distance to the first vector.</param>
/// <returns>The distance between the two vectors.</returns>
public static float Distance(LVector3 position1, LVector3 position2) => (position1 - position2).Length();
/// <summary>
/// Calculates the squared distance between two vectors.
/// </summary>
/// <param name="position1">The first vector to calculate the squared distance to the second vector.</param>
/// <param name="position2">The second vector to calculate the squared distance to the first vector.</param>
/// <returns>The squared distance between the two vectors.</returns>
public static float DistanceSquared(LVector3 position1, LVector3 position2) =>
(position1 - position2).LengthSquared();
/// <summary>
/// Calculates the distance between two vectors, ignoring the Z-component.
/// </summary>
/// <param name="position1">The first vector to calculate the distance to the second vector.</param>
/// <param name="position2">The second vector to calculate the distance to the first vector.</param>
/// <returns>The distance between the two vectors.</returns>
public static float Distance2D(LVector3 position1, LVector3 position2)
{
var pos1 = new LVector3(position1.X, position1.Y, 0);
var pos2 = new LVector3(position2.X, position2.Y, 0);
return (pos1 - pos2).Length();
}
/// <summary>
/// Calculates the squared distance between two vectors, ignoring the Z-component.
/// </summary>
/// <param name="position1">The first vector to calculate the squared distance to the second vector.</param>
/// <param name="position2">The second vector to calculate the squared distance to the first vector.</param>
/// <returns>The squared distance between the two vectors.</returns>
public static float DistanceSquared2D(LVector3 position1, LVector3 position2)
{
var pos1 = new LVector3(position1.X, position1.Y, 0);
var pos2 = new LVector3(position2.X, position2.Y, 0);
return (pos1 - pos2).LengthSquared();
}
/// <summary>
/// Returns the angle in degrees between from and to.
/// The angle returned is always the acute angle between the two vectors.
/// </summary>
public static float Angle(LVector3 from, LVector3 to)
{
double dot = Dot(from.Normalized, to.Normalized);
return (float)(System.Math.Acos((dot)) * (180.0 / System.Math.PI));
}
/// <summary>
/// Returns the signed angle in degrees between from and to.
/// </summary>
public static float SignedAngle(LVector3 from, LVector3 to, LVector3 planeNormal)
{
LVector3 perpVector = Cross(planeNormal, from);
double angle = Angle(from, to);
double dot = Dot(perpVector, to);
if (dot < 0)
{
angle *= -1;
}
return (float)angle;
}
/// <summary>
/// Converts a vector to a heading.
/// </summary>
public float ToHeading() => (float)((System.Math.Atan2(X, -Y) + System.Math.PI) * (180.0 / System.Math.PI));
/// <summary>
/// Creates a random vector inside the circle around this position.
/// </summary>
public LVector3 Around(float distance) => this + (RandomXY() * distance);
/// <summary>
/// Rounds each float inside the vector to a select amount of decimal places (2 by default).
/// </summary>
/// <param name="decimalPlaces">Number of decimal places to round to</param>
/// <returns>The vector containing rounded values</returns>
public LVector3 Round(int decimalPlaces = 2)
{
return new LVector3((float)System.Math.Round(X, decimalPlaces), (float)System.Math.Round(Y, decimalPlaces),
(float)System.Math.Round(Z, decimalPlaces));
}
/// <summary>
/// Returns a new normalized vector with random X and Y components.
/// </summary>
public static LVector3 RandomXY()
{
LVector3 v = Zero;
double radian = CoreUtils.SafeRandom.NextDouble() * 2 * System.Math.PI;
v.X = (float)(System.Math.Cos(radian));
v.Y = (float)(System.Math.Sin(radian));
v.Normalize();
return v;
}
/// <summary>
/// Returns a new normalized vector with random X, Y and Z components.
/// </summary>
public static LVector3 RandomXYZ()
{
LVector3 v = Zero;
double radian = CoreUtils.SafeRandom.NextDouble() * 2.0 * System.Math.PI;
double cosTheta = (CoreUtils.SafeRandom.NextDouble() * 2.0) - 1.0;
double theta = System.Math.Acos(cosTheta);
v.X = (float)(System.Math.Sin(theta) * System.Math.Cos(radian));
v.Y = (float)(System.Math.Sin(theta) * System.Math.Sin(radian));
v.Z = (float)(System.Math.Cos(theta));
v.Normalize();
return v;
}
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="left">The first vector to add.</param>
/// <param name="right">The second vector to add.</param>
/// <returns>The sum of the two vectors.</returns>
public static LVector3 Add(LVector3 left, LVector3 right) =>
new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="left">The first vector to subtract.</param>
/// <param name="right">The second vector to subtract.</param>
/// <returns>The difference of the two vectors.</returns>
public static LVector3 Subtract(LVector3 left, LVector3 right) =>
new(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="value">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector3 Multiply(LVector3 value, float scale) =>
new(value.X * scale, value.Y * scale, value.Z * scale);
/// <summary>
/// Multiply a vector with another by performing component-wise multiplication.
/// </summary>
/// <param name="left">The first vector to multiply.</param>
/// <param name="right">The second vector to multiply.</param>
/// <returns>The multiplied vector.</returns>
public static LVector3 Multiply(LVector3 left, LVector3 right) =>
new(left.X * right.X, left.Y * right.Y, left.Z * right.Z);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="value">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector3 Divide(LVector3 value, float scale) =>
new(value.X / scale, value.Y / scale, value.Z / scale);
/// <summary>
/// Reverses the direction of a given vector.
/// </summary>
/// <param name="value">The vector to negate.</param>
/// <returns>A vector facing in the opposite direction.</returns>
public static LVector3 Negate(LVector3 value) => new(-value.X, -value.Y, -value.Z);
/// <summary>
/// Restricts a value to be within a specified range.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static LVector3 Clamp(LVector3 value, LVector3 min, LVector3 max)
{
float x = value.X;
x = (x > max.X) ? max.X : x;
x = (x < min.X) ? min.X : x;
float y = value.Y;
y = (y > max.Y) ? max.Y : y;
y = (y < min.Y) ? min.Y : y;
float z = value.Z;
z = (z > max.Z) ? max.Z : z;
z = (z < min.Z) ? min.Z : z;
return new LVector3(x, y, z);
}
/// <summary>
/// Performs a linear interpolation between two vectors.
/// </summary>
/// <param name="start">Start vector.</param>
/// <param name="end">End vector.</param>
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end"/>.</param>
/// <returns>The linear interpolation of the two vectors.</returns>
/// <remarks>
/// This method performs the linear interpolation based on the following formula.
/// <code>start + (end - start) * amount</code>
/// Passing <paramref name="amount"/> a value of 0 will cause <paramref name="start"/> to be returned; a value of 1 will cause <paramref name="end"/> to be returned.
/// </remarks>
public static LVector3 Lerp(LVector3 start, LVector3 end, float amount)
{
LVector3 vector = Zero;
vector.X = start.X + ((end.X - start.X) * amount);
vector.Y = start.Y + ((end.Y - start.Y) * amount);
vector.Z = start.Z + ((end.Z - start.Z) * amount);
return vector;
}
/// <summary>
/// Converts the vector into a unit vector.
/// </summary>
/// <param name="vector">The vector to normalize.</param>
/// <returns>The normalized vector.</returns>
public static LVector3 Normalize(LVector3 vector)
{
vector.Normalize();
return vector;
}
/// <summary>
/// Calculates the dot product of two vectors.
/// </summary>
/// <param name="left">First source vector.</param>
/// <param name="right">Second source vector.</param>
/// <returns>The dot product of the two vectors.</returns>
public static float Dot(LVector3 left, LVector3 right) =>
(left.X * right.X + left.Y * right.Y + left.Z * right.Z);
/// <summary>
/// Calculates the cross product of two vectors.
/// </summary>
/// <param name="left">First source vector.</param>
/// <param name="right">Second source vector.</param>
/// <returns>The cross product of the two vectors.</returns>
public static LVector3 Cross(LVector3 left, LVector3 right)
{
LVector3 result = Zero;
result.X = left.Y * right.Z - left.Z * right.Y;
result.Y = left.Z * right.X - left.X * right.Z;
result.Z = left.X * right.Y - left.Y * right.X;
return result;
}
/// <summary>
/// Projects a vector onto another vector.
/// </summary>
/// <param name="vector">The vector to project.</param>
/// <param name="onNormal">Vector to project onto, does not assume it is normalized.</param>
/// <returns>The projected vector.</returns>
public static LVector3 Project(LVector3 vector, LVector3 onNormal) =>
onNormal * Dot(vector, onNormal) / Dot(onNormal, onNormal);
/// <summary>
/// Projects a vector onto a plane defined by a normal orthogonal to the plane.
/// </summary>
/// <param name="vector">The vector to project.</param>
/// <param name="planeNormal">Normal of the plane, does not assume it is normalized.</param>
/// <returns>The Projection of vector onto plane.</returns>
public static LVector3 ProjectOnPlane(LVector3 vector, LVector3 planeNormal) =>
(vector - Project(vector, planeNormal));
/// <summary>
/// Returns the reflection of a vector off a surface that has the specified normal.
/// </summary>
/// <param name="vector">The vector to project onto the plane.</param>
/// <param name="normal">Normal of the surface.</param>
/// <returns>The reflected vector.</returns>
/// <remarks>Reflect only gives the direction of a reflection off a surface, it does not determine
/// whether the original vector was close enough to the surface to hit it.</remarks>
public static LVector3 Reflect(LVector3 vector, LVector3 normal)
{
LVector3 result = Zero;
float dot = ((vector.X * normal.X) + (vector.Y * normal.Y)) + (vector.Z * normal.Z);
result.X = vector.X - ((2.0f * dot) * normal.X);
result.Y = vector.Y - ((2.0f * dot) * normal.Y);
result.Z = vector.Z - ((2.0f * dot) * normal.Z);
return result;
}
/// <summary>
/// Returns a vector containing the smallest components of the specified vectors.
/// </summary>
/// <param name="left">The first source vector.</param>
/// <param name="right">The second source vector.</param>
/// <returns>A vector containing the smallest components of the source vectors.</returns>
public static LVector3 Minimize(LVector3 left, LVector3 right)
{
LVector3 vector = Zero;
vector.X = (left.X < right.X) ? left.X : right.X;
vector.Y = (left.Y < right.Y) ? left.Y : right.Y;
vector.Z = (left.Z < right.Z) ? left.Z : right.Z;
return vector;
}
/// <summary>
/// Returns a vector containing the largest components of the specified vectors.
/// </summary>
/// <param name="left">The first source vector.</param>
/// <param name="right">The second source vector.</param>
/// <returns>A vector containing the largest components of the source vectors.</returns>
public static LVector3 Maximize(LVector3 left, LVector3 right)
{
LVector3 vector = Zero;
vector.X = (left.X > right.X) ? left.X : right.X;
vector.Y = (left.Y > right.Y) ? left.Y : right.Y;
vector.Z = (left.Z > right.Z) ? left.Z : right.Z;
return vector;
}
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="left">The first vector to add.</param>
/// <param name="right">The second vector to add.</param>
/// <returns>The sum of the two vectors.</returns>
public static LVector3 operator +(LVector3 left, LVector3 right) =>
new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
/// <summary>
/// Subtracts two vectors.
/// </summary>
/// <param name="left">The first vector to subtract.</param>
/// <param name="right">The second vector to subtract.</param>
/// <returns>The difference of the two vectors.</returns>
public static LVector3 operator -(LVector3 left, LVector3 right) =>
new(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
/// <summary>
/// Reverses the direction of a given vector.
/// </summary>
/// <param name="vector">The vector to negate.</param>
/// <returns>A vector facing in the opposite direction.</returns>
public static LVector3 operator -(LVector3 vector) => new(-vector.X, -vector.Y, -vector.Z);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="vector">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector3 operator *(LVector3 vector, float scale) =>
new(vector.X * scale, vector.Y * scale, vector.Z * scale);
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="vector">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector3 operator *(float scale, LVector3 vector) => vector * scale;
/// <summary>
/// Scales a vector by the given value.
/// </summary>
/// <param name="vector">The vector to scale.</param>
/// <param name="scale">The amount by which to scale the vector.</param>
/// <returns>The scaled vector.</returns>
public static LVector3 operator /(LVector3 vector, float scale)
{
float invScale = 1.0f / scale;
return new LVector3(vector.X * invScale, vector.Y * invScale, vector.Z * invScale);
}
/// <summary>
/// Tests for equality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><see langword="true" /> if <paramref name="left"/> has the same value as <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
public static bool operator ==(LVector3 left, LVector3 right) => Equals(left, right);
/// <summary>
/// Tests for inequality between two objects.
/// </summary>
/// <param name="left">The first value to compare.</param>
/// <param name="right">The second value to compare.</param>
/// <returns><see langword="true" /> if <paramref name="left"/> has a different value than <paramref name="right"/>; otherwise, <see langword="false" />.</returns>
public static bool operator !=(LVector3 left, LVector3 right) => !Equals(left, right);
/// <summary>
/// Converts a Vector3 to a Vector2 implicitly.
/// </summary>
public static implicit operator LVector2(LVector3 vector) => new(vector.X, vector.Y);
/// <summary>
/// Converts the matrix to an array of floats.
/// </summary>
public float[] ToArray() => new[] { X, Y, Z };
/// <summary>
/// Converts the value of the object to its equivalent string representation.
/// </summary>
/// <returns>The string representation of the value of this instance.</returns>
public override string ToString() => string.Format(CultureInfo.CurrentCulture, "X:{0} Y:{1} Z:{2}", X, Y, Z);
/// <summary>
/// Converts the value of the object to its equivalent string representation.
/// </summary>
/// <param name="format">The number format.</param>
/// <returns>The string representation of the value of this instance.</returns>
public string ToString(string format)
{
if (format == null)
{
return ToString();
}
return string.Format(CultureInfo.CurrentCulture, "X:{0} Y:{1} Z:{2}",
X.ToString(format, CultureInfo.CurrentCulture),
Y.ToString(format, CultureInfo.CurrentCulture), Z.ToString(format, CultureInfo.CurrentCulture));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
unchecked
{
var hashCode = X.GetHashCode();
hashCode = (hashCode * 397) ^ Y.GetHashCode();
hashCode = (hashCode * 397) ^ Z.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a specified object.
/// </summary>
/// <param name="obj">Object to make the comparison with.</param>
/// <returns><see langword="true" /> if the current instance is equal to the specified object; <see langword="false" /> otherwise.</returns>
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
return Equals((LVector3)obj);
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to the specified object.
/// </summary>
/// <param name="other">Object to make the comparison with.</param>
/// <returns><see langword="true" /> if the current instance is equal to the specified object; <see langword="false" /> otherwise.</returns>
public bool Equals(LVector3 other) => (X == other.X && Y == other.Y && Z == other.Z);
public static implicit operator LVector3(Vector3 v) => new(v.X, v.Y, v.Z);
public static implicit operator Vector3(LVector3 v) => new(v.X, v.Y, v.Z);
}
}

View File

@ -1,589 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Serialization;
using System.Xml;
using GTA;
using GTA.Math;
using Lidgren.Network;
using RageCoop.Core.Scripting;
[assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")]
[assembly: InternalsVisibleTo("RageCoop.Client.Scripting")]
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
[assembly: InternalsVisibleTo("DataDumper")]
[assembly: InternalsVisibleTo("UnitTest")]
[assembly: InternalsVisibleTo("RageCoop.ResourceBuilder")]
namespace RageCoop.Core
{
internal static class CoreUtils
{
internal static Random SafeRandom => _randInstance.Value;
private static int _randSeed = Environment.TickCount;
private static readonly ThreadLocal<Random> _randInstance
= new(() => new Random(Interlocked.Increment(ref _randSeed)));
private static readonly HashSet<string> ToIgnore = new()
{
"RageCoop.Client",
"RageCoop.Client.Loader",
"RageCoop.Client.Installer",
"RageCoop.Core",
"RageCoop.Server",
"ScriptHookVDotNet2",
"ScriptHookVDotNet3",
"ScriptHookVDotNet",
"ScriptHookVDotNetCore"
};
public static string FormatToSharpStyle(string input, int offset)
{
var ss = input.Substring(offset).Split("_".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
// Replace first character with upper case
for (var i = 0; i < ss.Length; i++)
{
var sec = ss[i].ToLower();
var head = sec[0];
ss[i] = head.ToString().ToUpper() + sec.Remove(0, 1);
}
return string.Join("", ss);
}
public static string ToHex(this int value)
{
return string.Format("0x{0:X}", value);
}
public static string ToHex(this uint value)
{
return string.Format("0x{0:X}", value);
}
public static int RandInt(int start, int end)
{
return SafeRandom.Next(start, end);
}
public static string GetTempDirectory(string dir = null)
{
dir = dir ?? Path.GetTempPath();
string path;
do
{
path = Path.Combine(dir, RandomString(10));
} while (Directory.Exists(path) || File.Exists(path));
return path;
}
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[SafeRandom.Next(s.Length)]).ToArray());
}
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[] { '\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)
{
name = Path.GetFileNameWithoutExtension(name);
return ToIgnore.Contains(name) || AssemblyLoadContext.Default.Assemblies.Any(x => x.GetName().Name == name);
}
public static void ForceLoadAllAssemblies()
{
foreach (var a in AssemblyLoadContext.Default.Assemblies)
LoadAllReferencedAssemblies(a.GetName());
}
public static void LoadAllReferencedAssemblies(this AssemblyName assembly)
{
try
{
foreach (var child in Assembly.Load(assembly).GetReferencedAssemblies())
LoadAllReferencedAssemblies(child);
}
catch (Exception ex)
{
if (!assembly.Name.StartsWith("Microsoft.CodeAnalysis"))
{
System.Console.WriteLine("Error loading dependency: " + ex);
}
}
}
public static string ToFullPath(this string path)
{
return Path.GetFullPath(path);
}
public static IPEndPoint StringToEndPoint(string endpointstring)
{
return StringToEndPoint(endpointstring, -1);
}
public static IPEndPoint StringToEndPoint(string endpointstring, int defaultport)
{
if (string.IsNullOrEmpty(endpointstring)
|| endpointstring.Trim().Length == 0)
throw new ArgumentException("Endpoint descriptor may not be empty.");
if (defaultport != -1 &&
(defaultport < IPEndPoint.MinPort
|| defaultport > IPEndPoint.MaxPort))
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
var values = endpointstring.Split(':');
IPAddress ipaddy;
var port = -1;
//check if we have an IPv6 or ports
if (values.Length <= 2) // ipv4 or hostname
{
if (values.Length == 1)
//no port is specified, default
port = defaultport;
else
port = getPort(values[1]);
//try to use the address as IPv4, otherwise get hostname
if (!IPAddress.TryParse(values[0], out ipaddy))
ipaddy = GetIPfromHost(values[0]);
}
else if (values.Length > 2) //ipv6
{
//could [a:b:c]:d
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
{
var ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
ipaddy = IPAddress.Parse(ipaddressstring);
port = getPort(values[values.Length - 1]);
}
else //[a:b:c] or a:b:c
{
ipaddy = IPAddress.Parse(endpointstring);
port = defaultport;
}
}
else
{
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
}
if (port == -1)
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
return new IPEndPoint(ipaddy, port);
}
private static int getPort(string p)
{
if (!int.TryParse(p, out var port)
|| port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort)
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
return port;
}
public static IPAddress GetLocalAddress(string target = "8.8.8.8")
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0))
{
socket.Connect(target, 65530);
var endPoint = socket.LocalEndPoint as IPEndPoint;
return endPoint.Address;
}
}
public static IPAddress GetIPfromHost(string p)
{
var hosts = Dns.GetHostAddresses(p);
if (hosts == null || hosts.Length == 0)
throw new ArgumentException(string.Format("Host not found: {0}", p));
return hosts[0];
}
public static IpInfo GetIPInfo()
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var httpClient = new HttpClient();
var response = httpClient.GetAsync("https://ipinfo.io/json").GetAwaiter().GetResult();
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return JsonDeserialize<IpInfo>(content);
}
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
foreach (var dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (var file in source.GetFiles())
file.CopyTo(Path.Combine(target.FullName, file.Name), true);
}
public static string GetInvariantRID()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "win-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return "linux-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return "osx-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
return "unknown";
}
/// <summary>
/// Get local ip addresses on all network interfaces
/// </summary>
/// <returns></returns>
public static List<IPAddress> GetLocalAddress()
{
var addresses = new List<IPAddress>();
foreach (var netInterface in NetworkInterface.GetAllNetworkInterfaces())
{
var ipProps = netInterface.GetIPProperties();
foreach (var addr in ipProps.UnicastAddresses) addresses.Add(addr.Address);
}
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));
}
public static float GetFloat(this XmlNode n)
{
return float.Parse(n.Attributes["value"].Value);
}
/// <summary>
/// Generate jenkins one-at-a-time hash from specified string (lower)
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static uint JoaatHash(string key)
{
var i = 0;
uint hash = 0;
while (i != key.Length)
{
hash += char.ToLowerInvariant(key[i++]);
hash += hash << 10;
hash ^= hash >> 6;
}
hash += hash << 3;
hash ^= hash >> 11;
hash += hash << 15;
return hash;
}
public static unsafe bool StructCmp<T>(ref T left, ref T right) where T : unmanaged
{
fixed (T* pLeft = &left, pRight = &right)
{
return MemCmp(pLeft, pRight, sizeof(T));
}
}
public static unsafe bool StructCmp<T>(T left, T right) where T : unmanaged
{
return MemCmp(&left, &right, sizeof(T));
}
static bool _simdSupported = System.Numerics.Vector<byte>.IsSupported;
static int _simdSlots = _simdSupported ? System.Numerics.Vector<byte>.Count : 0;
/// <summary>
/// SIMD-accelerated memory comparer
/// </summary>
/// <returns></returns>
public static unsafe bool MemCmp(void* p1, void* p2, int cbToCompare)
{
int numVectors = cbToCompare / _simdSlots;
int ceiling = numVectors * _simdSlots;
if (numVectors > 0)
{
ReadOnlySpan<System.Numerics.Vector<byte>> leftVecArray = new(p1, numVectors);
ReadOnlySpan<System.Numerics.Vector<byte>> rightVecArray = new(p2, numVectors);
for (int i = 0; i < numVectors; i++)
{
if (leftVecArray[i] != rightVecArray[i])
return false;
}
}
int numWords = cbToCompare / sizeof(IntPtr);
var pwLeft = (IntPtr*)p1;
var pwRight = (IntPtr*)p2;
for (int i = (ceiling / sizeof(IntPtr)); i < numWords; i++)
{
if (pwLeft[i] != pwRight[i])
return false;
}
var pbLeft = (byte*)p1;
var pbRight = (byte*)p2;
for (int i = ceiling + (numWords * sizeof(IntPtr)); i < cbToCompare; i++)
{
if (pbLeft[i] != pbRight[i])
return false;
}
return true;
}
}
internal class IpInfo
{
[JsonPropertyName("ip")] public string Address { get; set; }
[JsonPropertyName("country")] public string Country { get; set; }
}
internal static class Extensions
{
public static byte[] GetBytes(this string s)
{
return Encoding.UTF8.GetBytes(s);
}
public static string GetString(this byte[] data)
{
return Encoding.UTF8.GetString(data);
}
public static T GetPacket<T>(this NetIncomingMessage msg) where T : Packet, new()
{
var p = new T();
p.Deserialize(msg);
return p;
}
public static bool HasPedFlag(this PedDataFlags flags, PedDataFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasProjDataFlag(this ProjectileDataFlags flags, ProjectileDataFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasVehFlag(this VehicleDataFlags flags, VehicleDataFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasConfigFlag(this PlayerConfigFlags flags, PlayerConfigFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasEventFlag(this CustomEventFlags flags, CustomEventFlags flag)
{
return (flags & flag) != 0;
}
public static Type GetActualType(this TypeCode code)
{
switch (code)
{
case TypeCode.Boolean:
return typeof(bool);
case TypeCode.Byte:
return typeof(byte);
case TypeCode.Char:
return typeof(char);
case TypeCode.DateTime:
return typeof(DateTime);
case TypeCode.DBNull:
return typeof(DBNull);
case TypeCode.Decimal:
return typeof(decimal);
case TypeCode.Double:
return typeof(double);
case TypeCode.Empty:
return null;
case TypeCode.Int16:
return typeof(short);
case TypeCode.Int32:
return typeof(int);
case TypeCode.Int64:
return typeof(long);
case TypeCode.Object:
return typeof(object);
case TypeCode.SByte:
return typeof(sbyte);
case TypeCode.Single:
return typeof(float);
case TypeCode.String:
return typeof(string);
case TypeCode.UInt16:
return typeof(ushort);
case TypeCode.UInt32:
return typeof(uint);
case TypeCode.UInt64:
return typeof(ulong);
}
return null;
}
public static string DumpWithType(this IEnumerable<object> objects)
{
var sb = new StringBuilder();
foreach (var obj in objects) sb.Append(obj.GetType() + ":" + obj + "\n");
return sb.ToString();
}
public static string Dump<T>(this IEnumerable<T> objects)
{
return $"{{{string.Join(",", objects)}}}";
}
public static void ForEach<T>(this IEnumerable<T> objects, Action<T> action)
{
foreach (var obj in objects) action(obj);
}
public static byte[] ReadToEnd(this Stream stream)
{
if (stream is MemoryStream)
return ((MemoryStream)stream).ToArray();
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
public static MemoryStream ToMemStream(this Stream stream)
{
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream;
}
public static byte[] Join(this List<byte[]> arrays, int lengthPerArray = -1)
{
if (arrays.Count == 1) return arrays[0];
var output = lengthPerArray == -1
? new byte[arrays.Sum(arr => arr.Length)]
: new byte[arrays.Count * lengthPerArray];
var writeIdx = 0;
foreach (var byteArr in arrays)
{
byteArr.CopyTo(output, writeIdx);
writeIdx += byteArr.Length;
}
return output;
}
public static bool IsScript(this Type type, Type scriptType)
{
return !type.IsAbstract && type.IsSubclassOf(scriptType);
}
}
/// <summary>
/// Some extension methods provided by RageCoop
/// </summary>
public static class PublicExtensions
{
/// <summary>
/// Get a SHA256 hashed byte array of the input string, internally used to hash password at client side.
/// </summary>
/// <param name="inputString"></param>
/// <returns></returns>
public static byte[] GetSHA256Hash(this string inputString)
{
using (HashAlgorithm algorithm = SHA256.Create())
{
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
}
/// <summary>
/// Convert a byte array to hex-encoded string, internally used to trigger handshake event
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string ToHexString(this byte[] data)
{
return BitConverter.ToString(data).Replace("-", string.Empty);
}
/// <summary>
/// Convert a string to IP address
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
public static IPAddress ToIP(this string ip)
{
return IPAddress.Parse(ip);
}
}
}

View File

@ -1,55 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace RageCoop.Core
{
class IPAddressConverter : JsonConverter<IPAddress>
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IPAddress);
}
public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null) return null;
return IPAddress.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options)
{
writer.WriteStringValue(value?.ToString());
}
}
class IPEndPointConverter : JsonConverter<IPEndPoint>
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IPEndPoint);
}
public override IPEndPoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null) return null;
var jo = JsonNode.Parse(ref reader);
return new IPEndPoint(IPAddress.Parse(jo["Address"].ToString()), int.Parse(jo["Port"].ToString()));
}
public override void Write(Utf8JsonWriter writer, IPEndPoint value, JsonSerializerOptions options)
{
new JsonObject()
{
{ "Address", value.Address?.ToString() },
{ "Port", value.Port }
}.WriteTo(writer);
}
}
}

View File

@ -1,203 +0,0 @@
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 delegate void FlushDelegate(LogLine line, string fomatted);
private readonly ConcurrentQueue<LogLine> _queuedLines = new ConcurrentQueue<LogLine>();
public readonly string DateTimeFormat = "HH:mm:ss";
private readonly Thread LoggerThread;
/// <summary>
/// Whether to use UTC time for timestamping the log
/// </summary>
public readonly bool UseUtc = false;
public bool FlushImmediately = false;
public int FlushInterval = 1000;
/// <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";
private bool Stopping;
public List<StreamWriter> Writers = new() { new StreamWriter(Console.OpenStandardOutput()) };
internal Logger()
{
Name = Environment.ProcessId.ToString();
if (!FlushImmediately)
{
LoggerThread = new Thread(() =>
{
while (!Stopping)
{
Flush();
Thread.Sleep(1000);
}
Flush();
});
LoggerThread.Start();
}
}
/// <summary>
/// Stop backdround thread and flush all pending messages.
/// </summary>
public void Dispose()
{
Stopping = true;
LoggerThread?.Join();
}
public event FlushDelegate OnFlush;
/// <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 =>
{
try
{
x.WriteLine(formatted);
x.Flush();
}
catch (Exception ex)
{
HandleError(ex);
}
});
OnFlush?.Invoke(line, formatted);
}
}
catch (Exception ex)
{
HandleError(ex);
}
}
}
void HandleError(Exception ex)
{
Console.WriteLine($"Logger {this} flush error: {ex}");
}
public class LogLine
{
public LogLevel LogLevel;
public string Message;
public DateTime TimeStamp;
internal LogLine()
{
}
}
}
}

View File

@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Lidgren.Network;
namespace RageCoop.Core
{
internal class CoopPeer : NetPeer, IDisposable
{
private readonly Logger Log;
private readonly Thread _receiver;
private bool _stopping;
public EventHandler<NetIncomingMessage> OnMessageReceived;
public CoopPeer(NetPeerConfiguration config,Logger logger) : base(config)
{
Log = logger;
Start();
NetIncomingMessage msg;
_receiver = new Thread(() =>
{
while (!_stopping)
{
msg = WaitMessage(200);
if (msg != null) OnMessageReceived?.Invoke(this, msg);
}
});
_receiver.Start();
}
/// <summary>
/// Terminate all connections and background thread
/// </summary>
public void Dispose()
{
_stopping = true;
if (Status == NetPeerStatus.Running)
{
Shutdown("Bye!");
}
if (_receiver.IsAlive)
{
Log?.Debug("Stopping message thread");
_receiver.Join();
}
Log?.Debug("Stopping network thread");
Join();
Log?.Debug("CoopPeer disposed");
}
public void SendTo(Packet p, NetConnection connection, ConnectionChannel channel = ConnectionChannel.Default,
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
var outgoingMessage = CreateMessage();
p.Pack(outgoingMessage);
SendMessage(outgoingMessage, connection, method, (int)channel);
}
public void SendTo(Packet p, IList<NetConnection> connections,
ConnectionChannel channel = ConnectionChannel.Default,
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
var outgoingMessage = CreateMessage();
p.Pack(outgoingMessage);
SendMessage(outgoingMessage, connections, method, (int)channel);
}
public void Send(Packet p, IList<NetConnection> cons, ConnectionChannel channel = ConnectionChannel.Default,
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
var outgoingMessage = CreateMessage();
p.Pack(outgoingMessage);
SendMessage(outgoingMessage, cons, method, (int)channel);
}
}
}

View File

@ -1,38 +0,0 @@
using System;
using GTA;
using Lidgren.Network;
using RageCoop.Core.Scripting;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class CustomEvent : Packet
{
public CustomEventFlags Flags;
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
{
Flags = flags;
}
public override PacketType Type => PacketType.CustomEvent;
public int Hash { get; set; }
public byte[] Payload;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write((byte)Flags);
m.Write(Hash);
m.Write(Payload);
}
public unsafe override void Deserialize(NetIncomingMessage m)
{
Flags = (CustomEventFlags)m.ReadByte();
Hash = m.ReadInt32();
Payload = m.ReadBytes(m.LengthBytes - m.PositionInBytes);
}
}
}
}

View File

@ -1,74 +0,0 @@
using GTA;
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
/// <summary>
/// Common data for synchronizing an entity
/// </summary>
internal struct EntityData
{
public int ID;
public int OwnerID;
public LQuaternion Quaternion;
public LVector3 Position;
public LVector3 Velocity;
public int ModelHash;
}
internal struct VehicleData
{
public VehicleDataFlags Flags;
public float ThrottlePower;
public float BrakePower;
public float SteeringAngle;
public VehicleLockStatus LockStatus;
public float DeluxoWingRatio;
}
internal struct VehicleDataFull
{
public float EngineHealth;
public (byte, byte) Colors;
public byte ToggleModsMask;
public VehicleDamageModel DamageModel;
public int Livery;
public byte HeadlightColor;
public byte RadioStation;
public ushort ExtrasMask;
public byte RoofState;
public byte LandingGear;
}
/// <summary>
/// Non-fixed vehicle data
/// </summary>
internal struct VehicleDataVar
{
public string LicensePlate;
public (int, int)[] Mods;
public void WriteTo(NetOutgoingMessage m)
{
m.Write(LicensePlate);
m.Write((byte)Mods.Length);
for(int i = 0;i < Mods.Length; i++)
{
m.Write(ref Mods[i]);
}
}
public void ReadFrom(NetIncomingMessage m)
{
LicensePlate = m.ReadString();
Mods = new (int, int)[m.ReadByte()];
for(int i = 0; i < Mods.Length; i++)
{
Mods[i] = m.Read<(int, int)>();
}
}
}
}

View File

@ -1,41 +0,0 @@
using System.Collections.Generic;
using GTA;
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
public class VehicleSync : Packet
{
public override PacketType Type => PacketType.VehicleSync;
public EntityData ED;
public VehicleData VD;
public VehicleDataFull VDF;
public VehicleDataVar VDV;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ref ED);
m.Write(ref VD);
if (VD.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
m.Write(ref VDF);
VDV.WriteTo(m);
}
}
public override void Deserialize(NetIncomingMessage m)
{
m.Read(out ED);
m.Read(out VD);
if (VD.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
m.Read(out VDF);
VDV.ReadFrom(m);
}
}
}
}
}

View File

@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>true</ImplicitUsings>
<NoWarn>1701;1702;CS1591</NoWarn>
<TargetFrameworks>net7.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.1</AssemblyVersion>
<FileVersion>0.1</FileVersion>
<Version>0.1</Version>
<DebugType>embedded</DebugType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Configurations>Debug;Release;API</Configurations>
<OutDir>..\bin\$(Configuration)\Core</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'API'">
<OutDir>..\bin\API</OutDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libs\Lidgren.Network\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore\ScriptHookVDotNetCore.csproj" />
</ItemGroup>
</Project>

View File

@ -1,85 +0,0 @@
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
namespace RageCoop.Core.Scripting
{
public unsafe delegate void CustomEventHandlerDelegate(int hash, byte* data, int cbData);
/// <summary>
/// </summary>
public class CustomEventReceivedArgs : EventArgs
{
/// <summary>
/// The event hash
/// </summary>
public int Hash { get; set; }
/// <summary>
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion
/// </summary>
public object[] Args { get; set; }
internal object Tag { get; set; }
}
public unsafe class CustomEventHandler
{
// Make sure the handler doesn't get GC'd
static List<CustomEventHandler> _handlers = new();
[ThreadStatic]
static object _tag;
public CustomEventHandler()
{
lock (_handlers)
{
_handlers.Add(this);
}
}
public CustomEventHandler(IntPtr func) : this()
{
FunctionPtr = (ulong)func;
Directory = SHVDN.Core.CurrentDirectory;
}
[JsonIgnore]
private CustomEventHandlerDelegate _managedHandler; // Used to keep GC reference
[JsonInclude]
public ulong FunctionPtr { get; private set; }
[JsonInclude]
public string Directory { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="hash"></param>
/// <param name="data"></param>
/// <param name="cbData"></param>
/// <param name="tag">Only works when using <see cref="CustomEventReceivedArgs"/></param>
public void Invoke(int hash, byte* data, int cbData, object tag = null)
{
_tag = tag;
((delegate* unmanaged<int, byte*, int, void>)FunctionPtr)(hash, data, cbData);
_tag = null;
}
public static implicit operator CustomEventHandler(CustomEventHandlerDelegate handler)
=> new(Marshal.GetFunctionPointerForDelegate(handler)) { _managedHandler = handler };
public static implicit operator CustomEventHandler(Action<CustomEventReceivedArgs> handler)
{
return new CustomEventHandlerDelegate((hash, data, cbData) =>
{
var reader = GetReader(data, cbData);
var arg = new CustomEventReceivedArgs
{
Hash = hash,
Args = CustomEvents.ReadObjects(reader),
Tag = _tag
};
handler(arg);
});
}
}
}

View File

@ -1,325 +0,0 @@
using GTA;
using GTA.Math;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
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 var 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>
/// Common processing for custome client\server events
/// </summary>
public static partial 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";
#region TYPE CONSTANTS
public const byte T_BYTE = 1;
public const byte T_SHORT = 2;
public const byte T_USHORT = 3;
public const byte T_INT = 4;
public const byte T_UINT = 5;
public const byte T_LONG = 6;
public const byte T_ULONG = 7;
public const byte T_FLOAT = 8;
public const byte T_BOOL = 9;
public const byte T_STR = 10;
public const byte T_VEC3 = 11;
public const byte T_QUAT = 12;
public const byte T_MODEL = 13;
public const byte T_VEC2 = 14;
public const byte T_BYTEARR = 15;
public const byte T_ID_PROP = 50;
public const byte T_ID_PED = 51;
public const byte T_ID_VEH = 52;
public const byte T_ID_BLIP = 60;
#endregion
public static void WriteObjects(BufferWriter b, params object[] objs)
{
b.WriteVal(objs.Length);
foreach (var obj in objs)
{
switch (obj)
{
case byte value:
b.WriteVal(T_BYTE);
b.WriteVal(value);
break;
case short value:
b.WriteVal(T_SHORT);
b.WriteVal(value);
break;
case ushort value:
b.WriteVal(T_USHORT);
b.WriteVal(value);
break;
case int value:
b.WriteVal(T_INT);
b.WriteVal(value);
break;
case uint value:
b.WriteVal(T_UINT);
b.WriteVal(value);
break;
case long value:
b.WriteVal(T_LONG);
b.WriteVal(value);
break;
case ulong value:
b.WriteVal(T_ULONG);
b.WriteVal(value);
break;
case float value:
b.WriteVal(T_FLOAT);
b.WriteVal(value);
break;
case bool value:
b.WriteVal(T_BOOL);
b.WriteVal(value);
break;
case string value:
b.WriteVal(T_STR);
b.Write(value);
break;
case Vector2 value:
b.WriteVal(T_VEC2);
var vec2 = (LVector2)value;
b.Write(ref vec2);
break;
case LVector2 value:
b.WriteVal(T_VEC2);
b.Write(ref value);
break;
case Vector3 value:
b.WriteVal(T_VEC3);
var vec3 = (LVector3)value;
b.Write(ref vec3);
break;
case LVector3 value:
b.WriteVal(T_VEC3);
b.Write(ref value);
break;
case Quaternion value:
b.WriteVal(T_QUAT);
var quat = (LQuaternion)value;
b.Write(ref quat);
break;
case LQuaternion value:
b.WriteVal(T_QUAT);
b.Write(ref value);
break;
case Model value:
b.WriteVal(T_MODEL);
b.WriteVal(value);
break;
case byte[] value:
b.WriteVal(T_BYTEARR);
b.WriteArray(value);
break;
case Tuple<byte, byte[]> value:
b.WriteVal(value.Item1);
b.Write(new ReadOnlySpan<byte>(value.Item2));
break;
default:
throw new Exception("Unsupported object type: " + obj.GetType());
}
}
}
public static object[] ReadObjects(BufferReader r)
{
var Args = new object[r.ReadVal<int>()];
for (var i = 0; i < Args.Length; i++)
{
var type = r.ReadVal<byte>();
switch (type)
{
case T_BYTE:
Args[i] = r.ReadVal<byte>();
break;
case T_SHORT:
Args[i] = r.ReadVal<short>();
break;
case T_USHORT:
Args[i] = r.ReadVal<ushort>();
break;
case T_INT:
Args[i] = r.ReadVal<int>();
break;
case T_UINT:
Args[i] = r.ReadVal<uint>();
break;
case T_LONG:
Args[i] = r.ReadVal<long>();
break;
case T_ULONG:
Args[i] = r.ReadVal<ulong>();
break;
case T_FLOAT:
Args[i] = r.ReadVal<float>();
break;
case T_BOOL:
Args[i] = r.ReadVal<bool>();
break;
case T_STR:
r.Read(out string str);
Args[i] = str;
break;
case T_VEC3:
r.Read(out LVector3 vec);
Args[i] = (Vector3)vec;
break;
case T_QUAT:
r.Read(out LQuaternion quat);
Args[i] = (Quaternion)quat;
break;
case T_MODEL:
Args[i] = r.ReadVal<Model>();
break;
case T_VEC2:
r.Read(out LVector2 vec2);
Args[i] = (Vector2)vec2;
break;
case T_BYTEARR:
Args[i] = r.ReadArray<byte>();
break;
case T_ID_BLIP:
case T_ID_PED:
case T_ID_PROP:
case T_ID_VEH:
Args[i] = IdToHandle(type, r.ReadVal<int>());
break;
default:
throw new InvalidOperationException($"Unexpected type: {type}");
}
}
return Args;
}
static unsafe delegate* unmanaged<byte, int, int> _idToHandlePtr;
public static unsafe int IdToHandle(byte type, int id)
{
if (_idToHandlePtr == default)
{
if (SHVDN.Core.GetPtr == default)
throw new InvalidOperationException("Not client");
_idToHandlePtr = (delegate* unmanaged<byte, int, int>)SHVDN.Core.GetPtr("RageCoop.Client.Scripting.API.IdToHandle");
if (_idToHandlePtr == default)
throw new KeyNotFoundException("IdToHandle function not found");
}
return _idToHandlePtr(type, id);
}
}
}

View File

@ -1,70 +0,0 @@
global using static RageCoop.Core.Shared;
using System;
using System.Reflection;
using System.Text.Json;
namespace RageCoop.Core
{
public class JsonDontSerialize : Attribute
{
}
internal class Shared
{
static Type JsonTypeCheck(Type type)
{
if (type?.GetCustomAttribute<JsonDontSerialize>() != null)
throw new TypeAccessException($"The type {type} cannot be serialized");
return type;
}
static object JsonTypeCheck(object obj)
{
JsonTypeCheck(obj?.GetType());
return obj;
}
public static readonly JsonSerializerOptions JsonSettings = new();
static Shared()
{
JsonSettings.Converters.Add(new IPAddressConverter());
JsonSettings.Converters.Add(new IPEndPointConverter());
JsonSettings.WriteIndented = true;
JsonSettings.IncludeFields = true;
}
public static object JsonDeserialize(string text, Type type)
{
return JsonSerializer.Deserialize(text, JsonTypeCheck(type), JsonSettings);
}
public static T JsonDeserialize<T>(string text) => (T)JsonDeserialize(text, typeof(T));
public static string JsonSerialize(object obj) => JsonSerializer.Serialize(JsonTypeCheck(obj), JsonSettings);
/// <summary>
/// Shortcut to <see cref="BufferReader.ThreadLocal"/>
/// </summary>
/// <returns></returns>
public static unsafe BufferReader GetReader(byte* data = null, int cbData = 0)
{
var reader = BufferReader.ThreadLocal.Value;
reader.Initialise(data, cbData);
return reader;
}
/// <summary>
/// Shortcut to <see cref="BufferWriter.ThreadLocal"/>
/// </summary>
/// <returns></returns>
public static BufferWriter GetWriter(bool reset = true)
{
var writer = BufferWriter.ThreadLocal.Value;
if (reset)
{
writer.Reset();
}
return writer;
}
}
}

View File

@ -1,136 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
namespace RageCoop.Core
{
internal class AnimDic
{
public string[] Animations;
public string DictionaryName;
public static AnimDic[] Dump(string input, string output)
{
Console.WriteLine("Generating " + output);
if (!File.Exists(input))
{
Console.WriteLine("Downloading");
HttpHelper.DownloadFile(
"https://raw.githubusercontent.com/DurtyFree/gta-v-data-dumps/master/animDictsCompact.json", input);
}
Console.WriteLine("Deserializing");
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(input));
Console.WriteLine("Serializing");
File.WriteAllText(output, JsonSerialize(anims));
return anims;
}
}
internal class WeaponInfo
{
public string Audio;
public float Damage;
public string DamageType;
public string FireType;
public bool IsVehicleWeapon;
public string Name;
public float Speed;
public WeaponInfo()
{
}
public WeaponInfo(XmlNode node)
{
if (node.Attributes["type"].Value != "CWeaponInfo") throw new Exception("Not a CWeaponInfo node");
foreach (XmlNode info in node.ChildNodes)
switch (info.Name)
{
case "Name":
Name = info.InnerText;
break;
case "Audio":
Audio = info.InnerText;
break;
case "FireType":
FireType = info.InnerText;
break;
case "DamageType":
DamageType = info.InnerText;
break;
case "Damage":
Damage = info.GetFloat();
break;
case "Speed":
Speed = info.GetFloat();
break;
}
IsVehicleWeapon = Name.StartsWith("VEHICLE_WEAPON");
}
}
internal class VehicleInfo
{
public VehicleBone[] Bones;
public uint Hash;
public string Name;
public string[] Weapons;
}
internal class VehicleBone
{
public uint BoneID;
public uint BoneIndex;
public string BoneName;
}
internal class WeaponBones
{
public VehicleBone[] Bones;
public string Name;
}
internal class VehicleWeaponInfo
{
public uint Hash;
public string Name;
public Dictionary<uint, WeaponBones> Weapons = new();
public static void Dump(string input, string output)
{
Console.WriteLine("Generating " + output);
if (!File.Exists(input))
{
Console.WriteLine("Downloading");
HttpHelper.DownloadFile(
"https://raw.githubusercontent.com/DurtyFree/gta-v-data-dumps/master/vehicles.json", input);
}
Console.WriteLine("Deserializing");
var infos = JsonDeserialize<VehicleInfo[]>(File.ReadAllText(input));
Console.WriteLine("Serializing");
File.WriteAllText(output,
JsonSerialize(
infos.Select(FromVehicle).Where(x => x != null)));
}
public static VehicleWeaponInfo FromVehicle(VehicleInfo info)
{
if (info.Weapons.Length == 0) return null;
var result = new VehicleWeaponInfo { Hash = info.Hash, Name = info.Name };
for (var i = 0; i < info.Weapons.Length; i++)
result.Weapons.Add(CoreUtils.JoaatHash(info.Weapons[i])
, new WeaponBones
{
Name = info.Weapons[i],
Bones = info.Bones.Where(x =>
x.BoneName.StartsWith($"weapon_{i + 1}") && !x.BoneName.EndsWith("rot")).ToArray()
});
return result;
}
}
}

View File

@ -1,90 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace RageCoop.Core
{
/// <summary>
/// A worker that constantly execute jobs in a background thread.
/// </summary>
public class Worker : IDisposable
{
private readonly SemaphoreSlim _semaphoreSlim;
private readonly Thread _workerThread;
private readonly ConcurrentQueue<Action> Jobs = new ConcurrentQueue<Action>();
private bool _stopping;
internal Worker(string name, Logger logger, int maxJobs = int.MaxValue)
{
Name = name;
_semaphoreSlim = new SemaphoreSlim(0, maxJobs);
_workerThread = new Thread(() =>
{
while (!_stopping)
{
IsBusy = false;
_semaphoreSlim.Wait();
if (Jobs.TryDequeue(out var job))
{
IsBusy = true;
try
{
job.Invoke();
}
catch (Exception ex)
{
logger.Error("Error occurred when executing queued job:");
logger.Error(ex);
}
}
else
{
throw new InvalidOperationException("Hmm... that's unexpected.");
}
}
IsBusy = false;
});
_workerThread.Start();
}
/// <summary>
/// Name of the worker
/// </summary>
public string Name { get; set; }
/// <summary>
/// Whether this worker is busy executing job(s).
/// </summary>
public bool IsBusy { get; private set; }
/// <summary>
/// Finish current job and stop the worker.
/// </summary>
public void Dispose()
{
Stop();
_semaphoreSlim.Dispose();
}
/// <summary>
/// Queue a job to be executed
/// </summary>
/// <param name="work"></param>
public void QueueJob(Action work)
{
Jobs.Enqueue(work);
_semaphoreSlim.Release();
}
/// <summary>
/// Finish current job and stop the worker.
/// </summary>
public void Stop()
{
_stopping = true;
QueueJob(() => { });
if (_workerThread.IsAlive) _workerThread.Join();
}
}
}

View File

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
/// <summary>
/// A light-weight and less restricted implementation of <see cref="Span{T}"/>, gonna be used at some point, maybe?
/// </summary>
/// <typeparam name="T"></typeparam>
public readonly unsafe struct XSpan<T> where T : unmanaged
{
public XSpan(void* address, int len)
{
Address = (T*)address;
Length = len;
}
public T this[int i]
{
get { return Address[i]; }
set { Address[i] = value; }
}
public readonly T* Address;
public readonly int Length;
public void CopyTo(XSpan<T> dest, int destStart = 0)
{
for (int i = 0; i < Length; i++)
{
dest[destStart + i] = this[i];
}
}
public XSpan<byte> Slice(int start) => new(Address + start, Length - start);
public XSpan<byte> Slice(int start, int len) => new(Address + start, len);
public static implicit operator Span<T>(XSpan<T> s)
{
return new Span<T>(s.Address, s.Length);
}
public static implicit operator XSpan<T>(Span<T> s)
{
fixed (T* ptr = s)
{
return new XSpan<T>(ptr, s.Length);
}
}
}
}

View File

@ -15,7 +15,7 @@ RAGECOOP brings multiplayer experience to the story mode, you can complete missi
# 👁 Requirements
- ScriptHookV
- ScriptHookVDotNet 3.5.1 or later
- ScriptHookVDotNet 3.6.0 or later
- .NET Framework 4.8 Runtime or SDK
# 📋 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!
6. Weaponized vehicle sync(WIP).
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.online).
8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.com).
# ⚠ Known issues

View File

@ -3,48 +3,22 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "RageCoop.Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "RageCoop.Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client", "Client\Scripts\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "RageCoop.Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "Client\Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{531656CF-7269-488D-B042-741BC96C3941}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{12E29AB7-74C4-4250-8975-C02D7FFC2D7B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{70A1F09D-648D-4C8B-8947-E920B1A587A3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataDumper", "Tools\DataDumper\DataDumper.csproj", "{6387D897-09AF-4464-B440-80438E3BB8D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Scripting", "Client\Scripting\RageCoop.Client.Scripting.csproj", "{FE47AFBA-0613-4378-B318-892DEB7B3D88}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "Tools\UnitTest\UnitTest.csproj", "{4FE96671-3DC5-4394-B2E3-584399E57310}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{1AF84C35-B86B-46BB-9FDB-ACB7787E582A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptHookVDotNetCore", "libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore\ScriptHookVDotNetCore.csproj", "{B15EDABB-30AF-475A-823D-ACB9F75CFE13}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lidgren.Network", "libs\Lidgren.Network\Lidgren.Network\Lidgren.Network.csproj", "{02616B5A-2A68-42AA-A91E-311EF95FCF44}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeGen", "Tools\CodeGen\CodeGen.csproj", "{C4CF8A98-7393-42BD-97A1-2E850D12890A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "RageCoop.Client.Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
API|Any CPU = API|Any CPU
API|x64 = API|x64
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.API|Any CPU.ActiveCfg = API|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.API|Any CPU.Build.0 = API|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.API|x64.ActiveCfg = API|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.API|x64.Build.0 = API|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -53,10 +27,6 @@ Global
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|Any CPU.Build.0 = Release|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|x64.ActiveCfg = Release|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|x64.Build.0 = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.API|Any CPU.ActiveCfg = API|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.API|Any CPU.Build.0 = API|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.API|x64.ActiveCfg = API|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.API|x64.Build.0 = API|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -65,10 +35,6 @@ Global
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|Any CPU.Build.0 = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.ActiveCfg = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.Build.0 = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.API|Any CPU.ActiveCfg = API|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.API|Any CPU.Build.0 = API|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.API|x64.ActiveCfg = API|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.API|x64.Build.0 = API|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -77,10 +43,6 @@ Global
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.Build.0 = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.ActiveCfg = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.API|Any CPU.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.API|Any CPU.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.API|x64.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.API|x64.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -89,92 +51,10 @@ Global
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|Any CPU.ActiveCfg = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|Any CPU.Build.0 = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|x64.ActiveCfg = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|x64.Build.0 = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Debug|x64.ActiveCfg = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Debug|x64.Build.0 = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|Any CPU.Build.0 = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|x64.ActiveCfg = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|x64.Build.0 = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|Any CPU.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|Any CPU.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|x64.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|x64.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|x64.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|x64.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|Any CPU.Build.0 = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|x64.ActiveCfg = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|x64.Build.0 = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|Any CPU.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|Any CPU.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|x64.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|x64.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|x64.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|x64.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|Any CPU.Build.0 = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|x64.ActiveCfg = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|x64.Build.0 = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|Any CPU.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|Any CPU.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|x64.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|x64.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|x64.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|x64.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|Any CPU.Build.0 = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|x64.ActiveCfg = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|x64.Build.0 = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|Any CPU.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|Any CPU.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|x64.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|x64.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|x64.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|x64.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|Any CPU.Build.0 = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|x64.ActiveCfg = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|x64.Build.0 = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|Any CPU.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|Any CPU.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|x64.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|x64.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|x64.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|x64.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|Any CPU.Build.0 = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|x64.ActiveCfg = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681} = {531656CF-7269-488D-B042-741BC96C3941}
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B} = {531656CF-7269-488D-B042-741BC96C3941}
{6387D897-09AF-4464-B440-80438E3BB8D0} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
{FE47AFBA-0613-4378-B318-892DEB7B3D88} = {531656CF-7269-488D-B042-741BC96C3941}
{4FE96671-3DC5-4394-B2E3-584399E57310} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
{B15EDABB-30AF-475A-823D-ACB9F75CFE13} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{02616B5A-2A68-42AA-A91E-311EF95FCF44} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{C4CF8A98-7393-42BD-97A1-2E850D12890A} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
EndGlobalSection

View File

@ -1,3 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Joaat/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=RAGECOOP/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -1,4 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;&#xD;
&lt;Assembly Path="C:\Users\Sardelka\.nuget\packages\sharpziplib\1.4.0\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll" /&gt;&#xD;
&lt;/AssemblyExplorer&gt;</s:String></wpf:ResourceDictionary>

View File

@ -1,8 +1,9 @@
<Application x:Class="RageCoop.Client.Installer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RageCoop.Client.Installer"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
</Application>

View File

@ -3,9 +3,9 @@
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for App.xaml
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -3,13 +3,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RageCoop.Client.Installer"
mc:Ignorable="d"
Title="MainWindow" Height="376" Width="617" Background="#FFBABABA">
<Grid>
<Grid.Background>
<ImageBrush ImageSource="/bg.png" Opacity="0.2" />
<ImageBrush ImageSource="/bg.png" Opacity="0.2"/>
</Grid.Background>
<Label x:Name="Status" FontSize="20" Foreground="#FF232323" HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Label x:Name="Status" FontSize="20" Foreground="#FF232323" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
</Window>

View File

@ -6,16 +6,17 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using RageCoop.Core;
using static RageCoop.Client.Shared;
using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using Path = System.IO.Path;
using RageCoop.Core;
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
@ -27,12 +28,13 @@ namespace RageCoop.Client.Installer
private void Choose()
{
var od = new OpenFileDialog
var od = new OpenFileDialog()
{
Filter = "GTA 5 executable |GTA5.exe;PlayGTAV.exe",
Title = "Select you GTAV executable"
};
if (od.ShowDialog() ?? !true)
if (od.ShowDialog() ?? false == true)
{
Task.Run(() =>
{
try
@ -41,118 +43,124 @@ namespace RageCoop.Client.Installer
}
catch (Exception ex)
{
MessageBox.Show("Installation failed: " + ex);
MessageBox.Show("Installation failed: " + ex.ToString());
Environment.Exit(1);
}
});
}
else
{
Environment.Exit(0);
}
}
private void Install(string root)
{
UpdateStatus("Checking requirements");
var shvPath = Path.Combine(root, "ScriptHookV.dll");
var shvdnPath = Path.Combine(root, "ScriptHookVDotNetCore.dll");
var shvdnPath = Path.Combine(root, "ScriptHookVDotNet3.dll");
var scriptsPath = Path.Combine(root, "Scripts");
var installPath = Path.Combine(root, "RageCoop");
var legacyPath = Path.Combine(scriptsPath, "RageCoop");
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName.StartsWith(installPath))
throw new InvalidOperationException(
"The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
var lemonPath = Path.Combine(scriptsPath, "LemonUI.SHVDN3.dll");
var installPath = Path.Combine(scriptsPath, "RageCoop");
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName == installPath)
{
throw new InvalidOperationException("The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
}
Directory.CreateDirectory(installPath);
if (!File.Exists(shvPath))
{
MessageBox.Show("Please install ScriptHookV first!");
Environment.Exit(1);
}
Directory.CreateDirectory(installPath);
if (!File.Exists(shvdnPath))
{
MessageBox.Show("Please install ScriptHookVDotNet first!");
Environment.Exit(1);
}
var shvdnVer = GetVer(shvdnPath);
if (shvdnVer < new Version(3, 5, 1))
if (shvdnVer < new Version(3, 6, 0))
{
MessageBox.Show("Please update ScriptHookVDotNet to latest version!" +
$"\nCurrent version is {shvdnVer}, 3.5.1 or higher is required");
$"\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");
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
{
if (f.EndsWith("RageCoop.Client.Settings.xml")) { continue; }
File.Delete(f);
}
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories))
{
File.Delete(f);
}
// <= 1.5 installation check
if (Directory.Exists(legacyPath)) Directory.Delete(legacyPath, true);
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories)) File.Delete(f);
if (File.Exists("Scripts/RageCoop.Core.dll") && File.Exists("Scripts/RageCoop.Client.dll") &&
File.Exists("Loader/RageCoop.Client.Loader.dll"))
if (File.Exists("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll"))
{
UpdateStatus("Installing...");
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()),
new DirectoryInfo(installPath));
File.Copy("Loader/RageCoop.Client.Loader.dll", Path.Combine(scriptsPath, "RageCoop.Client.Loader.dll"),
true);
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath));
Finish();
}
else
{
throw new Exception(
"Required files are missing, please re-download the installer from official website");
throw new Exception("Required files are missing, please re-download the zip from official website");
}
void Finish()
{
checkKeys:
checkKeys:
UpdateStatus("Checking conflicts");
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
var settingsPath = Path.Combine(root, SettingsPath);
ClientSettings settings = null;
var settingsPath = Path.Combine(installPath, @"Data\RageCoop.Client.Settings.xml");
Settings settings = null;
try
{
settings = Util.ReadSettings(settingsPath);
}
catch
{
settings = new();
settings = new Settings();
}
if (File.Exists(menyooConfig))
{
var lines = File.ReadAllLines(menyooConfig)
.Where(x => !x.StartsWith(";") && x.EndsWith(" = " + (int)settings.MenuKey));
var lines = File.ReadAllLines(menyooConfig).Where(x => !x.StartsWith(";") && x.EndsWith(" = " + (int)settings.MenuKey));
if (lines.Any())
{
if (MessageBox.Show("Following menyoo config value will conflict with RAGECOOP menu key\n" +
string.Join("\n", lines)
+ "\nDo you wish to change the Menu Key?", "Warning!",
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
string.Join("\n", lines)
+ "\nDo you wish to change the Menu Key?", "Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var ae = new AutoResetEvent(false);
UpdateStatus("Press the key you wish to change to");
Dispatcher.BeginInvoke(new Action(() =>
KeyDown += (s, e) =>
{
settings.MenuKey = (GTA.Keys)KeyInterop.VirtualKeyFromKey(e.Key);
ae.Set();
}));
KeyDown += (s, e) =>
{
settings.MenuKey = (Keys)KeyInterop.VirtualKeyFromKey(e.Key);
ae.Set();
}));
ae.WaitOne();
if (!Util.SaveSettings(settingsPath, settings))
{
MessageBox.Show("Error occurred when saving settings");
Environment.Exit(1);
}
MessageBox.Show("Menu key changed to " + settings.MenuKey);
goto checkKeys;
}
}
}
UpdateStatus("Checking ZeroTier");
@ -162,29 +170,26 @@ namespace RageCoop.Client.Installer
}
catch
{
if (MessageBox.Show(
"You can't join ZeroTier server unless ZeroTier is installed, do you want to download and install it?",
"Install ZeroTier", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
if (MessageBox.Show("You can't join ZeroTier server unless ZeroTier is installed, do you want to download and install it?", "Install ZeroTier", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var url = "https://download.zerotier.com/dist/ZeroTier%20One.msi";
UpdateStatus("Downloading ZeroTier from " + url);
try
{
HttpHelper.DownloadFile(url, "ZeroTier.msi",
p => UpdateStatus("Downloading ZeroTier " + p + "%"));
HttpHelper.DownloadFile(url, "ZeroTier.msi", (p) => UpdateStatus("Downloading ZeroTier " + p + "%"));
UpdateStatus("Installing ZeroTier");
Process.Start("ZeroTier.msi").WaitForExit();
}
catch
{
MessageBox.Show("Failed to download ZeroTier, please download it from official website");
MessageBox.Show("Failed to download ZeroTier, please download it from officail website");
Process.Start(url);
}
}
}
UpdateStatus("Completed!");
MessageBox.Show("Installation successful!");
MessageBox.Show("Installation sucessful!");
Environment.Exit(0);
}
}
@ -198,5 +203,11 @@ namespace RageCoop.Client.Installer
{
return Version.Parse(FileVersionInfo.GetVersionInfo(location).FileVersion);
}
private byte[] getLemon()
{
return (byte[])Resource.ResourceManager.GetObject("LemonUI_SHVDN3");
}
}
}
}

View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="bg.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RageCoop.Client\RageCoop.Client.csproj" />
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="bg.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -59,5 +59,15 @@ namespace RageCoop.Client.Installer {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] LemonUI_SHVDN3 {
get {
object obj = ResourceManager.GetObject("LemonUI_SHVDN3", resourceCulture);
return ((byte[])(obj));
}
}
}
}

View File

@ -118,4 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="LemonUI_SHVDN3" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\LemonUI.SHVDN3.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

50
RageCoop.Client/Debug.cs Normal file
View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
namespace RageCoop.Client
{
internal enum TimeStamp
{
AddPeds,
PedTotal,
AddVehicles,
VehicleTotal,
SendPed,
SendPedState,
SendVehicle,
SendVehicleState,
UpdatePed,
UpdateVehicle,
CheckProjectiles,
GetAllEntities,
Receive,
ProjectilesTotal,
}
internal static class Debug
{
public static Dictionary<TimeStamp, long> TimeStamps = new Dictionary<TimeStamp, long>();
private static int _lastNfHandle;
static Debug()
{
foreach (TimeStamp t in Enum.GetValues(typeof(TimeStamp)))
{
TimeStamps.Add(t, 0);
}
}
public static string Dump(this Dictionary<TimeStamp, long> d)
{
string s = "";
foreach (KeyValuePair<TimeStamp, long> kvp in d)
{
s += kvp.Key + ":" + kvp.Value + "\n";
}
return s;
}
public static void ShowTimeStamps()
{
GTA.UI.Notification.Hide(_lastNfHandle);
_lastNfHandle = GTA.UI.Notification.Show(Debug.TimeStamps.Dump());
}
}
}

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