25 Commits

Author SHA1 Message Date
85706518fe Update to SHVDN nightly (#70) 2024-06-30 16:17:35 +08:00
49208371d1 Allow keyboard layout change in chat 2023-12-12 11:52:20 -03:00
761156a574 Avoid vehicle door break and repair loop 2023-11-27 10:24:35 -03:00
8bd6ea15a3 Remove global limits
world limits will be applied to all players instead
Fix explosions when remote and local vehicles exist at same position
Fix "floating corpses" issue when remote NPCs are killed
2023-11-20 11:32:18 -03:00
3c7f16f7a4 Add ped limit menu setting
Fix vehicle limit menu setting
Fix player blip disappearing after switching character
2023-11-12 10:50:37 -03:00
393a401860 Always sync vehicles with synced peds 2023-11-07 20:27:14 -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
9287aba0f9 Update README.md 2023-07-26 15:42:08 -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
255 changed files with 13937 additions and 359311 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,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,168 +0,0 @@
using System;
using System.Drawing;
using GTA;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.Menus
{
internal static class SettingsMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
private static readonly NativeCheckboxItem _disableTrafficItem =
new("Disable Traffic (NPCs/Vehicles)", "Local traffic only",
Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem =
new("Flip menu", Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new("Disable Alternate Pause",
"Don't freeze game time when Esc pressed", Settings.DisableAlternatePause);
private static readonly NativeCheckboxItem _disableVoice = new("Enable voice",
"Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends",
Settings.Voice);
private static readonly NativeCheckboxItem _showBlip = new("Show player blip",
"Show other player's blip on map, can be overridden by server resource ",
Settings.ShowPlayerBlip);
private static readonly NativeCheckboxItem _showNametag = new("Show player nametag",
"Show other player's nametag on your screen, only effective if server didn't disable nametag display",
Settings.ShowPlayerNameTag);
private static readonly NativeItem _menuKey =
new("Menu Key", "The key to open menu", Settings.MenuKey.ToString());
private static readonly NativeItem _passengerKey = new("Passenger Key",
"The key to enter a vehicle as passenger", Settings.PassengerKey.ToString());
private static readonly NativeItem _vehicleSoftLimit = new("Vehicle limit (soft)",
"The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).",
Settings.WorldVehicleSoftLimit.ToString());
static SettingsMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
_disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged;
_disableVoice.CheckboxChanged += DisableVoiceCheckboxChanged;
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
_menuKey.Activated += ChaneMenuKey;
_passengerKey.Activated += ChangePassengerKey;
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
_showBlip.Activated += (s, e) =>
{
Settings.ShowPlayerBlip = _showBlip.Checked;
Util.SaveSettings();
};
_showNametag.Activated += (s, e) =>
{
API.Config.ShowPlayerNameTag = _showNametag.Checked;
};
Menu.Add(_disableTrafficItem);
Menu.Add(_disablePauseAlt);
Menu.Add(_flipMenuItem);
Menu.Add(_disableVoice);
Menu.Add(_menuKey);
Menu.Add(_passengerKey);
Menu.Add(_vehicleSoftLimit);
Menu.Add(_showBlip);
Menu.Add(_showNametag);
}
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
{
if (_disableVoice.Checked)
{
if (Networking.IsOnServer && !Voice.WasInitialized()) Voice.Init();
}
else
{
Voice.ClearAll();
}
Settings.Voice = _disableVoice.Checked;
Util.SaveSettings();
}
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
{
Settings.DisableAlternatePause = _disablePauseAlt.Checked;
Util.SaveSettings();
}
private static void VehicleSoftLimitActivated(object sender, EventArgs e)
{
try
{
Settings.WorldVehicleSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20,
Settings.WorldVehicleSoftLimit.ToString(), 20));
_menuKey.AltTitle = Settings.WorldVehicleSoftLimit.ToString();
Util.SaveSettings();
}
catch
{
}
}
private static void ChaneMenuKey(object sender, EventArgs e)
{
try
{
Settings.MenuKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Settings.MenuKey.ToString(), 20));
_menuKey.AltTitle = Settings.MenuKey.ToString();
Util.SaveSettings();
}
catch
{
}
}
private static void ChangePassengerKey(object sender, EventArgs e)
{
try
{
Settings.PassengerKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Settings.PassengerKey.ToString(), 20));
_passengerKey.AltTitle = Settings.PassengerKey.ToString();
Util.SaveSettings();
}
catch
{
}
}
public static void DisableTrafficCheckboxChanged(object a, EventArgs b)
{
WorldThread.Traffic(!_disableTrafficItem.Checked);
Settings.DisableTraffic = _disableTrafficItem.Checked;
Util.SaveSettings();
}
public static void FlipMenuCheckboxChanged(object a, EventArgs b)
{
CoopMenu.Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
Settings.FlipMenu = _flipMenuItem.Checked;
Util.SaveSettings();
}
}
}

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,340 +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 (SteeringAngle != MainVehicle.SteeringAngle)
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * SteeringAngle);
MainVehicle.ThrottlePower = ThrottlePower;
MainVehicle.BrakePower = 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 (!Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
}
else if (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 (LandingGear != (byte)MainVehicle.LandingGearState)
MainVehicle.LandingGearState = (VehicleLandingGearState)LandingGear;
}
else
{
if (MainVehicle.HasSiren && SireneActive != MainVehicle.IsSirenActive)
MainVehicle.IsSirenActive = SireneActive;
if (HornActive)
{
if (!_lastHornActive)
{
_lastHornActive = true;
MainVehicle.SoundHorn(99999);
}
}
else if (_lastHornActive)
{
_lastHornActive = false;
MainVehicle.SoundHorn(1);
}
if (HasRoof && MainVehicle.RoofState != RoofState) MainVehicle.RoofState = RoofState;
if (HasRocketBoost && Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
MainVehicle.IsRocketBoostActive)
MainVehicle.IsRocketBoostActive = Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive);
if (HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
!MainVehicle.IsParachuteDeployed)
MainVehicle.StartParachuting(false);
if (IsSubmarineCar)
{
if (Transformed)
{
if (!_lastTransformed)
{
_lastTransformed = true;
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
}
}
else if (_lastTransformed)
{
_lastTransformed = false;
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
}
}
else if (IsDeluxo)
{
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
}
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
}
MainVehicle.LockStatus = LockStatus;
if (LastFullSynced >= LastUpdated)
{
if (Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
if (Colors != _lastVehicleColors)
{
Call(SET_VEHICLE_COLOURS, MainVehicle, Colors.Item1, Colors.Item2);
_lastVehicleColors = Colors;
}
MainVehicle.EngineHealth = EngineHealth;
if (Mods != null && !Mods.SequenceEqual(_lastVehicleMods))
{
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (var mod in Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
_lastVehicleMods = Mods;
}
if (ToggleModsMask != _lastToggleMods)
{
for (int i = 0; i < 7; i++)
{
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (ToggleModsMask & (1 << i)) != 0);
}
_lastToggleMods = ToggleModsMask;
}
if (Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
if (_lastLivery != Livery)
{
Call(SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery = Livery;
}
if (_lastHeadlightColor != HeadlightColor)
{
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, HeadlightColor);
_lastHeadlightColor = HeadlightColor;
}
MainVehicle.SetDamageModel(DamageModel);
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != RadioStation)
Util.SetPlayerRadioIndex(MainVehicle.Handle, RadioStation);
if (_lastExtras != 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 = (ExtrasMask & flag) != 0;
Call(SET_VEHICLE_EXTRA, MainVehicle.Handle, i, !on);
}
_lastExtras = ExtrasMask;
}
}
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 = 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,228 +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 (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();
}
}
}
}

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,304 +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));
}
// Struct in GTA.Math have pack/padding for memory alignment, we don't want it to waste the bandwidth
public void Write(ref Vector2 vec2)
{
var faddr = Alloc<float>(2);
faddr[0] = vec2.X;
faddr[1] = vec2.Y;
}
public void Write(ref Vector3 vec3)
{
var faddr = Alloc<float>(3);
faddr[0] = vec3.X;
faddr[1] = vec3.Y;
faddr[2] = vec3.Z;
}
public void Write(ref Quaternion quat)
{
var faddr = Alloc<float>(4);
faddr[0] = quat.X;
faddr[1] = quat.Y;
faddr[2] = quat.Z;
faddr[3] = quat.W;
}
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);
}
public void Read(out Vector2 vec)
{
var faddr = Alloc<float>(2);
vec = new()
{
X = faddr[0],
Y = faddr[1],
};
}
public void Read(out Vector3 vec)
{
var faddr = Alloc<float>(3);
vec = new()
{
X = faddr[0],
Y = faddr[1],
Z = faddr[2],
};
}
public void Read(out Quaternion quat)
{
var faddr = Alloc<float>(4);
quat = new()
{
X = faddr[0],
Y = faddr[1],
Z = faddr[2],
W = faddr[3],
};
}
/// <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,968 +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
{
public 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
{
public 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]
public 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,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,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,310 +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);
b.Write(ref value);
break;
case Vector3 value:
b.WriteVal(T_VEC3);
b.Write(ref value);
break;
case Quaternion 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 Vector3 vec);
Args[i] = vec;
break;
case T_QUAT:
r.Read(out Quaternion quat);
Args[i] = quat;
break;
case T_MODEL:
Args[i] = r.ReadVal<Model>();
break;
case T_VEC2:
r.Read(out Vector2 vec2);
Args[i] = 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,116 +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, "CoreScripts");
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 ScriptHookVDotNetCore first!");
MessageBox.Show("Please install ScriptHookVDotNet first!");
Environment.Exit(1);
}
var shvdnVer = GetVer(shvdnPath);
if (shvdnVer < new Version(1, 2, 1))
if (shvdnVer < new Version(3, 6, 0))
{
MessageBox.Show("Please update ScriptHookVDotNetCore to latest version!" +
$"\nCurrent version is {shvdnVer}, 1.2.1 or higher is required");
MessageBox.Show("Please update ScriptHookVDotNet to latest version!" +
$"\nCurrent version is {shvdnVer}, 3.6.0 or higher is required");
Environment.Exit(1);
}
if (File.Exists(lemonPath))
{
var lemonVer = GetVer(lemonPath);
if (lemonVer < new Version(1, 7))
{
UpdateStatus("Updating LemonUI");
File.WriteAllBytes(lemonPath, getLemon());
}
}
UpdateStatus("Removing old versions");
if (Directory.Exists(scriptsPath))
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
File.Delete(f);
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"))
if (File.Exists("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll"))
{
UpdateStatus("Installing...");
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()),
new DirectoryInfo(installPath));
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(installPath, "Data", "Setting.json");
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");
@ -160,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);
}
}
@ -196,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.PostTicker(TimeStamps.Dump(), false).Handle;
}
}
}

View File

@ -0,0 +1,179 @@
using GTA;
using GTA.Math;
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace RageCoop.Client
{
internal class DevTool : Script
{
public static Vehicle ToMark;
public static bool UseSecondary = false;
public static int Current = 0;
public static int Secondary = 0;
public static MuzzleDir Direction = MuzzleDir.Forward;
public DevTool()
{
Tick += OnTick;
KeyDown += OnKeyDown;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (ToMark == null || (!ToMark.Exists())) { return; }
if (DevToolMenu.Menu.SelectedItem == DevToolMenu.boneIndexItem)
{
switch (e.KeyCode)
{
case Keys.Right:
Current++;
break;
case Keys.Left:
Current--;
break;
}
}
else if (DevToolMenu.Menu.SelectedItem == DevToolMenu.secondaryBoneIndexItem)
{
switch (e.KeyCode)
{
case Keys.Right:
Secondary++;
break;
case Keys.Left:
Secondary--;
break;
}
}
Update();
}
private static void Update()
{
if (Current > ToMark.Bones.Count - 1)
{
Current = 0;
}
else if (Current < 0)
{
Current = ToMark.Bones.Count - 1;
}
DevToolMenu.boneIndexItem.AltTitle = Current.ToString();
if (Secondary > ToMark.Bones.Count - 1)
{
Secondary = 0;
}
else if (Secondary < 0)
{
Secondary = ToMark.Bones.Count - 1;
}
DevToolMenu.secondaryBoneIndexItem.AltTitle = Secondary.ToString();
}
private static void OnTick(object sender, EventArgs e)
{
if (ToMark == null || !ToMark.Exists()) { return; }
Update();
Draw(Current);
if (UseSecondary)
{
Draw(Secondary);
}
}
private static void Draw(int boneindex)
{
var bone = ToMark.Bones[boneindex];
World.DrawLine(bone.Position, bone.Position + 2 * bone.ForwardVector, Color.Blue);
World.DrawLine(bone.Position, bone.Position + 2 * bone.UpVector, Color.Green);
World.DrawLine(bone.Position, bone.Position + 2 * bone.RightVector, Color.Yellow);
Vector3 todraw = bone.ForwardVector;
switch ((byte)Direction)
{
case 0:
todraw = bone.ForwardVector;
break;
case 1:
todraw = bone.RightVector;
break;
case 2:
todraw = bone.UpVector;
break;
case 3:
todraw = bone.ForwardVector * -1;
break;
case 4:
todraw = bone.RightVector * -1;
break;
case 5:
todraw = bone.UpVector * -1;
break;
}
World.DrawLine(bone.Position, bone.Position + 10 * todraw, Color.Red);
}
public static void CopyToClipboard(MuzzleDir dir)
{
if (ToMark != null)
{
string s;
if (UseSecondary)
{
if ((byte)dir < 3)
{
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
return BulletsShot%2==0 ? {Current} : {Secondary};
";
}
else
{
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
return BulletsShot%2==0 ? {Current} : {Secondary};
";
}
}
else
{
if ((byte)dir < 3)
{
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
return {Current};
";
}
else
{
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
return {Current};
";
}
}
Thread thread = new Thread(() => Clipboard.SetText(s));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
GTA.UI.Notification.PostTicker("Copied to clipboard, please paste it on the GitHub issue page!", false);
}
}
}
internal enum MuzzleDir : byte
{
Forward = 0,
Right = 1,
Up = 2,
Backward = 3,
Left = 4,
Down = 5,
}
}

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura />
<Costura ExcludeAssemblies="RageCoop.Core"/>
</Weavers>

408
RageCoop.Client/Main.cs Normal file
View File

@ -0,0 +1,408 @@
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Client.Menus;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
internal class Main : Script
{
private bool _gameLoaded = false;
internal static Version Version = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0;
internal static RelationshipGroup SyncedPedsGroup;
internal static new Settings Settings = null;
internal static Scripting.BaseScript BaseScript = new Scripting.BaseScript();
#if !NON_INTERACTIVE
#endif
internal static Chat MainChat = null;
internal static Stopwatch Counter = new Stopwatch();
internal static Logger Logger = null;
internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition;
internal static Scripting.Resources Resources = null;
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
public static Worker Worker;
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
try
{
Settings = Util.ReadSettings();
}
catch
{
GTA.UI.Notification.PostTicker("Malformed configuration, overwriting with default values...", false);
Settings = new Settings();
Util.SaveSettings();
}
Directory.CreateDirectory(Settings.DataDirectory);
Logger = new Logger()
{
LogPath = $"{Settings.DataDirectory}\\RageCoop.Client.log",
UseConsole = false,
#if DEBUG
LogLevel = 0,
#else
LogLevel=Settings.LogLevel,
#endif
};
Resources = new Scripting.Resources();
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{
Tick += (object sender, EventArgs e) =>
{
if (Game.IsLoading)
{
return;
}
if (!_gameLoaded)
{
GTA.UI.Notification.PostTicker("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
_gameLoaded = true;
}
};
return;
}
BaseScript.OnStart();
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral, true);
#if !NON_INTERACTIVE
#endif
MainChat = new Chat();
Tick += OnTick;
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
KeyDown += OnKeyDown;
KeyDown += (s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
KeyUp += (s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
Aborted += (object sender, EventArgs e) => Disconnected("Abort");
Util.NativeMemory();
Counter.Restart();
}
public static Ped P;
public static float FPS;
private bool _lastDead;
private void OnTick(object sender, EventArgs e)
{
P = Game.Player.Character;
PlayerPosition = P.ReadPosition();
FPS = Game.FPS;
if (Game.IsLoading)
{
return;
}
else if (!_gameLoaded && (_gameLoaded = true))
{
#if !NON_INTERACTIVE
GTA.UI.Notification.PostMessageText($"Press ~g~{Settings.MenuKey}~s~ to open the menu.", new GTA.Graphics.TextureAsset("CHAR_ALL_PLAYERS_CONF", "CHAR_ALL_PLAYERS_CONF"), false, GTA.UI.FeedTextIcon.Message, "RAGECOOP", "Welcome!");
#endif
}
#if !NON_INTERACTIVE
CoopMenu.MenuPool.Process();
#endif
DoQueuedActions();
if (!Networking.IsOnServer)
{
return;
}
if (Game.TimeScale != 1)
{
Game.TimeScale = 1;
}
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
#if DEBUG
Main.Logger.Error(ex);
#endif
}
if (Networking.ShowNetworkInfo)
{
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0), $"L: {Networking.Latency * 1000:N0}ms", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
}
MainChat.Tick();
PlayerList.Tick();
if (!Scripting.API.Config.EnableAutoRespawn)
{
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
Function.Call(Hash.TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
if (P.IsDead)
{
Function.Call(Hash.SET_FADE_OUT_AFTER_DEATH, false);
if (P.Health != 1)
{
P.Health = 1;
Game.Player.WantedLevel = 0;
Main.Logger.Debug("Player died.");
Scripting.API.Events.InvokePlayerDied(KillMessage());
}
GTA.UI.Screen.StopEffects();
}
else
{
Function.Call(Hash.DISPLAY_HUD, true);
}
}
else if (P.IsDead && !_lastDead)
{
Scripting.API.Events.InvokePlayerDied(KillMessage());
}
_lastDead = P.IsDead;
Ticked++;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (MainChat.Focused)
{
MainChat.OnKeyDown(e.KeyCode);
return;
}
if (Networking.IsOnServer)
{
if (Voice.WasInitialized())
{
if (Game.IsControlPressed(GTA.Control.PushToTalk))
{
Voice.StartRecording();
return;
}
else if (Voice.IsRecording())
{
Voice.StopRecording();
return;
}
}
if (Game.IsControlPressed(GTA.Control.FrontendPause))
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
if (Game.IsControlPressed(GTA.Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
}
if (e.KeyCode == Settings.MenuKey)
{
if (CoopMenu.MenuPool.AreAnyVisible)
{
CoopMenu.MenuPool.ForEach<LemonUI.Menus.NativeMenu>(x =>
{
if (x.Visible)
{
CoopMenu.LastMenu = x;
x.Visible = false;
}
});
}
else
{
CoopMenu.LastMenu.Visible = true;
}
}
else if (Game.IsControlJustPressed(GTA.Control.MpTextChatAll))
{
if (Networking.IsOnServer)
{
MainChat.Focused = true;
}
}
else if (MainChat.Focused) { return; }
else if (Game.IsControlJustPressed(GTA.Control.MultiplayerInfo))
{
if (Networking.IsOnServer)
{
ulong currentTimestamp = Util.GetTickCount64();
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp;
}
}
else if (e.KeyCode == Settings.PassengerKey)
{
var P = Game.Player.Character;
if (!P.IsInVehicle())
{
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
{
P.Task.ClearAll();
}
else
{
var V = World.GetClosestVehicle(P.ReadPosition(), 50);
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();
}
QueueAction(() =>
{
WorldThread.Traffic(!Settings.DisableTraffic);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
CoopMenu.ConnectedMenuSetting();
MainChat.Init();
GTA.UI.Notification.PostTicker("~g~Connected!", false);
});
Logger.Info(">> Connected <<");
}
public static void Disconnected(string reason)
{
Logger.Info($">> Disconnected << reason: {reason}");
QueueAction(() =>
{
if (MainChat.Focused)
{
MainChat.Focused = false;
}
PlayerList.Cleanup();
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
if (reason != "Abort")
GTA.UI.Notification.PostTicker("~r~Disconnected: " + reason, false);
LocalPlayerID = default;
});
Memory.RestorePatches();
DownloadManager.Cleanup();
Voice.ClearAll();
Resources.Unload();
}
private static void DoQueuedActions()
{
lock (QueuedActions)
{
foreach (var action in QueuedActions.ToArray())
{
try
{
if (action())
{
QueuedActions.Remove(action);
}
}
catch (Exception ex)
{
#if DEBUG
Logger.Error(ex);
#endif
QueuedActions.Remove(action);
}
}
}
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
internal static void QueueAction(Func<bool> a)
{
lock (QueuedActions)
{
QueuedActions.Add(a);
}
}
internal static void QueueAction(Action a)
{
lock (QueuedActions)
{
QueuedActions.Add(() => { a(); return true; });
}
}
/// <summary>
/// Clears all queued actions
/// </summary>
internal static void ClearQueuedActions()
{
lock (QueuedActions) { QueuedActions.Clear(); }
}
public static void Delay(Action a, int time)
{
Task.Run(() =>
{
Thread.Sleep(time);
QueueAction(a);
});
}
private string KillMessage()
{
if (P.Killer != null)
{
var killer = EntityPool.GetPedByHandle(P.Killer.Handle);
if (killer != null && killer.ID == killer.Owner.ID)
return $"~h~{PlayerList.GetPlayer(LocalPlayerID).Username}~h~ was killed by ~h~{killer.Owner.Username}~h~ ({P.CauseOfDeath})";
}
return $"~h~{PlayerList.GetPlayer(LocalPlayerID).Username}~h~ died";
}
}
}

View File

@ -1,53 +1,61 @@
using System;
using System.Drawing;
using GTA;
using GTA;
using GTA.Native;
using GTA.UI;
using LemonUI;
using LemonUI.Elements;
using LemonUI.Menus;
using LemonUI.Scaleform;
using System.Drawing;
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// Don't use it!
/// </summary>
internal static class CoopMenu
{
public static ObjectPool MenuPool = new ObjectPool();
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "MAIN")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static PopUp PopUp = new PopUp
public static PopUp PopUp = new PopUp()
{
Title = "",
Prompt = "",
Subtitle = "",
Error = "",
ShowBackground = true,
Visible = false
Visible = false,
};
public static NativeMenu LastMenu { get; set; } = Menu;
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*', Main.Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.Version)
{ LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
#endregion
/// <summary>
/// Don't use it!
/// Don't use it!
/// </summary>
static CoopMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated;
_passwordItem.Activated += _passwordActivated;
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) =>
{
Networking.ToggleConnection(Settings.LastServerAddress);
};
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
Menu.AddSubMenu(ServersMenu.Menu);
@ -59,22 +67,23 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(SettingsMenu.Menu);
Menu.AddSubMenu(DevToolMenu.Menu);
#if DEBUG
Menu.AddSubMenu(DebugMenu.Menu);
#endif
MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu);
MenuPool.Add(DevToolMenu.Menu);
#if DEBUG
MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu);
MenuPool.Add(DebugMenu.TuneMenu);
#endif
MenuPool.Add(ServersMenu.Menu);
MenuPool.Add(PopUp);
Menu.Add(_aboutItem);
}
public static NativeMenu LastMenu { get; set; } = Menu;
public static bool ShowPopUp(string prompt, string title, string subtitle, string error, bool showbackground)
{
@ -90,15 +99,13 @@ namespace RageCoop.Client.Menus
Game.DisableAllControlsThisFrame();
MenuPool.Process();
var scaleform = new Scaleform("instructional_buttons");
var scaleform = Scaleform.RequestMovie("instructional_buttons");
scaleform.CallFunction("CLEAR_ALL");
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
scaleform.CallFunction("CREATE_CONTAINER");
scaleform.CallFunction("SET_DATA_SLOT", 0,
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
scaleform.CallFunction("SET_DATA_SLOT", 1,
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("SET_DATA_SLOT", 0, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
scaleform.CallFunction("SET_DATA_SLOT", 1, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
scaleform.Render2D();
if (Game.IsControlJustPressed(Control.FrontendAccept))
@ -106,44 +113,41 @@ namespace RageCoop.Client.Menus
PopUp.Visible = false;
return true;
}
if (Game.IsControlJustPressed(Control.FrontendCancel))
else if (Game.IsControlJustPressed(Control.FrontendCancel))
{
PopUp.Visible = false;
return false;
}
Script.Yield();
Game.DisableAllControlsThisFrame();
}
}
public static void UsernameActivated(object a, EventArgs b)
public static void UsernameActivated(object a, System.EventArgs b)
{
var newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
if (!string.IsNullOrWhiteSpace(newUsername))
{
Settings.Username = newUsername;
Main.Settings.Username = newUsername;
Util.SaveSettings();
_usernameItem.AltTitle = newUsername;
}
}
private static void _passwordActivated(object sender, EventArgs e)
private static void _passwordActivated(object sender, System.EventArgs e)
{
var newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
Settings.Password = newPass;
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
public static void ServerIpActivated(object a, EventArgs b)
public static void ServerIpActivated(object a, System.EventArgs b)
{
var newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
{
Settings.LastServerAddress = newServerIp;
Main.Settings.LastServerAddress = newServerIp;
Util.SaveSettings();
ServerIpItem.AltTitle = newServerIp;
@ -171,26 +175,5 @@ namespace RageCoop.Client.Menus
_serverConnectItem.Enabled = true;
_serverConnectItem.Title = "Connect";
}
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username")
{ AltTitle = Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password")
{ AltTitle = new string('*', Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP")
{ AltTitle = Settings.LastServerAddress };
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.ModVersion)
{ LeftBadge = new ScaledTexture("commonmenu", "shop_new_star") };
#endregion
}
}
}

View File

@ -0,0 +1,60 @@
#if DEBUG
using GTA;
using LemonUI.Menus;
using System;
using System.Drawing;
namespace RageCoop.Client
{
internal static class DebugMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static NativeMenu DiagnosticMenu = new NativeMenu("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0");
public static NativeCheckboxItem ShowOwnerItem = new NativeCheckboxItem("Show entity owner", "Show the owner name of the entity you're aiming at", false);
private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
static DebugMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0);
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()));
}
};
SimulatedLatencyItem.Activated += (s, e) =>
{
try
{
SimulatedLatencyItem.AltTitle = ((Networking.SimulatedLatency = int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
}
catch (Exception ex) { Main.Logger.Error(ex); }
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
Menu.Add(SimulatedLatencyItem);
Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.AddSubMenu(DiagnosticMenu);
}
}
}
#endif

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