Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4fbdd86566 | |||
| 826e80c5d8 | |||
| 3944d41e85 | |||
| 114e59a3bd | |||
| db7146e00e | |||
| e6718cec58 | |||
| 6f2c0077cb | |||
| 5f1aadbdb5 | |||
| 7cd0fd22af | |||
| e6c6e5ceff | |||
| bda1dac90b | |||
| 0bc89a9bf3 | |||
| 7e019cc112 | |||
| 713e005975 | |||
| 0cb1098819 | |||
| ba8c2c741d | |||
| feb20c47f4 | |||
| 7f4aa47943 | |||
| 525bd495b4 | |||
| cd70256315 | |||
| 920b5d15ba | |||
| fd4b5f8afb | |||
| bdcdf75a8e | |||
| f100261e88 | |||
| cea49dae2b | |||
| f44715cdf4 | |||
| 57c9c49757 | |||
| df9efb6210 | |||
| cc92d1ad2b | |||
| 63f11053c7 | |||
| 11c7ee19a3 | |||
| 211ab4ee7c | |||
| 83f7cbc963 | |||
| 2451131e36 | |||
| 0e5271b322 | |||
| 6e2b4aff2f | |||
| e5f426690f | |||
| e4f432b593 | |||
| f555fef48d | |||
| ac07edfe68 | |||
| fb654f0e56 | |||
| 718814c491 | |||
| b4f86719ce | |||
| 6792559b5b | |||
| ffb31b0986 | |||
| f1b9bf0571 | |||
| d4df041c44 | |||
| a7ea28281e | |||
| 6a1ebcf9fe | |||
| a79f9f2a69 | |||
| 798776c822 | |||
| 9cb7b28d8f | |||
| cac2385c35 | |||
| 0112140f0e | |||
| 174f711e42 | |||
| 3a9068f060 | |||
| 4cc5054b91 | |||
| a9b05c62ff | |||
| 952ab4f7b3 | |||
| 598790dedd | |||
| 979be4cfa8 | |||
| 7dec978e9a | |||
| 78f0dfaebb | |||
| e6c8d4df46 | |||
| 279492e0bd | |||
| 478305112f | |||
| 745b212b42 | |||
| 50153d860a | |||
| 822a69573f | |||
| 23e4151b2e | |||
| 71195e93c2 | |||
| 8961eb102b | |||
| 2828b9b74f | |||
| 6b34ab6e36 | |||
| 69419d41e0 | |||
| a50ae062d8 | |||
| 9e0ce58bd2 | |||
| 806f1d194a | |||
| 4e6bab1b53 | |||
| 64692bc161 | |||
| 8d27c072ca | |||
| d7c0abdfc2 | |||
| d0b6bbaa3a | |||
| 411b199a98 | |||
| b48b15b652 | |||
| e83480b7f9 | |||
| d1b4f23992 | |||
| 42c0ef2159 | |||
| 8701ac703e | |||
| fe53e01a4a | |||
| 617dbc9812 | |||
| 1606d25fed | |||
| 5585876005 | |||
| 54c7f4ce92 | |||
| a062322dda | |||
| 4599c558e4 | |||
| 71f7f4b257 |
@@ -0,0 +1,4 @@
|
||||
[*.cs]
|
||||
|
||||
# CS0649: Field 'VehicleInfo.Name' is never assigned to, and will always have its default value null
|
||||
dotnet_diagnostic.CS0649.severity = silent
|
||||
@@ -2,9 +2,7 @@ name: Build test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*' # matches every branch that doesn't contain a '/'
|
||||
- '*/*' # matches every branch containing a single '/'
|
||||
branches:
|
||||
- '**' # matches every branch
|
||||
- '!main' # excludes main
|
||||
- '!dev-nightly' # excludes nightly
|
||||
@@ -18,7 +16,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dotnet-version: ['6.0.x']
|
||||
dotnet-version: ['7.0.x']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -26,23 +24,30 @@ 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 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: Build
|
||||
run: .\build.cmd
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dotnet-version: ['6.0.x']
|
||||
dotnet-version: ['7.0.x']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -18,81 +18,72 @@ 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 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
|
||||
- name: Build server linux-arm
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o bin/Release/Server/linux-arm -c Release
|
||||
|
||||
- name: Build artifacts
|
||||
run: .\build.cmd
|
||||
|
||||
- name: Build server for different platforms
|
||||
run: .\publish-server.cmd
|
||||
|
||||
- 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: RageCoop.Server-win-x64.zip
|
||||
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
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/linux-x64
|
||||
dest: RageCoop.Server-linux-x64.zip
|
||||
dest: bin/Artifacts/RageCoop.Server-linux-x64.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/linux-arm
|
||||
dest: RageCoop.Server-linux-arm.zip
|
||||
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
|
||||
|
||||
- uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
- 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
|
||||
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.Client.zip
|
||||
asset_name: RageCoop.Client.zip
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/GTAV-RESOURCES/releases/75463254/assets{?name,label}
|
||||
release_id: 75463254
|
||||
asset_path: bin/Artifacts
|
||||
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-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
|
||||
|
||||
|
||||
- uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
|
||||
release_id: 70603992
|
||||
asset_path: RageCoop.Server-linux-arm.zip
|
||||
asset_name: RageCoop.Server-linux-arm.zip
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[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
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,314 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
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)
|
||||
{
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("RageCoop.Client.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")]
|
||||
@@ -0,0 +1,118 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
+335214
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
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
@@ -1,9 +1,8 @@
|
||||
<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>
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)]
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura />
|
||||
<Costura />
|
||||
</Weavers>
|
||||
@@ -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>
|
||||
+54
-65
@@ -6,17 +6,16 @@ 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
|
||||
{
|
||||
@@ -28,13 +27,12 @@ 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() ?? false == true)
|
||||
{
|
||||
if (od.ShowDialog() ?? !true)
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
@@ -43,124 +41,118 @@ namespace RageCoop.Client.Installer
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Installation failed: " + ex.ToString());
|
||||
MessageBox.Show("Installation failed: " + ex);
|
||||
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, "ScriptHookVDotNet3.dll");
|
||||
var shvdnPath = Path.Combine(root, "ScriptHookVDotNetCore.dll");
|
||||
var scriptsPath = Path.Combine(root, "Scripts");
|
||||
var lemonPath = Path.Combine(scriptsPath, "LemonUI.SHVDN3.dll");
|
||||
var installPath = Path.Combine(scriptsPath, "RageCoop");
|
||||
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName == installPath)
|
||||
{
|
||||
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);
|
||||
var installPath = Path.Combine(root, "RageCoop");
|
||||
var legacyPath = Path.Combine(scriptsPath, "RageCoop");
|
||||
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName.StartsWith(installPath))
|
||||
throw new InvalidOperationException(
|
||||
"The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
|
||||
if (!File.Exists(shvPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookV first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(installPath);
|
||||
|
||||
if (!File.Exists(shvdnPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookVDotNet first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
var shvdnVer = GetVer(shvdnPath);
|
||||
if (shvdnVer < new Version(3, 5, 1))
|
||||
{
|
||||
MessageBox.Show("Please update ScriptHookVDotNet to latest version!" +
|
||||
$"\nCurrent version is {shvdnVer}, 3.5.1 or higher is required");
|
||||
$"\nCurrent version is {shvdnVer}, 3.5.1 or higher is required");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
if (File.Exists(lemonPath))
|
||||
{
|
||||
var lemonVer = GetVer(lemonPath);
|
||||
if (lemonVer < new Version(1, 7))
|
||||
{
|
||||
UpdateStatus("Updating LemonUI");
|
||||
File.WriteAllBytes(lemonPath, getLemon());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UpdateStatus("Removing old versions");
|
||||
|
||||
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (f.EndsWith("RageCoop.Client.Settings.xml")) { continue; }
|
||||
File.Delete(f);
|
||||
}
|
||||
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(f);
|
||||
}
|
||||
|
||||
if (File.Exists("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll"))
|
||||
// <= 1.5 installation check
|
||||
if (Directory.Exists(legacyPath)) Directory.Delete(legacyPath, true);
|
||||
|
||||
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories)) File.Delete(f);
|
||||
|
||||
if (File.Exists("Scripts/RageCoop.Core.dll") && File.Exists("Scripts/RageCoop.Client.dll") &&
|
||||
File.Exists("Loader/RageCoop.Client.Loader.dll"))
|
||||
{
|
||||
UpdateStatus("Installing...");
|
||||
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath));
|
||||
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()),
|
||||
new DirectoryInfo(installPath));
|
||||
File.Copy("Loader/RageCoop.Client.Loader.dll", Path.Combine(scriptsPath, "RageCoop.Client.Loader.dll"),
|
||||
true);
|
||||
Finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Required files are missing, please re-download the zip from official website");
|
||||
throw new Exception(
|
||||
"Required files are missing, please re-download the installer from official website");
|
||||
}
|
||||
|
||||
void Finish()
|
||||
{
|
||||
|
||||
checkKeys:
|
||||
checkKeys:
|
||||
UpdateStatus("Checking conflicts");
|
||||
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
|
||||
var settingsPath = Path.Combine(installPath, @"Data\RageCoop.Client.Settings.xml");
|
||||
Settings settings = null;
|
||||
var settingsPath = Path.Combine(root, SettingsPath);
|
||||
ClientSettings settings = null;
|
||||
try
|
||||
{
|
||||
settings = Util.ReadSettings(settingsPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
settings = new Settings();
|
||||
settings = new();
|
||||
}
|
||||
|
||||
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 = (Keys)KeyInterop.VirtualKeyFromKey(e.Key);
|
||||
ae.Set();
|
||||
}));
|
||||
KeyDown += (s, e) =>
|
||||
{
|
||||
settings.MenuKey = (GTA.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");
|
||||
@@ -170,26 +162,29 @@ 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 officail website");
|
||||
MessageBox.Show("Failed to download ZeroTier, please download it from official website");
|
||||
Process.Start(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatus("Completed!");
|
||||
MessageBox.Show("Installation sucessful!");
|
||||
MessageBox.Show("Installation successful!");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
@@ -203,11 +198,5 @@ namespace RageCoop.Client.Installer
|
||||
{
|
||||
return Version.Parse(FileVersionInfo.GetVersionInfo(location).FileVersion);
|
||||
}
|
||||
|
||||
private byte[] getLemon()
|
||||
{
|
||||
return (byte[])Resource.ResourceManager.GetObject("LemonUI_SHVDN3");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<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>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
-10
@@ -59,15 +59,5 @@ namespace RageCoop.Client.Installer {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] LemonUI_SHVDN3 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("LemonUI_SHVDN3", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,4 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="LemonUI_SHVDN3" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Resources\LemonUI.SHVDN3.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
||||
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("RageCoop.Client.Loader")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client.Loader")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("fc8cbdbb-6dc3-43af-b34d-092e476410a5")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>RageCoop.Client.Loader</RootNamespace>
|
||||
<AssemblyName>RageCoop.Client.Loader</AssemblyName>
|
||||
<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>
|
||||
@@ -0,0 +1,47 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<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>
|
||||
@@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,92 @@
|
||||
#undef DEBUG
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public class Settings
|
||||
public class ClientSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// LogLevel for RageCoop.
|
||||
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
|
||||
/// </summary>
|
||||
public int LogLevel = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public string Username { get; set; } = "Player";
|
||||
|
||||
/// <summary>
|
||||
/// The password used to authenticate when connecting to a server.
|
||||
/// The password used to authenticate when connecting to a server.
|
||||
/// </summary>
|
||||
public string Password { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public string LastServerAddress { get; set; } = "127.0.0.1:4499";
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public string MasterServer { get; set; } = "https://masterserver.ragecoop.online/";
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public bool FlipMenu { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public bool Voice { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// LogLevel for RageCoop.
|
||||
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
|
||||
/// The key to open menu
|
||||
/// </summary>
|
||||
public int LogLevel = 00;
|
||||
public Keys MenuKey { get; set; } = Keys.F7;
|
||||
|
||||
/// <summary>
|
||||
/// The key to open menu
|
||||
/// </summary>
|
||||
public Keys MenuKey { get; set; } = Keys.F9;
|
||||
|
||||
/// <summary>
|
||||
/// The key to enter a vehicle as passenger.
|
||||
/// The key to enter a vehicle as passenger.
|
||||
/// </summary>
|
||||
public Keys PassengerKey { get; set; } = Keys.G;
|
||||
|
||||
/// <summary>
|
||||
/// Disable world NPC traffic, mission entities won't be affected
|
||||
/// Disable world NPC traffic, mission entities won't be affected
|
||||
/// </summary>
|
||||
public bool DisableTraffic { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed.
|
||||
/// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed.
|
||||
/// </summary>
|
||||
public bool DisableAlternatePause { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
|
||||
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
|
||||
/// </summary>
|
||||
public int WorldVehicleSoftLimit { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
|
||||
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
|
||||
/// </summary>
|
||||
public int WorldPedSoftLimit { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// The directory where log and resources downloaded from server will be placed.
|
||||
/// </summary>
|
||||
public string DataDirectory { get; set; } = "Scripts\\RageCoop\\Data";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Show the owner name of the entity you're aiming at
|
||||
/// Show the owner name of the entity you're aiming at
|
||||
/// </summary>
|
||||
public bool ShowEntityOwnerName { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Show other player's nametag on your screen, only effective if server didn't disable nametag display
|
||||
/// </summary>
|
||||
public bool ShowPlayerNameTag { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show other player's blip on map, can be overridden by server resource
|
||||
/// </summary>
|
||||
public bool ShowPlayerBlip { get; set; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,53 @@
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
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 = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : 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);
|
||||
|
||||
_usernameItem.Activated += UsernameActivated;
|
||||
_passwordItem.Activated += _passwordActivated;
|
||||
ServerIpItem.Activated += ServerIpActivated;
|
||||
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
|
||||
_serverConnectItem.Activated += (sender, item) =>
|
||||
{
|
||||
Networking.ToggleConnection(Settings.LastServerAddress);
|
||||
};
|
||||
|
||||
|
||||
Menu.AddSubMenu(ServersMenu.Menu);
|
||||
@@ -68,20 +60,21 @@ namespace RageCoop.Client.Menus
|
||||
Menu.AddSubMenu(SettingsMenu.Menu);
|
||||
Menu.AddSubMenu(DevToolMenu.Menu);
|
||||
Menu.AddSubMenu(DebugMenu.Menu);
|
||||
Menu.AddSubMenu(UpdateMenu.Menu);
|
||||
|
||||
MenuPool.Add(Menu);
|
||||
MenuPool.Add(SettingsMenu.Menu);
|
||||
MenuPool.Add(DevToolMenu.Menu);
|
||||
MenuPool.Add(DebugMenu.Menu);
|
||||
MenuPool.Add(DebugMenu.DiagnosticMenu);
|
||||
MenuPool.Add(DebugMenu.TuneMenu);
|
||||
MenuPool.Add(ServersMenu.Menu);
|
||||
MenuPool.Add(UpdateMenu.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)
|
||||
{
|
||||
@@ -102,8 +95,10 @@ namespace RageCoop.Client.Menus
|
||||
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
|
||||
scaleform.CallFunction("CREATE_CONTAINER");
|
||||
|
||||
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("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("DRAW_INSTRUCTIONAL_BUTTONS", -1);
|
||||
scaleform.Render2D();
|
||||
if (Game.IsControlJustPressed(Control.FrontendAccept))
|
||||
@@ -111,41 +106,44 @@ namespace RageCoop.Client.Menus
|
||||
PopUp.Visible = false;
|
||||
return true;
|
||||
}
|
||||
else if (Game.IsControlJustPressed(Control.FrontendCancel))
|
||||
|
||||
if (Game.IsControlJustPressed(Control.FrontendCancel))
|
||||
{
|
||||
PopUp.Visible = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Script.Yield();
|
||||
Game.DisableAllControlsThisFrame();
|
||||
|
||||
}
|
||||
}
|
||||
public static void UsernameActivated(object a, System.EventArgs b)
|
||||
|
||||
public static void UsernameActivated(object a, EventArgs b)
|
||||
{
|
||||
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
|
||||
var newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
|
||||
if (!string.IsNullOrWhiteSpace(newUsername))
|
||||
{
|
||||
Main.Settings.Username = newUsername;
|
||||
Settings.Username = newUsername;
|
||||
Util.SaveSettings();
|
||||
|
||||
_usernameItem.AltTitle = newUsername;
|
||||
}
|
||||
}
|
||||
|
||||
private static void _passwordActivated(object sender, System.EventArgs e)
|
||||
private static void _passwordActivated(object sender, EventArgs e)
|
||||
{
|
||||
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
|
||||
Main.Settings.Password = newPass;
|
||||
var newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
|
||||
Settings.Password = newPass;
|
||||
Util.SaveSettings();
|
||||
_passwordItem.AltTitle = new string('*', newPass.Length);
|
||||
}
|
||||
public static void ServerIpActivated(object a, System.EventArgs b)
|
||||
|
||||
public static void ServerIpActivated(object a, EventArgs b)
|
||||
{
|
||||
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
|
||||
var newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
|
||||
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
|
||||
{
|
||||
Main.Settings.LastServerAddress = newServerIp;
|
||||
Settings.LastServerAddress = newServerIp;
|
||||
Util.SaveSettings();
|
||||
|
||||
ServerIpItem.AltTitle = newServerIp;
|
||||
@@ -173,5 +171,26 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-25
@@ -1,50 +1,49 @@
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
internal static class ServersMenu
|
||||
{
|
||||
private static Thread GetServersThread;
|
||||
|
||||
internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
internal static NativeItem ResultItem = null;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
static ServersMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) =>
|
||||
Menu.Opening += (object sender, CancelEventArgs e) =>
|
||||
{
|
||||
CleanUpList();
|
||||
Menu.Add(ResultItem = new NativeItem("Loading..."));
|
||||
|
||||
// Prevent freezing
|
||||
GetServersThread = new Thread(() => GetAllServers());
|
||||
GetServersThread.Start();
|
||||
};
|
||||
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
|
||||
{
|
||||
CleanUpList();
|
||||
GetServersThread = ThreadManager.CreateThread(() => GetAllServers(),"GetServers");
|
||||
};
|
||||
Menu.Closing += (object sender, CancelEventArgs e) => { CleanUpList(); };
|
||||
}
|
||||
|
||||
private static void CleanUpList()
|
||||
@@ -56,27 +55,34 @@ namespace RageCoop.Client.Menus
|
||||
private static void GetAllServers()
|
||||
{
|
||||
List<ServerInfo> serverList = null;
|
||||
var realUrl = Main.Settings.MasterServer;
|
||||
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
|
||||
var realUrl = Settings.MasterServer;
|
||||
serverList = null;
|
||||
try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
|
||||
catch (Exception ex) { Log.Error(ex); }
|
||||
|
||||
// Need to be processed in main thread
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (serverList == null)
|
||||
{
|
||||
ResultItem.Title = "Something went wrong!";
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverList.Count == 0)
|
||||
{
|
||||
ResultItem.Title = "No server was found!";
|
||||
return;
|
||||
}
|
||||
|
||||
CleanUpList();
|
||||
foreach (ServerInfo server in serverList)
|
||||
{
|
||||
string address = $"{server.address}:{server.port}";
|
||||
NativeItem tmpItem = new NativeItem($"[{server.country}] {server.name}", $"~b~{address}~s~~n~~g~Version {server.version}.x~s~") { AltTitle = $"[{server.players}/{server.maxPlayers}]" };
|
||||
NativeItem tmpItem =
|
||||
new NativeItem($"[{server.country}] {server.name}",
|
||||
$"~b~{address}~s~~n~~g~Version {server.version}.x~s~")
|
||||
{ AltTitle = $"[{server.players}/{server.maxPlayers}]" };
|
||||
tmpItem.Activated += (object sender, EventArgs e) =>
|
||||
{
|
||||
try
|
||||
@@ -91,13 +97,14 @@ namespace RageCoop.Client.Menus
|
||||
throw new Exception("Failed to obtain ZeroTier network IP");
|
||||
}
|
||||
}
|
||||
|
||||
Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server));
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.ServerIpItem.AltTitle = address;
|
||||
|
||||
CoopMenu.Menu.Visible = true;
|
||||
#endif
|
||||
Main.Settings.LastServerAddress = address;
|
||||
Settings.LastServerAddress = address;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -105,7 +112,8 @@ namespace RageCoop.Client.Menus
|
||||
Notification.Show($"~r~{ex.Message}");
|
||||
if (server.useZT)
|
||||
{
|
||||
Notification.Show($"Make sure ZeroTier is correctly installed, download it from https://www.zerotier.com/");
|
||||
Notification.Show(
|
||||
$"Make sure ZeroTier is correctly installed, download it from https://www.zerotier.com/");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -113,6 +121,7 @@ namespace RageCoop.Client.Menus
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static string DownloadString(string url)
|
||||
{
|
||||
try
|
||||
@@ -122,12 +131,12 @@ namespace RageCoop.Client.Menus
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
|
||||
|
||||
WebClient client = new WebClient();
|
||||
return client.DownloadString(url);
|
||||
var client = new HttpClient();
|
||||
return client.GetStringAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
ResultItem.Title = "Download failed!";
|
||||
ResultItem.Description = ex.Message;
|
||||
@@ -0,0 +1,168 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
@@ -11,18 +10,21 @@ namespace RageCoop.Client
|
||||
{
|
||||
private readonly Scaleform MainScaleForm;
|
||||
|
||||
public Chat()
|
||||
{
|
||||
MainScaleForm = new Scaleform("multiplayer_chat");
|
||||
}
|
||||
|
||||
public string CurrentInput { get; set; }
|
||||
|
||||
private bool CurrentFocused { get; set; }
|
||||
|
||||
public bool Focused
|
||||
{
|
||||
get => CurrentFocused;
|
||||
set
|
||||
{
|
||||
if (value && Hidden)
|
||||
{
|
||||
Hidden = false;
|
||||
}
|
||||
if (value && Hidden) Hidden = false;
|
||||
|
||||
MainScaleForm.CallFunction("SET_FOCUS", value ? 2 : 1, 2, "ALL");
|
||||
|
||||
@@ -33,6 +35,7 @@ namespace RageCoop.Client
|
||||
private ulong LastMessageTime { get; set; }
|
||||
|
||||
private bool CurrentHidden { get; set; }
|
||||
|
||||
private bool Hidden
|
||||
{
|
||||
get => CurrentHidden;
|
||||
@@ -40,10 +43,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (!CurrentHidden)
|
||||
{
|
||||
MainScaleForm.CallFunction("hide");
|
||||
}
|
||||
if (!CurrentHidden) MainScaleForm.CallFunction("hide");
|
||||
}
|
||||
else if (CurrentHidden)
|
||||
{
|
||||
@@ -54,11 +54,6 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
|
||||
public Chat()
|
||||
{
|
||||
MainScaleForm = new Scaleform("multiplayer_chat");
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL");
|
||||
@@ -72,22 +67,13 @@ namespace RageCoop.Client
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if ((Util.GetTickCount64() - LastMessageTime) > 15000 && !Focused && !Hidden)
|
||||
{
|
||||
Hidden = true;
|
||||
}
|
||||
if (Util.GetTickCount64() - LastMessageTime > 15000 && !Focused && !Hidden) Hidden = true;
|
||||
|
||||
if (!Hidden)
|
||||
{
|
||||
MainScaleForm.Render2D();
|
||||
}
|
||||
if (!Hidden) MainScaleForm.Render2D();
|
||||
|
||||
if (!CurrentFocused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!CurrentFocused) return;
|
||||
|
||||
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, 0);
|
||||
Call(DISABLE_ALL_CONTROL_ACTIONS, 0);
|
||||
}
|
||||
|
||||
public void AddMessage(string sender, string msg)
|
||||
@@ -107,20 +93,12 @@ namespace RageCoop.Client
|
||||
}
|
||||
|
||||
if (key == Keys.PageUp)
|
||||
{
|
||||
MainScaleForm.CallFunction("PAGE_UP");
|
||||
}
|
||||
else if (key == Keys.PageDown)
|
||||
{
|
||||
MainScaleForm.CallFunction("PAGE_DOWN");
|
||||
}
|
||||
else if (key == Keys.PageDown) MainScaleForm.CallFunction("PAGE_DOWN");
|
||||
|
||||
string keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
|
||||
var keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
|
||||
|
||||
if (keyChar.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (keyChar.Length == 0) return;
|
||||
|
||||
switch (keyChar[0])
|
||||
{
|
||||
@@ -130,14 +108,12 @@ namespace RageCoop.Client
|
||||
CurrentInput = CurrentInput.Remove(CurrentInput.Length - 1);
|
||||
MainScaleForm.CallFunction("DELETE_TEXT");
|
||||
}
|
||||
|
||||
return;
|
||||
case (char)13:
|
||||
MainScaleForm.CallFunction("ADD_TEXT", "ENTER");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(CurrentInput))
|
||||
{
|
||||
Networking.SendChatMessage(CurrentInput);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(CurrentInput)) Networking.SendChatMessage(CurrentInput);
|
||||
|
||||
Focused = false;
|
||||
CurrentInput = "";
|
||||
@@ -151,19 +127,19 @@ namespace RageCoop.Client
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int ToUnicodeEx(uint virtualKeyCode, uint scanCode, byte[] keyboardState,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
|
||||
[Out] [MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
|
||||
StringBuilder receivingBuffer,
|
||||
int bufferSize, uint flags, IntPtr kblayout);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetKeyboardLayout(uint idThread);
|
||||
|
||||
public static string GetCharFromKey(Keys key, bool shift, bool altGr)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
byte[] keyboardState = new byte[256];
|
||||
var buf = new StringBuilder(256);
|
||||
var keyboardState = new byte[256];
|
||||
|
||||
if (shift)
|
||||
{
|
||||
keyboardState[(int)Keys.ShiftKey] = 0xff;
|
||||
}
|
||||
if (shift) keyboardState[(int)Keys.ShiftKey] = 0xff;
|
||||
|
||||
if (altGr)
|
||||
{
|
||||
@@ -171,8 +147,8 @@ namespace RageCoop.Client
|
||||
keyboardState[(int)Keys.Menu] = 0xff;
|
||||
}
|
||||
|
||||
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, InputLanguage.CurrentInputLanguage.Handle);
|
||||
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, GetKeyboardLayout(0));
|
||||
return buf.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-55
@@ -1,88 +1,94 @@
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using static RageCoop.Client.Shared;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DownloadManager
|
||||
{
|
||||
public static event EventHandler<string> DownloadCompleted;
|
||||
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
|
||||
private static readonly HashSet<string> _resources = new HashSet<string>();
|
||||
|
||||
static DownloadManager()
|
||||
{
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) =>
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, data =>
|
||||
{
|
||||
var fr = new Packets.FileTransferRequest();
|
||||
fr.Deserialize(data);
|
||||
if (fr.Name.EndsWith(".res"))
|
||||
{
|
||||
_resources.Add(fr.Name);
|
||||
}
|
||||
return new Packets.FileTransferResponse()
|
||||
if (fr.Name.EndsWith(".res")) _resources.Add(fr.Name);
|
||||
return new Packets.FileTransferResponse
|
||||
{
|
||||
ID = fr.ID,
|
||||
Response = AddFile(fr.ID, fr.Name, fr.FileLength) ? FileResponse.NeedToDownload : FileResponse.AlreadyExists
|
||||
Response = AddFile(fr.ID, fr.Name, fr.FileLength)
|
||||
? FileResponse.NeedToDownload
|
||||
: FileResponse.AlreadyExists
|
||||
};
|
||||
});
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferComplete, (data) =>
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferComplete, data =>
|
||||
{
|
||||
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
|
||||
var packet = new Packets.FileTransferComplete();
|
||||
packet.Deserialize(data);
|
||||
|
||||
Main.Logger.Debug($"Finalizing download:{packet.ID}");
|
||||
Log.Debug($"Finalizing download:{packet.ID}");
|
||||
Complete(packet.ID);
|
||||
|
||||
// Inform the server that the download is completed
|
||||
return new Packets.FileTransferResponse()
|
||||
return new Packets.FileTransferResponse
|
||||
{
|
||||
ID = packet.ID,
|
||||
Response = FileResponse.Completed
|
||||
};
|
||||
});
|
||||
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, (data) =>
|
||||
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, data =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Resources.Load(ResourceFolder, _resources.ToArray());
|
||||
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.Loaded };
|
||||
Directory.CreateDirectory(ResourceFolder);
|
||||
MainRes.Load(ResourceFolder, _resources.ToArray());
|
||||
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.Loaded };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error("Error occurred when loading server resource:");
|
||||
Main.Logger.Error(ex);
|
||||
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
|
||||
Log.Error("Error occurred when loading server resource");
|
||||
Log.Error(ex);
|
||||
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.LoadFailed };
|
||||
}
|
||||
});
|
||||
}
|
||||
public static string ResourceFolder => Path.GetFullPath(Path.Combine(Main.Settings.DataDirectory, "Resources", Main.Settings.LastServerAddress.Replace(":", ".")));
|
||||
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
|
||||
private static readonly HashSet<string> _resources = new HashSet<string>();
|
||||
|
||||
public static string ResourceFolder => Path.GetFullPath(Path.Combine(DataPath, "Resources",
|
||||
API.ServerEndPoint.ToString().Replace(":", ".")));
|
||||
|
||||
public static event EventHandler<string> DownloadCompleted;
|
||||
|
||||
public static bool AddFile(int id, string name, long length)
|
||||
{
|
||||
var path = $"{ResourceFolder}\\{name}";
|
||||
Main.Logger.Debug($"Downloading file to {path} , id:{id}");
|
||||
Log.Debug($"Downloading file to {path} , id:{id}");
|
||||
if (!Directory.Exists(Directory.GetParent(path).FullName))
|
||||
{
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
}
|
||||
|
||||
if (FileAlreadyExists(ResourceFolder, name, length))
|
||||
{
|
||||
Main.Logger.Debug($"File already exists! canceling download:{name}");
|
||||
Log.Debug($"File already exists! canceling download:{name}");
|
||||
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
if (!name.EndsWith(".zip"))
|
||||
{
|
||||
Main.Logger.Error($"File download blocked! [{name}]");
|
||||
Log.Error($"File download blocked! [{name}]");
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
InProgressDownloads.Add(id, new DownloadFile()
|
||||
InProgressDownloads.Add(id, new DownloadFile
|
||||
{
|
||||
FileID = id,
|
||||
FileName = name,
|
||||
@@ -90,11 +96,12 @@ namespace RageCoop.Client
|
||||
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the file already exists and if the size correct otherwise delete this file
|
||||
/// Check if the file already exists and if the size correct otherwise delete this file
|
||||
/// </summary>
|
||||
/// <param name="folder"></param>
|
||||
/// <param name="name"></param>
|
||||
@@ -102,15 +109,11 @@ namespace RageCoop.Client
|
||||
/// <returns></returns>
|
||||
private static bool FileAlreadyExists(string folder, string name, long length)
|
||||
{
|
||||
string filePath = $"{folder}\\{name}";
|
||||
var filePath = $"{folder}\\{name}";
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
if (new FileInfo(filePath).Length == length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (new FileInfo(filePath).Length == length) return true;
|
||||
// Delete the file because the length is wrong (maybe the file was updated)
|
||||
File.Delete(filePath);
|
||||
}
|
||||
@@ -122,31 +125,25 @@ namespace RageCoop.Client
|
||||
{
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
if (InProgressDownloads.TryGetValue(id, out DownloadFile file))
|
||||
{
|
||||
|
||||
if (InProgressDownloads.TryGetValue(id, out var file))
|
||||
file.Stream.Write(chunk, 0, chunk.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Logger.Trace($"Received unhandled file chunk:{id}");
|
||||
}
|
||||
Log.Trace($"Received unhandled file chunk:{id}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Complete(int id)
|
||||
{
|
||||
|
||||
if (InProgressDownloads.TryGetValue(id, out DownloadFile f))
|
||||
if (InProgressDownloads.TryGetValue(id, out var f))
|
||||
{
|
||||
InProgressDownloads.Remove(id);
|
||||
f.Dispose();
|
||||
Main.Logger.Info($"Download finished:{f.FileName}");
|
||||
Log.Info($"Download finished:{f.FileName}");
|
||||
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Logger.Error($"Download not found! {id}");
|
||||
Log.Error($"Download not found! {id}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,24 +151,22 @@ namespace RageCoop.Client
|
||||
{
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
foreach (var file in InProgressDownloads.Values)
|
||||
{
|
||||
file.Dispose();
|
||||
}
|
||||
foreach (var file in InProgressDownloads.Values) file.Dispose();
|
||||
InProgressDownloads.Clear();
|
||||
}
|
||||
_resources.Clear();
|
||||
|
||||
_resources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DownloadFile : IDisposable
|
||||
{
|
||||
public int FileID { get; set; } = 0;
|
||||
public int FileID { get; set; }
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public long FileLength { get; set; } = 0;
|
||||
public long FileLength { get; set; }
|
||||
public long FileWritten { get; set; } = 0;
|
||||
public FileStream Stream { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Stream != null)
|
||||
@@ -182,4 +177,4 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Timers;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class HolePunch
|
||||
internal static class HolePunch
|
||||
{
|
||||
static HolePunch()
|
||||
{
|
||||
@@ -22,25 +22,26 @@ namespace RageCoop.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Networking.IsOnServer) { return; }
|
||||
if (!Networking.IsOnServer) return;
|
||||
foreach (var p in PlayerList.Players.Values.ToArray())
|
||||
{
|
||||
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null || p.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null ||
|
||||
p.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
{
|
||||
Main.Logger.Trace($"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.ID}");
|
||||
Log.Trace(
|
||||
$"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.ID}");
|
||||
var msg = Networking.Peer.CreateMessage();
|
||||
new Packets.HolePunch
|
||||
{
|
||||
Puncher = Main.LocalPlayerID,
|
||||
Puncher = LocalPlayerID,
|
||||
Status = p.HolePunchStatus
|
||||
}.Pack(msg);
|
||||
Networking.Peer.SendUnconnectedMessage(msg, new List<IPEndPoint> { p.InternalEndPoint, p.ExternalEndPoint });
|
||||
Networking.Peer.SendUnconnectedMessage(msg,
|
||||
new List<IPEndPoint> { p.InternalEndPoint, p.ExternalEndPoint });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
Log.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,31 +49,33 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (PlayerList.Players.TryGetValue(p.TargetID, out var player))
|
||||
{
|
||||
Main.Logger.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
|
||||
Log.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
|
||||
player.InternalEndPoint = CoreUtils.StringToEndPoint(p.TargetInternal);
|
||||
player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal);
|
||||
player.ConnectWhenPunched = p.Connect;
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Logger.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
|
||||
Log.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Punched(Packets.HolePunch p, IPEndPoint from)
|
||||
{
|
||||
Main.Logger.Debug($"HolePunch message received from:{from}, status:{p.Status}");
|
||||
Log.Debug($"HolePunch message received from:{from}, status:{p.Status}");
|
||||
if (PlayerList.Players.TryGetValue(p.Puncher, out var puncher))
|
||||
{
|
||||
Main.Logger.Debug("Puncher identified as: " + puncher.Username);
|
||||
Log.Debug("Puncher identified as: " + puncher.Username);
|
||||
puncher.HolePunchStatus = (byte)(p.Status + 1);
|
||||
if (p.Status >= 3)
|
||||
{
|
||||
Main.Logger.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
|
||||
if (puncher.ConnectWhenPunched && (puncher.Connection == null || puncher.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
Log.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
|
||||
if (puncher.ConnectWhenPunched && (puncher.Connection == null ||
|
||||
puncher.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
{
|
||||
Main.Logger.Debug("Connecting to peer: " + from);
|
||||
Log.Debug("Connecting to peer: " + from);
|
||||
var msg = Networking.Peer.CreateMessage();
|
||||
new Packets.P2PConnect { ID = Main.LocalPlayerID }.Pack(msg);
|
||||
new Packets.P2PConnect { ID = LocalPlayerID }.Pack(msg);
|
||||
puncher.Connection = Networking.Peer.Connect(from, msg);
|
||||
Networking.Peer.FlushSendQueue();
|
||||
}
|
||||
@@ -80,4 +83,4 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
-83
@@ -1,90 +1,97 @@
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static CoopPeer Peer;
|
||||
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
|
||||
public static bool ShowNetworkInfo = false;
|
||||
public static Security Security;
|
||||
public static NetConnection ServerConnection;
|
||||
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new Dictionary<int, Action<PacketType, NetIncomingMessage>>();
|
||||
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new Dictionary<PacketType, Func<NetIncomingMessage, Packet>>();
|
||||
internal static float SimulatedLatency = 0;
|
||||
public static bool IsConnecting { get; private set; }
|
||||
public static IPEndPoint _targetServerEP;
|
||||
static Networking()
|
||||
{
|
||||
Security = new Security(Main.Logger);
|
||||
}
|
||||
|
||||
public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null)
|
||||
public static CoopPeer Peer;
|
||||
public static bool ShowNetworkInfo = false;
|
||||
public static Security Security = new();
|
||||
public static NetConnection ServerConnection;
|
||||
|
||||
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new();
|
||||
|
||||
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new();
|
||||
|
||||
internal static float SimulatedLatency = 0;
|
||||
|
||||
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
|
||||
public static bool IsConnecting { get; private set; }
|
||||
public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
|
||||
|
||||
public static void ToggleConnection(string address, string username = null, string password = null,
|
||||
PublicKey publicKey = null)
|
||||
{
|
||||
Menus.CoopMenu.Menu.Visible = false;
|
||||
Peer?.Shutdown("Bye");
|
||||
if (IsOnServer)
|
||||
{
|
||||
// ?
|
||||
}
|
||||
else if (IsConnecting)
|
||||
CoopMenu.Menu.Visible = false;
|
||||
|
||||
if (IsConnecting)
|
||||
{
|
||||
_publicKeyReceived.Set();
|
||||
IsConnecting = false;
|
||||
Notification.Show("Connection has been canceled");
|
||||
}
|
||||
else
|
||||
{
|
||||
Peer?.Dispose();
|
||||
API.QueueAction(() =>
|
||||
Notification.Show("Connection has been canceled"));
|
||||
|
||||
Peer.Shutdown("bye");
|
||||
}
|
||||
else if (IsOnServer)
|
||||
{
|
||||
Peer.Shutdown("bye");
|
||||
}
|
||||
else
|
||||
{
|
||||
IsConnecting = true;
|
||||
password = password ?? Main.Settings.Password;
|
||||
username = username ?? Main.Settings.Username;
|
||||
password ??= Settings.Password;
|
||||
username ??= Settings.Username;
|
||||
|
||||
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
|
||||
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
|
||||
var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
|
||||
{
|
||||
AutoFlushSendQueue = false,
|
||||
SimulatedMinimumLatency = SimulatedLatency,
|
||||
SimulatedRandomLatency = 0,
|
||||
AcceptIncomingConnections = true,
|
||||
MaximumConnections = 32,
|
||||
PingInterval = 5
|
||||
};
|
||||
#if DEBUG
|
||||
config.SimulatedMinimumLatency = SimulatedLatency;
|
||||
config.SimulatedRandomLatency = 0;
|
||||
#endif
|
||||
|
||||
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
|
||||
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
|
||||
|
||||
string[] ip = new string[2];
|
||||
var ip = new string[2];
|
||||
|
||||
int idx = address.LastIndexOf(':');
|
||||
var idx = address.LastIndexOf(':');
|
||||
if (idx != -1)
|
||||
{
|
||||
ip[0] = address.Substring(0, idx);
|
||||
ip[1] = address.Substring(idx + 1);
|
||||
}
|
||||
|
||||
if (ip.Length != 2)
|
||||
{
|
||||
throw new Exception("Malformed URL");
|
||||
}
|
||||
if (ip.Length != 2) throw new Exception("Malformed URL");
|
||||
|
||||
PlayerList.Cleanup();
|
||||
EntityPool.AddPlayer();
|
||||
if (publicKey == null && !string.IsNullOrEmpty(password) && !Menus.CoopMenu.ShowPopUp("", "WARNING", "Server's IP can be spoofed when using direct connection, do you wish to continue?", "", true))
|
||||
if (publicKey == null && !string.IsNullOrEmpty(password) && !CoopMenu.ShowPopUp("", "WARNING",
|
||||
"Server's IP can be spoofed when using direct connection, do you wish to continue?", "", true))
|
||||
{
|
||||
IsConnecting = false;
|
||||
return;
|
||||
}
|
||||
Task.Run(() =>
|
||||
|
||||
ThreadManager.CreateThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -92,20 +99,26 @@ namespace RageCoop.Client
|
||||
|
||||
// Ensure static constructor invocation
|
||||
DownloadManager.Cleanup();
|
||||
Peer = new CoopPeer(config);
|
||||
Peer = new CoopPeer(config,Log);
|
||||
Peer.OnMessageReceived += (s, m) =>
|
||||
{
|
||||
try { ProcessMessage(m); }
|
||||
catch (Exception ex) { Main.Logger.Error(ex); }
|
||||
try
|
||||
{
|
||||
ProcessMessage(m);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
};
|
||||
Main.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
|
||||
Menus.CoopMenu._serverConnectItem.Enabled = false;
|
||||
API.QueueAction(() => { Notification.Show("~y~Trying to connect..."); });
|
||||
CoopMenu._serverConnectItem.Enabled = false;
|
||||
Security.Regen();
|
||||
if (publicKey == null)
|
||||
{
|
||||
if (!GetServerPublicKey(ip[0], int.Parse(ip[1])))
|
||||
{
|
||||
Menus.CoopMenu._serverConnectItem.Enabled = true;
|
||||
CoopMenu._serverConnectItem.Enabled = true;
|
||||
throw new TimeoutException("Failed to retrive server's public key");
|
||||
}
|
||||
}
|
||||
@@ -115,60 +128,79 @@ namespace RageCoop.Client
|
||||
}
|
||||
|
||||
// Send handshake packet
|
||||
NetOutgoingMessage outgoingMessage = Peer.CreateMessage();
|
||||
var handshake = new Packets.Handshake()
|
||||
var outgoingMessage = Peer.CreateMessage();
|
||||
var handshake = new Packets.Handshake
|
||||
{
|
||||
PedID = Main.LocalPlayerID,
|
||||
PedID = LocalPlayerID,
|
||||
Username = username,
|
||||
ModVersion = Main.Version.ToString(),
|
||||
ModVersion = Main.ModVersion.ToString(),
|
||||
PasswordEncrypted = Security.Encrypt(password.GetBytes()),
|
||||
InternalEndPoint = new System.Net.IPEndPoint(CoreUtils.GetLocalAddress(ip[0]), Peer.Port)
|
||||
InternalEndPoint = new IPEndPoint(CoreUtils.GetLocalAddress(ip[0]), Peer.Port)
|
||||
};
|
||||
|
||||
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted, out handshake.AesIVCrypted);
|
||||
handshake.Pack(outgoingMessage);
|
||||
ServerConnection = Peer.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error("Cannot connect to server: ", ex);
|
||||
Main.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
|
||||
Log.Error("Cannot connect to server: ", ex);
|
||||
API.QueueAction(() => Notification.Show("~r~Cannot connect to server: " + ex.Message));
|
||||
}
|
||||
|
||||
IsConnecting = false;
|
||||
});
|
||||
}, "Connect");
|
||||
}
|
||||
}
|
||||
public static bool IsOnServer { get => ServerConnection?.Status == NetConnectionStatus.Connected; }
|
||||
|
||||
private static int NewRequestID()
|
||||
{
|
||||
var ID = 0;
|
||||
while (ID == 0 || PendingResponses.ContainsKey(ID))
|
||||
{
|
||||
var rngBytes = new byte[4];
|
||||
|
||||
RandomNumberGenerator.Create().GetBytes(rngBytes);
|
||||
|
||||
// Convert the bytes into an integer
|
||||
ID = BitConverter.ToInt32(rngBytes, 0);
|
||||
}
|
||||
|
||||
return ID;
|
||||
}
|
||||
|
||||
#region -- PLAYER --
|
||||
|
||||
private static void PlayerConnect(Packets.PlayerConnect packet)
|
||||
{
|
||||
var p = new Player
|
||||
{
|
||||
ID = packet.PedID,
|
||||
Username = packet.Username,
|
||||
Username = packet.Username
|
||||
};
|
||||
PlayerList.SetPlayer(packet.PedID, packet.Username);
|
||||
|
||||
Main.Logger.Debug($"player connected:{p.Username}");
|
||||
Main.QueueAction(() =>
|
||||
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected."));
|
||||
Log.Debug($"player connected:{p.Username}");
|
||||
API.QueueAction(() =>
|
||||
Notification.Show($"~h~{p.Username}~h~ connected."));
|
||||
}
|
||||
|
||||
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
|
||||
{
|
||||
var player = PlayerList.GetPlayer(packet.PedID);
|
||||
if (player == null) { return; }
|
||||
if (player == null) return;
|
||||
PlayerList.RemovePlayer(packet.PedID);
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
EntityPool.RemoveAllFromPlayer(packet.PedID);
|
||||
GTA.UI.Notification.Show($"~h~{player.Username}~h~ left.");
|
||||
Notification.Show($"~h~{player.Username}~h~ left.");
|
||||
});
|
||||
}
|
||||
|
||||
#endregion // -- PLAYER --
|
||||
|
||||
#region -- GET --
|
||||
|
||||
private static bool GetServerPublicKey(string host, int port, int timeout = 10000)
|
||||
{
|
||||
Security.ServerRSA = null;
|
||||
@@ -178,7 +210,8 @@ namespace RageCoop.Client
|
||||
return _publicKeyReceived.WaitOne(timeout) && Security.ServerRSA != null;
|
||||
}
|
||||
|
||||
public static void GetResponse<T>(Packet request, Action<T> callback, ConnectionChannel channel = ConnectionChannel.RequestResponse) where T : Packet, new()
|
||||
public static void GetResponse<T>(Packet request, Action<T> callback,
|
||||
ConnectionChannel channel = ConnectionChannel.RequestResponse) where T : Packet, new()
|
||||
{
|
||||
var received = new AutoResetEvent(false);
|
||||
var id = NewRequestID();
|
||||
@@ -196,20 +229,5 @@ namespace RageCoop.Client
|
||||
}
|
||||
|
||||
#endregion
|
||||
private static int NewRequestID()
|
||||
{
|
||||
int ID = 0;
|
||||
while ((ID == 0) || PendingResponses.ContainsKey(ID))
|
||||
{
|
||||
byte[] rngBytes = new byte[4];
|
||||
|
||||
RandomNumberGenerator.Create().GetBytes(rngBytes);
|
||||
|
||||
// Convert the bytes into an integer
|
||||
ID = BitConverter.ToInt32(rngBytes, 0);
|
||||
}
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +1,75 @@
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </summary>
|
||||
private static class ReceivedPackets
|
||||
{
|
||||
public static Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent"/>
|
||||
/// </summary>
|
||||
private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case 50:
|
||||
return EntityPool.ServerProps[reader.ReadInt32()].MainProp?.Handle;
|
||||
case 51:
|
||||
return EntityPool.GetPedByID(reader.ReadInt32())?.MainPed?.Handle;
|
||||
case 52:
|
||||
return EntityPool.GetVehicleByID(reader.ReadInt32())?.MainVehicle?.Handle;
|
||||
case 60:
|
||||
return EntityPool.ServerBlips[reader.ReadInt32()].Handle;
|
||||
default:
|
||||
throw new ArgumentException("Cannot resolve server side argument: " + t);
|
||||
}
|
||||
};
|
||||
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
|
||||
private static bool _recycle;
|
||||
|
||||
public static void ProcessMessage(NetIncomingMessage message)
|
||||
{
|
||||
if (message == null) { return; }
|
||||
_recycle = true;
|
||||
if (message == null) return;
|
||||
var _recycle = true;
|
||||
switch (message.MessageType)
|
||||
{
|
||||
case NetIncomingMessageType.StatusChanged:
|
||||
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
|
||||
string reason = message.ReadString();
|
||||
var status = (NetConnectionStatus)message.ReadByte();
|
||||
var reason = message.ReadString();
|
||||
switch (status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
if (message.SenderConnection == ServerConnection)
|
||||
{
|
||||
CoopMenu.InitiateConnectionMenuSetting();
|
||||
}
|
||||
if (message.SenderConnection == ServerConnection) CoopMenu.InitiateConnectionMenuSetting();
|
||||
break;
|
||||
case NetConnectionStatus.Connected:
|
||||
if (message.SenderConnection == ServerConnection)
|
||||
{
|
||||
var response = message.SenderConnection.RemoteHailMessage;
|
||||
if ((PacketType)response.ReadByte() != PacketType.HandshakeSuccess)
|
||||
{
|
||||
throw new Exception("Invalid handshake response!");
|
||||
}
|
||||
var p = new Packets.HandshakeSuccess();
|
||||
p.Deserialize(response);
|
||||
foreach (var player in p.Players)
|
||||
{
|
||||
PlayerList.SetPlayer(player.ID, player.Username);
|
||||
}
|
||||
Main.Connected();
|
||||
foreach (var player in p.Players) PlayerList.SetPlayer(player.ID, player.Username);
|
||||
Connected();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Self-initiated connection
|
||||
if (message.SenderConnection.RemoteHailMessage == null) { return; }
|
||||
if (message.SenderConnection.RemoteHailMessage == null) return;
|
||||
|
||||
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
|
||||
if (PlayerList.Players.TryGetValue(p.ID, out var player))
|
||||
{
|
||||
player.Connection = message.SenderConnection;
|
||||
Main.Logger.Debug($"Direct connection to {player.Username} established");
|
||||
Log.Debug($"Direct connection to {player.Username} established");
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Logger.Info($"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
|
||||
Log.Info(
|
||||
$"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
|
||||
message.SenderConnection.Disconnect("eat poop");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
if (message.SenderConnection == ServerConnection)
|
||||
{
|
||||
Main.Disconnected(reason);
|
||||
}
|
||||
if (message.SenderConnection == ServerConnection) API.QueueAction(() => CleanUp(reason));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case NetIncomingMessageType.Data:
|
||||
{
|
||||
if (message.LengthBytes == 0) { break; }
|
||||
if (message.LengthBytes == 0) break;
|
||||
var packetType = PacketType.Unknown;
|
||||
try
|
||||
{
|
||||
@@ -112,17 +79,18 @@ namespace RageCoop.Client
|
||||
{
|
||||
case PacketType.Response:
|
||||
{
|
||||
int id = message.ReadInt32();
|
||||
var id = message.ReadInt32();
|
||||
if (PendingResponses.TryGetValue(id, out var callback))
|
||||
{
|
||||
callback((PacketType)message.ReadByte(), message);
|
||||
PendingResponses.Remove(id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PacketType.Request:
|
||||
{
|
||||
int id = message.ReadInt32();
|
||||
var id = message.ReadInt32();
|
||||
var realType = (PacketType)message.ReadByte();
|
||||
if (RequestHandlers.TryGetValue(realType, out var handler))
|
||||
{
|
||||
@@ -130,18 +98,19 @@ namespace RageCoop.Client
|
||||
response.Write((byte)PacketType.Response);
|
||||
response.Write(id);
|
||||
handler(message).Pack(response);
|
||||
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered, message.SequenceChannel);
|
||||
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered,
|
||||
message.SequenceChannel);
|
||||
Peer.FlushSendQueue();
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Logger.Debug("Did not find a request handler of type: " + realType);
|
||||
Log.Debug("Did not find a request handler of type: " + realType);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
||||
HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
|
||||
break;
|
||||
}
|
||||
@@ -149,15 +118,16 @@ namespace RageCoop.Client
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
|
||||
Notification.Show($"~r~~h~Packet Error {ex.Message}");
|
||||
return true;
|
||||
});
|
||||
Main.Logger.Error($"[{packetType}] {ex.Message}");
|
||||
Main.Logger.Error(ex);
|
||||
Log.Error($"[{packetType}] {ex.Message}");
|
||||
Log.Error(ex);
|
||||
Peer.Shutdown($"Packet Error [{packetType}]");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case NetIncomingMessageType.UnconnectedData:
|
||||
@@ -165,7 +135,6 @@ namespace RageCoop.Client
|
||||
var packetType = (PacketType)message.ReadByte();
|
||||
switch (packetType)
|
||||
{
|
||||
|
||||
case PacketType.HolePunch:
|
||||
{
|
||||
HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
|
||||
@@ -173,30 +142,29 @@ namespace RageCoop.Client
|
||||
}
|
||||
case PacketType.PublicKeyResponse:
|
||||
{
|
||||
if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) { break; }
|
||||
if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) break;
|
||||
var packet = message.GetPacket<Packets.PublicKeyResponse>();
|
||||
Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
|
||||
_publicKeyReceived.Set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case NetIncomingMessageType.DebugMessage:
|
||||
case NetIncomingMessageType.ErrorMessage:
|
||||
case NetIncomingMessageType.WarningMessage:
|
||||
case NetIncomingMessageType.VerboseDebugMessage:
|
||||
Main.Logger.Trace(message.ReadString());
|
||||
break;
|
||||
default:
|
||||
Log.Trace(message.ReadString());
|
||||
break;
|
||||
}
|
||||
if (_recycle)
|
||||
{
|
||||
Peer.Recycle(message);
|
||||
}
|
||||
|
||||
if (_recycle) Peer.Recycle(message);
|
||||
}
|
||||
private static void HandlePacket(PacketType packetType, NetIncomingMessage msg, NetConnection senderConnection, ref bool recycle)
|
||||
|
||||
private static void HandlePacket(PacketType packetType, NetIncomingMessage msg, NetConnection senderConnection,
|
||||
ref bool recycle)
|
||||
{
|
||||
switch (packetType)
|
||||
{
|
||||
@@ -232,25 +200,28 @@ namespace RageCoop.Client
|
||||
|
||||
case PacketType.ChatMessage:
|
||||
{
|
||||
|
||||
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
|
||||
var packet = new Packets.ChatMessage(b => Security.Decrypt(b));
|
||||
packet.Deserialize(msg);
|
||||
|
||||
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
MainChat.AddMessage(packet.Username, packet.Message);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.Voice:
|
||||
{
|
||||
if (Main.Settings.Voice)
|
||||
if (Settings.Voice)
|
||||
{
|
||||
Packets.Voice packet = new Packets.Voice();
|
||||
var packet = new Packets.Voice();
|
||||
packet.Deserialize(msg);
|
||||
|
||||
|
||||
SyncedPed player = EntityPool.GetPedByID(packet.ID);
|
||||
var player = EntityPool.GetPedByID(packet.ID);
|
||||
player.IsSpeaking = true;
|
||||
player.LastSpeakingTime = Main.Ticked;
|
||||
player.LastSpeakingTime = Ticked;
|
||||
|
||||
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
|
||||
}
|
||||
@@ -259,28 +230,28 @@ namespace RageCoop.Client
|
||||
|
||||
case PacketType.CustomEvent:
|
||||
{
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.CustomEventQueued:
|
||||
{
|
||||
recycle = false;
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
|
||||
Main.QueueAction(() =>
|
||||
var packet = new Packets.CustomEvent();
|
||||
if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
|
||||
{
|
||||
recycle = false;
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
API.Events.InvokeCustomEventReceived(packet);
|
||||
Peer.Recycle(msg);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
Peer.Recycle(msg);
|
||||
});
|
||||
API.Events.InvokeCustomEventReceived(packet);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.FileTransferChunk:
|
||||
{
|
||||
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
|
||||
var packet = new Packets.FileTransferChunk();
|
||||
packet.Deserialize(msg);
|
||||
DownloadManager.Write(packet.ID, packet.FileChunk);
|
||||
}
|
||||
@@ -291,21 +262,24 @@ namespace RageCoop.Client
|
||||
{
|
||||
recycle = false;
|
||||
// Dispatch to script thread
|
||||
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
SyncEvents.HandleEvent(packetType, msg);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PedSync(Packets.PedSync packet)
|
||||
{
|
||||
SyncedPed c = EntityPool.GetPedByID(packet.ID);
|
||||
var c = EntityPool.GetPedByID(packet.ID);
|
||||
if (c == null)
|
||||
{
|
||||
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
|
||||
// Log.Debug($"Creating character for incoming sync:{packet.ID}");
|
||||
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
|
||||
}
|
||||
PedDataFlags flags = packet.Flags;
|
||||
var flags = packet.Flags;
|
||||
c.ID = packet.ID;
|
||||
c.OwnerID = packet.OwnerID;
|
||||
c.Health = packet.Health;
|
||||
@@ -315,7 +289,6 @@ namespace RageCoop.Client
|
||||
c.Flags = packet.Flags;
|
||||
c.Heading = packet.Heading;
|
||||
c.Position = packet.Position;
|
||||
c.LastSyncedStopWatch.Restart();
|
||||
if (c.IsRagdoll)
|
||||
{
|
||||
c.HeadPosition = packet.HeadPosition;
|
||||
@@ -327,14 +300,14 @@ namespace RageCoop.Client
|
||||
c.VehicleID = packet.VehicleID;
|
||||
c.Seat = packet.Seat;
|
||||
}
|
||||
c.LastSynced = Main.Ticked;
|
||||
if (c.IsAiming)
|
||||
|
||||
if (c.IsAiming) c.AimCoords = packet.AimCoords;
|
||||
bool full = packet.Flags.HasPedFlag(PedDataFlags.IsFullSync);
|
||||
if (full)
|
||||
{
|
||||
c.AimCoords = packet.AimCoords;
|
||||
}
|
||||
if (packet.Flags.HasPedFlag(PedDataFlags.IsFullSync))
|
||||
{
|
||||
c.CurrentWeaponHash = packet.CurrentWeaponHash;
|
||||
if (packet.Speed == 4)
|
||||
c.VehicleWeapon = packet.VehicleWeapon;
|
||||
c.CurrentWeapon = packet.CurrentWeapon;
|
||||
c.Clothes = packet.Clothes;
|
||||
c.WeaponComponents = packet.WeaponComponents;
|
||||
c.WeaponTint = packet.WeaponTint;
|
||||
@@ -342,66 +315,60 @@ namespace RageCoop.Client
|
||||
c.BlipColor = packet.BlipColor;
|
||||
c.BlipSprite = packet.BlipSprite;
|
||||
c.BlipScale = packet.BlipScale;
|
||||
c.LastFullSynced = Main.Ticked;
|
||||
}
|
||||
|
||||
c.SetLastSynced(full);
|
||||
}
|
||||
|
||||
private static void VehicleSync(Packets.VehicleSync packet)
|
||||
{
|
||||
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
|
||||
if (v == null)
|
||||
var v = EntityPool.GetVehicleByID(packet.ED.ID);
|
||||
if (v == null) EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ED.ID));
|
||||
if (v.IsLocal) return;
|
||||
v.ID = packet.ED.ID;
|
||||
v.OwnerID = packet.ED.OwnerID;
|
||||
v.Position = packet.ED.Position;
|
||||
v.Quaternion = packet.ED.Quaternion;
|
||||
v.Velocity = packet.ED.Velocity;
|
||||
v.Model = packet.ED.ModelHash;
|
||||
v.VD = packet.VD;
|
||||
bool full = packet.VD.Flags.HasVehFlag(VehicleDataFlags.IsFullSync);
|
||||
if (full)
|
||||
{
|
||||
EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
|
||||
}
|
||||
if (v.IsLocal) { return; }
|
||||
v.ID = packet.ID;
|
||||
v.OwnerID = packet.OwnerID;
|
||||
v.Flags = packet.Flags;
|
||||
v.Position = packet.Position;
|
||||
v.Quaternion = packet.Quaternion;
|
||||
v.SteeringAngle = packet.SteeringAngle;
|
||||
v.ThrottlePower = packet.ThrottlePower;
|
||||
v.BrakePower = packet.BrakePower;
|
||||
v.Velocity = packet.Velocity;
|
||||
v.RotationVelocity = packet.RotationVelocity;
|
||||
v.DeluxoWingRatio = packet.DeluxoWingRatio;
|
||||
v.LastSynced = Main.Ticked;
|
||||
v.LastSyncedStopWatch.Restart();
|
||||
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
|
||||
{
|
||||
v.DamageModel = packet.DamageModel;
|
||||
v.EngineHealth = packet.EngineHealth;
|
||||
v.Mods = packet.Mods;
|
||||
v.Model = packet.ModelHash;
|
||||
v.Colors = packet.Colors;
|
||||
v.LandingGear = packet.LandingGear;
|
||||
v.RoofState = (VehicleRoofState)packet.RoofState;
|
||||
v.LockStatus = packet.LockStatus;
|
||||
v.RadioStation = packet.RadioStation;
|
||||
v.LicensePlate = packet.LicensePlate;
|
||||
v.Livery = packet.Livery;
|
||||
v.LastFullSynced = Main.Ticked;
|
||||
v.VDF = packet.VDF;
|
||||
v.VDV = packet.VDV;
|
||||
}
|
||||
v.SetLastSynced(full);
|
||||
}
|
||||
|
||||
private static void ProjectileSync(Packets.ProjectileSync packet)
|
||||
{
|
||||
|
||||
var p = EntityPool.GetProjectileByID(packet.ID);
|
||||
if (p == null)
|
||||
{
|
||||
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) { return; }
|
||||
// Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
|
||||
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) return;
|
||||
// Log.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
|
||||
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
|
||||
}
|
||||
|
||||
p.Flags = packet.Flags;
|
||||
p.Position = packet.Position;
|
||||
p.Rotation = packet.Rotation;
|
||||
p.Velocity = packet.Velocity;
|
||||
p.WeaponHash = (WeaponHash)packet.WeaponHash;
|
||||
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle) ?
|
||||
(SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID) : EntityPool.GetPedByID(packet.ShooterID);
|
||||
p.LastSynced = Main.Ticked;
|
||||
p.LastSyncedStopWatch.Restart();
|
||||
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle)
|
||||
? (SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID)
|
||||
: EntityPool.GetPedByID(packet.ShooterID);
|
||||
p.SetLastSynced(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </summary>
|
||||
private static class ReceivedPackets
|
||||
{
|
||||
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static readonly Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static int SyncInterval = 30;
|
||||
public static List<NetConnection> Targets = new List<NetConnection>();
|
||||
|
||||
public static void SendSync(Packet p, ConnectionChannel channel = ConnectionChannel.Default,
|
||||
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
|
||||
{
|
||||
Peer.SendTo(p, Targets, channel, method);
|
||||
}
|
||||
|
||||
public static void SendPed(SyncedPed sp, bool full)
|
||||
{
|
||||
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
|
||||
var ped = sp.MainPed;
|
||||
var p = SendPackets.PedPacket;
|
||||
p.ID = sp.ID;
|
||||
p.OwnerID = sp.OwnerID;
|
||||
p.Health = ped.Health;
|
||||
p.Rotation = ped.ReadRotation();
|
||||
p.Velocity = ped.ReadVelocity();
|
||||
p.Speed = ped.GetPedSpeed();
|
||||
p.Flags = ped.GetPedFlags();
|
||||
p.Heading = ped.Heading;
|
||||
if (p.Flags.HasPedFlag(PedDataFlags.IsAiming)) p.AimCoords = ped.GetAimCoord();
|
||||
if (p.Flags.HasPedFlag(PedDataFlags.IsRagdoll))
|
||||
{
|
||||
p.HeadPosition = ped.Bones[Bone.SkelHead].Position;
|
||||
p.RightFootPosition = ped.Bones[Bone.SkelRightFoot].Position;
|
||||
p.LeftFootPosition = ped.Bones[Bone.SkelLeftFoot].Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Seat sync
|
||||
if (p.Speed >= 4)
|
||||
{
|
||||
var veh = ped.CurrentVehicle?.GetSyncEntity() ??
|
||||
ped.VehicleTryingToEnter?.GetSyncEntity() ?? ped.LastVehicle?.GetSyncEntity();
|
||||
p.VehicleID = veh?.ID ?? 0;
|
||||
if (p.VehicleID == 0) Log.Error("Invalid vehicle");
|
||||
if (p.Speed == 5)
|
||||
p.Seat = ped.GetSeatTryingToEnter();
|
||||
else
|
||||
p.Seat = ped.SeatIndex;
|
||||
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
|
||||
{
|
||||
veh.OwnerID = LocalPlayerID;
|
||||
SyncEvents.TriggerChangeOwner(veh.ID, LocalPlayerID);
|
||||
}
|
||||
}
|
||||
|
||||
p.Position = ped.ReadPosition();
|
||||
}
|
||||
|
||||
sp.LastSentStopWatch.Restart();
|
||||
if (full)
|
||||
{
|
||||
if (p.Speed == 4)
|
||||
p.VehicleWeapon = ped.VehicleWeapon;
|
||||
p.CurrentWeapon = ped.Weapons.Current.Hash;
|
||||
p.Flags |= PedDataFlags.IsFullSync;
|
||||
p.Clothes = ped.GetPedClothes();
|
||||
p.ModelHash = ped.Model.Hash;
|
||||
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
|
||||
p.WeaponTint = (byte)Call<int>(GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
|
||||
|
||||
Blip b;
|
||||
if (sp.IsPlayer)
|
||||
{
|
||||
p.BlipColor = API.Config.BlipColor;
|
||||
p.BlipSprite = API.Config.BlipSprite;
|
||||
p.BlipScale = API.Config.BlipScale;
|
||||
}
|
||||
else if ((b = ped.AttachedBlip) != null)
|
||||
{
|
||||
p.BlipColor = b.Color;
|
||||
p.BlipSprite = b.Sprite;
|
||||
|
||||
if (p.BlipSprite == BlipSprite.PoliceOfficer || p.BlipSprite == BlipSprite.PoliceOfficer2)
|
||||
p.BlipScale = 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.BlipColor = (BlipColor)255;
|
||||
}
|
||||
}
|
||||
|
||||
SendSync(p, ConnectionChannel.PedSync);
|
||||
}
|
||||
|
||||
public static void SendVehicle(SyncedVehicle v, bool full)
|
||||
{
|
||||
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
|
||||
var veh = v.MainVehicle;
|
||||
var packet = SendPackets.VehicelPacket;
|
||||
packet.ED.ID = v.ID;
|
||||
packet.ED.OwnerID = v.OwnerID;
|
||||
packet.ED.Position = veh.ReadPosition();
|
||||
packet.ED.Velocity = veh.Velocity;
|
||||
packet.ED.Quaternion = veh.ReadQuaternion();
|
||||
packet.ED.ModelHash = veh.Model.Hash;
|
||||
packet.VD.Flags = v.GetVehicleFlags();
|
||||
packet.VD.SteeringAngle = veh.SteeringAngle;
|
||||
packet.VD.ThrottlePower = veh.ThrottlePower;
|
||||
packet.VD.BrakePower = veh.BrakePower;
|
||||
packet.VD.Flags |= VehicleDataFlags.IsFullSync;
|
||||
packet.VD.LockStatus = veh.LockStatus;
|
||||
|
||||
v.LastSentStopWatch.Restart();
|
||||
if (packet.VD.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
|
||||
packet.VD.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio();
|
||||
if (full)
|
||||
{
|
||||
byte primaryColor = 0;
|
||||
byte secondaryColor = 0;
|
||||
unsafe
|
||||
{
|
||||
Call<byte>(GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
|
||||
}
|
||||
|
||||
packet.VDF.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
|
||||
packet.VDF.RoofState = (byte)veh.RoofState;
|
||||
packet.VDF.Colors = (primaryColor, secondaryColor);
|
||||
packet.VDF.DamageModel = veh.GetVehicleDamageModel();
|
||||
packet.VDF.EngineHealth = veh.EngineHealth;
|
||||
packet.VDF.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
|
||||
packet.VDF.HeadlightColor = (byte)Call<int>(GET_VEHICLE_XENON_LIGHT_COLOR_INDEX, veh);
|
||||
packet.VDF.ExtrasMask = v.GetVehicleExtras();
|
||||
packet.VDF.RadioStation = v.MainVehicle == LastV
|
||||
? Util.GetPlayerRadioIndex() : byte.MaxValue;
|
||||
if (packet.VDF.EngineHealth > v.LastEngineHealth) packet.VD.Flags |= VehicleDataFlags.Repaired;
|
||||
|
||||
packet.VDV.Mods = v.GetVehicleMods(out packet.VDF.ToggleModsMask);
|
||||
packet.VDV.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
|
||||
v.LastEngineHealth = packet.VDF.EngineHealth;
|
||||
}
|
||||
|
||||
SendSync(packet, ConnectionChannel.VehicleSync);
|
||||
}
|
||||
|
||||
public static void SendProjectile(SyncedProjectile sp)
|
||||
{
|
||||
sp.ExtractData(ref SendPackets.ProjectilePacket);
|
||||
if (sp.MainProjectile.IsDead) EntityPool.RemoveProjectile(sp.ID, "Dead");
|
||||
SendSync(SendPackets.ProjectilePacket, ConnectionChannel.ProjectileSync);
|
||||
}
|
||||
|
||||
public static void SendChatMessage(string message)
|
||||
{
|
||||
Peer.SendTo(new Packets.ChatMessage(s => Security.Encrypt(s.GetBytes()))
|
||||
{ Username = Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat,
|
||||
NetDeliveryMethod.ReliableOrdered);
|
||||
Peer.FlushSendQueue();
|
||||
}
|
||||
|
||||
public static void SendVoiceMessage(byte[] buffer, int recorded)
|
||||
{
|
||||
SendSync(new Packets.Voice { ID = LocalPlayerID, Buffer = buffer, Recorded = recorded },
|
||||
ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </summary>
|
||||
private static class SendPackets
|
||||
{
|
||||
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
|
||||
|
||||
#region SYNC EVENTS
|
||||
|
||||
public static void SendBullet(int ownerID, uint weapon, Vector3 end)
|
||||
{
|
||||
SendSync(new Packets.BulletShot
|
||||
{
|
||||
EndPosition = end,
|
||||
OwnerID = ownerID,
|
||||
WeaponHash = weapon
|
||||
}, ConnectionChannel.SyncEvents);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,11 @@ namespace RageCoop.Client
|
||||
{
|
||||
internal static class Statistics
|
||||
{
|
||||
public static int BytesDownPerSecond { get; private set; }
|
||||
public static int BytesUpPerSecond { get; private set; }
|
||||
static Statistics()
|
||||
{
|
||||
Task.Run(() =>
|
||||
ThreadManager.CreateThread(() =>
|
||||
{
|
||||
while (true)
|
||||
while (!IsUnloading)
|
||||
{
|
||||
var bu = Networking.Peer.Statistics.SentBytes;
|
||||
var bd = Networking.Peer.Statistics.ReceivedBytes;
|
||||
@@ -19,7 +17,10 @@ namespace RageCoop.Client
|
||||
BytesUpPerSecond = Networking.Peer.Statistics.SentBytes - bu;
|
||||
BytesDownPerSecond = Networking.Peer.Statistics.ReceivedBytes - bd;
|
||||
}
|
||||
});
|
||||
},"Statistics");
|
||||
}
|
||||
|
||||
public static int BytesDownPerSecond { get; private set; }
|
||||
public static int BytesUpPerSecond { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
using GTA;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json.Serialization;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
@@ -15,33 +18,26 @@ namespace RageCoop.Client
|
||||
private const float RIGHT_POSITION = 0.9f;
|
||||
private static readonly Scaleform _mainScaleform = new Scaleform("mp_mm_card_freemode");
|
||||
private static ulong _lastUpdate = Util.GetTickCount64();
|
||||
public static ulong Pressed { get; set; }
|
||||
|
||||
public static bool LeftAlign = true;
|
||||
public static Dictionary<int, Player> Players = new Dictionary<int, Player> { };
|
||||
public static Dictionary<int, Player> Players = new Dictionary<int, Player>();
|
||||
public static ulong Pressed { get; set; }
|
||||
|
||||
public static void Tick()
|
||||
{
|
||||
if (!Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Networking.IsOnServer) return;
|
||||
|
||||
if ((Util.GetTickCount64() - _lastUpdate) >= 1000)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
if (Util.GetTickCount64() - _lastUpdate >= 1000) Update();
|
||||
|
||||
if ((Util.GetTickCount64() - Pressed) < 5000 && !Main.MainChat.Focused
|
||||
if (Util.GetTickCount64() - Pressed < 5000 && !MainChat.Focused
|
||||
#if !NON_INTERACTIVE
|
||||
&& !Menus.CoopMenu.MenuPool.AreAnyVisible
|
||||
&& !CoopMenu.MenuPool.AreAnyVisible
|
||||
#endif
|
||||
)
|
||||
{
|
||||
Function.Call(Hash.DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
|
||||
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
|
||||
0.28f, 0.6f,
|
||||
255, 255, 255, 255, 0);
|
||||
}
|
||||
)
|
||||
Call(DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
|
||||
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
|
||||
0.28f, 0.6f,
|
||||
255, 255, 255, 255, 0);
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
@@ -50,20 +46,20 @@ namespace RageCoop.Client
|
||||
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
|
||||
|
||||
int i = 0;
|
||||
var i = 0;
|
||||
|
||||
foreach (var player in Players.Values)
|
||||
{
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms", player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' ');
|
||||
}
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms",
|
||||
player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' ');
|
||||
|
||||
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players");
|
||||
_mainScaleform.CallFunction("DISPLAY_VIEW");
|
||||
}
|
||||
|
||||
public static void SetPlayer(int id, string username, float latency = 0)
|
||||
{
|
||||
Main.Logger.Debug($"{id},{username},{latency}");
|
||||
if (Players.TryGetValue(id, out Player p))
|
||||
Log.Debug($"{id},{username},{latency}");
|
||||
if (Players.TryGetValue(id, out var p))
|
||||
{
|
||||
p.Username = username;
|
||||
p.ID = id;
|
||||
@@ -75,6 +71,7 @@ namespace RageCoop.Client
|
||||
Players.Add(id, p);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdatePlayer(Packets.PlayerInfoUpdate packet)
|
||||
{
|
||||
var p = GetPlayer(packet.PedID);
|
||||
@@ -83,86 +80,94 @@ namespace RageCoop.Client
|
||||
p._latencyToServer = packet.Latency;
|
||||
p.Position = packet.Position;
|
||||
p.IsHost = packet.IsHost;
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (p.FakeBlip?.Exists() != true)
|
||||
{
|
||||
p.FakeBlip = World.CreateBlip(p.Position);
|
||||
}
|
||||
if (p.FakeBlip?.Exists() != true) p.FakeBlip = World.CreateBlip(p.Position);
|
||||
if (EntityPool.PedExists(p.ID))
|
||||
{
|
||||
p.FakeBlip.DisplayType = BlipDisplayType.NoDisplay;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.FakeBlip.Color = Scripting.API.Config.BlipColor;
|
||||
p.FakeBlip.Scale = Scripting.API.Config.BlipScale;
|
||||
p.FakeBlip.Sprite = Scripting.API.Config.BlipSprite;
|
||||
p.FakeBlip.Color = API.Config.BlipColor;
|
||||
p.FakeBlip.Scale = API.Config.BlipScale;
|
||||
p.FakeBlip.Sprite = API.Config.BlipSprite;
|
||||
p.FakeBlip.DisplayType = BlipDisplayType.Default;
|
||||
p.FakeBlip.Position = p.Position;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static Player GetPlayer(int id)
|
||||
{
|
||||
Players.TryGetValue(id, out Player p);
|
||||
Players.TryGetValue(id, out var p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Player GetPlayer(SyncedPed p)
|
||||
{
|
||||
var player = GetPlayer(p.ID);
|
||||
if (player != null)
|
||||
{
|
||||
player.Character = p;
|
||||
}
|
||||
if (player != null) player.Character = p;
|
||||
return player;
|
||||
}
|
||||
|
||||
public static void RemovePlayer(int id)
|
||||
{
|
||||
if (Players.TryGetValue(id, out var player))
|
||||
{
|
||||
Players.Remove(id);
|
||||
Main.QueueAction(() => player.FakeBlip?.Delete());
|
||||
API.QueueAction(() => player.FakeBlip?.Delete());
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cleanup()
|
||||
{
|
||||
foreach (var p in Players.Values.ToArray())
|
||||
{
|
||||
p.FakeBlip?.Delete();
|
||||
}
|
||||
Players = new Dictionary<int, Player> { };
|
||||
foreach (var p in Players.Values.ToArray()) p.FakeBlip?.Delete();
|
||||
Players = new Dictionary<int, Player>();
|
||||
}
|
||||
}
|
||||
|
||||
public class Player
|
||||
internal class Player
|
||||
{
|
||||
internal float _latencyToServer;
|
||||
internal bool ConnectWhenPunched { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Blip FakeBlip { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public Vector3 Position { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public SyncedPed Character { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public NetConnection Connection { get; internal set; }
|
||||
|
||||
public byte HolePunchStatus { get; internal set; } = 1;
|
||||
public bool IsHost { get; internal set; }
|
||||
public string Username { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Universal ped ID.
|
||||
/// Universal ped ID.
|
||||
/// </summary>
|
||||
public int ID
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
public int ID { get; internal set; }
|
||||
|
||||
public int EntityHandle => Character?.MainPed?.Handle ?? 0;
|
||||
|
||||
public IPEndPoint InternalEndPoint { get; internal set; }
|
||||
public IPEndPoint ExternalEndPoint { get; internal set; }
|
||||
internal bool ConnectWhenPunched { get; set; }
|
||||
public Blip FakeBlip { get; internal set; }
|
||||
public Vector3 Position { get; internal set; }
|
||||
public SyncedPed Character { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Player round-trip time in seconds, will be the rtt to server if not using P2P connection.
|
||||
/// Player round-trip time in seconds, will be the rtt to server if not using P2P connection.
|
||||
/// </summary>
|
||||
public float Ping => Main.LocalPlayerID == ID ? Networking.Latency * 2 : (HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2);
|
||||
public float PacketTravelTime => HasDirectConnection ? Connection.AverageRoundtripTime / 2 : Networking.Latency + _latencyToServer;
|
||||
internal float _latencyToServer = 0;
|
||||
public float Ping => LocalPlayerID == ID ? Networking.Latency * 2 :
|
||||
HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2;
|
||||
|
||||
public float PacketTravelTime => HasDirectConnection
|
||||
? Connection.AverageRoundtripTime / 2
|
||||
: Networking.Latency + _latencyToServer;
|
||||
|
||||
public bool DisplayNameTag { get; set; } = true;
|
||||
public NetConnection Connection { get; internal set; }
|
||||
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -15,8 +15,8 @@ using System.Resources;
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
// Version informationr(
|
||||
[assembly: AssemblyVersion("1.5.4.3")]
|
||||
[assembly: AssemblyFileVersion("1.5.4.3")]
|
||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||
// Version information
|
||||
[assembly: AssemblyVersion("1.6.0.58")]
|
||||
[assembly: AssemblyFileVersion("1.6.0.58")]
|
||||
[assembly: NeutralResourcesLanguage( "en-US" )]
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<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 "$(SolutionDir)Client\Data" "$(OutDir)..\Data" /i /s /y" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<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>
|
||||
@@ -0,0 +1,140 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
#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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+118
-128
@@ -1,18 +1,20 @@
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal class BaseScript : ClientScript
|
||||
internal static class BaseScript
|
||||
{
|
||||
private bool _isHost = false;
|
||||
public override void OnStart()
|
||||
private static bool _isHost;
|
||||
|
||||
public static void OnStart()
|
||||
{
|
||||
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
|
||||
API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
|
||||
@@ -29,50 +31,50 @@ namespace RageCoop.Client.Scripting
|
||||
API.RegisterCustomEventHandler(CustomEvents.DeleteServerBlip, DeleteServerBlip);
|
||||
API.RegisterCustomEventHandler(CustomEvents.CreateVehicle, CreateVehicle);
|
||||
API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip);
|
||||
API.RegisterCustomEventHandler(CustomEvents.IsHost, (e) => { _isHost = (bool)e.Args[0]; });
|
||||
API.RegisterCustomEventHandler(CustomEvents.IsHost, e => { _isHost = (bool)e.Args[0]; });
|
||||
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
|
||||
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied, (e) => { GTA.UI.Notification.Show($"~h~{e.Args[0]}~h~ died."); });
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_isHost)
|
||||
{
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var time = World.CurrentTimeOfDay;
|
||||
int weather1 = default(int);
|
||||
int weather2 = default(int);
|
||||
float percent2 = default(float);
|
||||
Function.Call(Hash._GET_WEATHER_TYPE_TRANSITION, &weather1, &weather2, &percent2);
|
||||
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes, time.Seconds, weather1, weather2, percent2);
|
||||
}
|
||||
});
|
||||
}
|
||||
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied,
|
||||
e => { Notification.Show($"~h~{e.Args[0]}~h~ died."); });
|
||||
ThreadManager.CreateThread(() =>
|
||||
{
|
||||
while (!IsUnloading)
|
||||
{
|
||||
if (Networking.IsOnServer && _isHost)
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var time = World.CurrentTimeOfDay;
|
||||
var weather1 = default(int);
|
||||
var weather2 = default(int);
|
||||
var percent2 = default(float);
|
||||
Call(GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2);
|
||||
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes,
|
||||
time.Seconds, weather1, weather2, percent2);
|
||||
}
|
||||
});
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
});
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}, "BaseScript");
|
||||
}
|
||||
|
||||
private void WeatherTimeSync(CustomEventReceivedArgs e)
|
||||
private static void WeatherTimeSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
|
||||
Function.Call(Hash._SET_WEATHER_TYPE_TRANSITION, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
|
||||
Call(SET_CURR_WEATHER_STATE, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
|
||||
}
|
||||
|
||||
private void SetDisplayNameTag(CustomEventReceivedArgs e)
|
||||
private static void SetDisplayNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; }
|
||||
if (p != null) p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
|
||||
private void UpdatePedBlip(CustomEventReceivedArgs e)
|
||||
private static void UpdatePedBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = Entity.FromHandle((int)e.Args[0]);
|
||||
if (p == null) { return; }
|
||||
if (p == null) return;
|
||||
if (p.Handle == Game.Player.Character.Handle)
|
||||
{
|
||||
API.Config.BlipColor = (BlipColor)(byte)e.Args[1];
|
||||
@@ -82,34 +84,31 @@ namespace RageCoop.Client.Scripting
|
||||
else
|
||||
{
|
||||
var b = p.AttachedBlip;
|
||||
if (b == null) { b = p.AddBlip(); }
|
||||
if (b == null) b = p.AddBlip();
|
||||
b.Color = (BlipColor)(byte)e.Args[1];
|
||||
b.Sprite = (BlipSprite)(ushort)e.Args[2];
|
||||
b.Scale = (float)e.Args[3];
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateVehicle(CustomEventReceivedArgs e)
|
||||
private static void CreateVehicle(CustomEventReceivedArgs e)
|
||||
{
|
||||
var vehicleModel = (Model)e.Args[1];
|
||||
vehicleModel.Request(1000);
|
||||
Vehicle veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
|
||||
while (veh == null)
|
||||
{
|
||||
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
|
||||
Vehicle veh;
|
||||
while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
veh.CanPretendOccupants = false;
|
||||
var v = new SyncedVehicle()
|
||||
var v = new SyncedVehicle
|
||||
{
|
||||
ID = (int)e.Args[0],
|
||||
MainVehicle = veh,
|
||||
OwnerID = Main.LocalPlayerID,
|
||||
OwnerID = LocalPlayerID
|
||||
};
|
||||
EntityPool.Add(v);
|
||||
}
|
||||
|
||||
private void DeleteServerBlip(CustomEventReceivedArgs e)
|
||||
private static void DeleteServerBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
|
||||
{
|
||||
@@ -118,19 +117,17 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
}
|
||||
|
||||
private void ServerBlipSync(CustomEventReceivedArgs obj)
|
||||
private static void ServerBlipSync(CustomEventReceivedArgs obj)
|
||||
{
|
||||
int id = (int)obj.Args[0];
|
||||
var id = (int)obj.Args[0];
|
||||
var sprite = (BlipSprite)(ushort)obj.Args[1];
|
||||
var color = (BlipColor)(byte)obj.Args[2];
|
||||
var scale = (float)obj.Args[3];
|
||||
var pos = (Vector3)obj.Args[4];
|
||||
int rot = (int)obj.Args[5];
|
||||
var rot = (int)obj.Args[5];
|
||||
var name = (string)obj.Args[6];
|
||||
if (!EntityPool.ServerBlips.TryGetValue(id, out Blip blip))
|
||||
{
|
||||
if (!EntityPool.ServerBlips.TryGetValue(id, out var blip))
|
||||
EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos));
|
||||
}
|
||||
blip.Sprite = sprite;
|
||||
blip.Color = color;
|
||||
blip.Scale = scale;
|
||||
@@ -140,27 +137,23 @@ namespace RageCoop.Client.Scripting
|
||||
}
|
||||
|
||||
|
||||
private void DeleteEntity(CustomEventReceivedArgs e)
|
||||
private static void DeleteEntity(CustomEventReceivedArgs e)
|
||||
{
|
||||
Entity.FromHandle((int)e.Args[0])?.Delete();
|
||||
}
|
||||
|
||||
public override void OnStop()
|
||||
{
|
||||
}
|
||||
private void SetNameTag(CustomEventReceivedArgs e)
|
||||
private static void SetNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null)
|
||||
{
|
||||
p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
if (p != null) p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
private void SetAutoRespawn(CustomEventReceivedArgs args)
|
||||
|
||||
private static void SetAutoRespawn(CustomEventReceivedArgs args)
|
||||
{
|
||||
API.Config.EnableAutoRespawn = (bool)args.Args[0];
|
||||
}
|
||||
private void DeleteServerProp(CustomEventReceivedArgs e)
|
||||
|
||||
private static void DeleteServerProp(CustomEventReceivedArgs e)
|
||||
{
|
||||
var id = (int)e.Args[0];
|
||||
if (EntityPool.ServerProps.TryGetValue(id, out var prop))
|
||||
@@ -168,137 +161,134 @@ namespace RageCoop.Client.Scripting
|
||||
EntityPool.ServerProps.Remove(id);
|
||||
prop?.MainProp?.Delete();
|
||||
}
|
||||
|
||||
}
|
||||
private void ServerObjectSync(CustomEventReceivedArgs e)
|
||||
|
||||
private static void ServerObjectSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
SyncedProp prop;
|
||||
var id = (int)e.Args[0];
|
||||
lock (EntityPool.PropsLock)
|
||||
{
|
||||
if (!EntityPool.ServerProps.TryGetValue(id, out prop))
|
||||
{
|
||||
EntityPool.ServerProps.Add(id, prop = new SyncedProp(id));
|
||||
}
|
||||
}
|
||||
prop.LastSynced = Main.Ticked + 1;
|
||||
|
||||
prop.LastSynced = Ticked + 1;
|
||||
prop.Model = (Model)e.Args[1];
|
||||
prop.Position = (Vector3)e.Args[2];
|
||||
prop.Rotation = (Vector3)e.Args[3];
|
||||
prop.Model.Request(1000);
|
||||
prop.Update();
|
||||
}
|
||||
private void NativeCall(CustomEventReceivedArgs e)
|
||||
|
||||
private static void NativeCall(CustomEventReceivedArgs e)
|
||||
{
|
||||
List<InputArgument> arguments = new List<InputArgument>();
|
||||
var arguments = new List<InputArgument>();
|
||||
int i;
|
||||
var ty = (byte)e.Args[0];
|
||||
TypeCode returnType = (TypeCode)ty;
|
||||
var returnType = (TypeCode)ty;
|
||||
i = returnType == TypeCode.Empty ? 1 : 2;
|
||||
var hash = (Hash)e.Args[i++];
|
||||
for (; i < e.Args.Length; i++)
|
||||
{
|
||||
arguments.Add(GetInputArgument(e.Args[i]));
|
||||
}
|
||||
for (; i < e.Args.Length; i++) arguments.Add(GetInputArgument(e.Args[i]));
|
||||
|
||||
if (returnType == TypeCode.Empty)
|
||||
{
|
||||
Function.Call(hash, arguments.ToArray());
|
||||
Call(hash, arguments.ToArray());
|
||||
return;
|
||||
}
|
||||
var t = returnType;
|
||||
int id = (int)e.Args[1];
|
||||
|
||||
var id = (int)e.Args[1];
|
||||
|
||||
|
||||
switch (returnType)
|
||||
{
|
||||
case TypeCode.Boolean:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<bool>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<bool>(hash, arguments.ToArray()));
|
||||
break;
|
||||
case TypeCode.Byte:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<byte>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<byte>(hash, arguments.ToArray()));
|
||||
break;
|
||||
case TypeCode.Char:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<char>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<char>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Single:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<float>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<float>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Double:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<double>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<double>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int16:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<short>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<short>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int32:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<int>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id, Call<int>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int64:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<long>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<long>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.String:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<string>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<string>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt16:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<ushort>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<ushort>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt32:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<uint>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<uint>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt64:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse,
|
||||
new object[] { id, Function.Call<ulong>(hash, arguments.ToArray()) });
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<ulong>(hash, arguments.ToArray()));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
private InputArgument GetInputArgument(object obj)
|
||||
|
||||
private static InputArgument GetInputArgument(object obj)
|
||||
{
|
||||
// Implicit conversion
|
||||
switch (obj)
|
||||
{
|
||||
case byte _:
|
||||
return (byte)obj;
|
||||
case short _:
|
||||
return (short)obj;
|
||||
case ushort _:
|
||||
return (ushort)obj;
|
||||
case int _:
|
||||
return (int)obj;
|
||||
case uint _:
|
||||
return (uint)obj;
|
||||
case long _:
|
||||
return (long)obj;
|
||||
case ulong _:
|
||||
return (ulong)obj;
|
||||
case float _:
|
||||
return (float)obj;
|
||||
case bool _:
|
||||
return (bool)obj;
|
||||
case string _:
|
||||
return (obj as string);
|
||||
case byte stuff:
|
||||
return stuff;
|
||||
case short stuff:
|
||||
return stuff;
|
||||
case ushort stuff:
|
||||
return stuff;
|
||||
case int stuff:
|
||||
return stuff;
|
||||
case uint stuff:
|
||||
return stuff;
|
||||
case long stuff:
|
||||
return stuff;
|
||||
case ulong stuff:
|
||||
return stuff;
|
||||
case float stuff:
|
||||
return stuff;
|
||||
case bool stuff:
|
||||
return stuff;
|
||||
case string stuff:
|
||||
return stuff;
|
||||
default:
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,40 @@
|
||||
using RageCoop.Core;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class Security
|
||||
{
|
||||
public RSA ServerRSA { get; set; }
|
||||
public Aes ClientAes { get; set; } = Aes.Create();
|
||||
private readonly Logger Logger;
|
||||
public Security(Logger logger)
|
||||
|
||||
public Security()
|
||||
{
|
||||
Logger = logger;
|
||||
ClientAes.GenerateKey();
|
||||
ClientAes.GenerateIV();
|
||||
}
|
||||
|
||||
public RSA ServerRSA { get; set; }
|
||||
public Aes ClientAes { get; set; } = Aes.Create();
|
||||
|
||||
public void GetSymmetricKeysCrypted(out byte[] cryptedKey, out byte[] cryptedIV)
|
||||
{
|
||||
// Logger?.Debug($"Aes.Key:{ClientAes.Key.Dump()}, Aes.IV:{ClientAes.IV.Dump()}");
|
||||
cryptedKey = ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1);
|
||||
cryptedIV = ServerRSA.Encrypt(ClientAes.IV, RSAEncryptionPadding.Pkcs1);
|
||||
}
|
||||
|
||||
public byte[] Encrypt(byte[] data)
|
||||
{
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read).ReadToEnd();
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read)
|
||||
.ReadToEnd();
|
||||
}
|
||||
|
||||
public byte[] Decrypt(byte[] data)
|
||||
{
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateDecryptor(), CryptoStreamMode.Read).ReadToEnd();
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateDecryptor(), CryptoStreamMode.Read)
|
||||
.ReadToEnd();
|
||||
}
|
||||
|
||||
public void SetServerPublicKey(byte[] modulus, byte[] exponent)
|
||||
{
|
||||
var para = new RSAParameters();
|
||||
@@ -35,6 +42,7 @@ namespace RageCoop.Client
|
||||
para.Exponent = exponent;
|
||||
ServerRSA = RSA.Create(para);
|
||||
}
|
||||
|
||||
public void Regen()
|
||||
{
|
||||
ClientAes = Aes.Create();
|
||||
@@ -42,4 +50,4 @@ namespace RageCoop.Client
|
||||
ClientAes.GenerateIV();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-36
@@ -1,57 +1,64 @@
|
||||
using GTA;
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using LemonUI.Elements;
|
||||
using RageCoop.Core;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// ?
|
||||
/// </summary>
|
||||
public partial class SyncedPed : SyncedEntity
|
||||
{
|
||||
internal Blip PedBlip = null;
|
||||
private readonly string[] _currentAnimation = new string[2] { "", "" };
|
||||
private byte[] _lastClothes;
|
||||
private bool _lastDriveBy;
|
||||
private bool _lastInCover;
|
||||
private bool _lastIsJumping;
|
||||
private WeaponHash _lastWeaponHash;
|
||||
private bool _lastMoving;
|
||||
private bool _lastRagdoll;
|
||||
private ulong _lastRagdollTime;
|
||||
private Dictionary<uint, bool> _lastWeaponComponents;
|
||||
private ScaledText _nameTag;
|
||||
internal Entity WeaponObj;
|
||||
internal BlipColor BlipColor = (BlipColor)255;
|
||||
internal BlipSprite BlipSprite = 0;
|
||||
internal float BlipScale = 1;
|
||||
internal BlipSprite BlipSprite = 0;
|
||||
internal PedDataFlags Flags;
|
||||
internal Blip PedBlip = null;
|
||||
internal VehicleSeat Seat;
|
||||
|
||||
internal int VehicleID
|
||||
{
|
||||
get => CurrentVehicle?.ID ?? 0;
|
||||
set
|
||||
{
|
||||
if (CurrentVehicle == null || value != CurrentVehicle?.ID)
|
||||
{
|
||||
CurrentVehicle = EntityPool.GetVehicleByID(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal SyncedVehicle CurrentVehicle { get; private set; }
|
||||
internal VehicleSeat Seat;
|
||||
public bool IsPlayer { get => OwnerID == ID && ID != 0; }
|
||||
public bool IsPlayer => OwnerID == ID && ID != 0;
|
||||
public Ped MainPed { get; internal set; }
|
||||
internal int Health { get; set; }
|
||||
internal int Health;
|
||||
|
||||
internal Vector3 HeadPosition { get; set; }
|
||||
internal Vector3 RightFootPosition { get; set; }
|
||||
internal Vector3 LeftFootPosition { get; set; }
|
||||
internal Vector3 HeadPosition;
|
||||
internal Vector3 RightFootPosition;
|
||||
internal Vector3 LeftFootPosition;
|
||||
|
||||
internal byte WeaponTint { get; set; }
|
||||
private bool _lastRagdoll = false;
|
||||
private ulong _lastRagdollTime = 0;
|
||||
private bool _lastInCover = false;
|
||||
private byte[] _lastClothes = null;
|
||||
internal byte[] Clothes { get; set; }
|
||||
internal byte WeaponTint;
|
||||
internal byte[] Clothes;
|
||||
|
||||
internal float Heading { get; set; }
|
||||
internal float Heading;
|
||||
|
||||
internal ulong LastSpeakingTime { get; set; } = 0;
|
||||
internal bool IsSpeaking { get; set; } = false;
|
||||
public byte Speed { get; set; }
|
||||
private bool _lastIsJumping = false;
|
||||
internal PedDataFlags Flags;
|
||||
|
||||
internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming);
|
||||
internal bool _lastDriveBy;
|
||||
internal bool IsReloading => Flags.HasPedFlag(PedDataFlags.IsReloading);
|
||||
internal bool IsJumping => Flags.HasPedFlag(PedDataFlags.IsJumping);
|
||||
internal bool IsRagdoll => Flags.HasPedFlag(PedDataFlags.IsRagdoll);
|
||||
@@ -65,17 +72,10 @@ namespace RageCoop.Client
|
||||
internal bool IsInCoverFacingLeft => Flags.HasPedFlag(PedDataFlags.IsInCoverFacingLeft);
|
||||
internal bool IsBlindFiring => Flags.HasPedFlag(PedDataFlags.IsBlindFiring);
|
||||
internal bool IsInStealthMode => Flags.HasPedFlag(PedDataFlags.IsInStealthMode);
|
||||
internal Prop ParachuteProp { get; set; } = null;
|
||||
internal uint CurrentWeaponHash { get; set; }
|
||||
private Dictionary<uint, bool> _lastWeaponComponents = null;
|
||||
internal Dictionary<uint, bool> WeaponComponents { get; set; } = null;
|
||||
private Entity _weaponObj;
|
||||
internal Vector3 AimCoords { get; set; }
|
||||
|
||||
|
||||
private readonly string[] _currentAnimation = new string[2] { "", "" };
|
||||
|
||||
private bool LastMoving;
|
||||
|
||||
internal Prop ParachuteProp = null;
|
||||
internal WeaponHash CurrentWeapon = WeaponHash.Unarmed;
|
||||
internal VehicleWeaponHash VehicleWeapon = VehicleWeaponHash.Invalid;
|
||||
internal Dictionary<uint, bool> WeaponComponents = null;
|
||||
internal Vector3 AimCoords;
|
||||
}
|
||||
}
|
||||
}
|
||||
+211
-235
@@ -1,23 +1,24 @@
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using LemonUI.Elements;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.NaturalMotion;
|
||||
using GTA.UI;
|
||||
using LemonUI.Elements;
|
||||
using RageCoop.Core;
|
||||
using Font = GTA.UI.Font;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// ?
|
||||
/// </summary>
|
||||
public partial class SyncedPed : SyncedEntity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Create a local entity (outgoing sync)
|
||||
/// Create a local entity (outgoing sync)
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
internal SyncedPed(Ped p)
|
||||
@@ -26,62 +27,53 @@ namespace RageCoop.Client
|
||||
p.CanWrithe = false;
|
||||
p.IsOnlyDamagedByPlayer = false;
|
||||
MainPed = p;
|
||||
OwnerID = Main.LocalPlayerID;
|
||||
OwnerID = LocalPlayerID;
|
||||
|
||||
Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
|
||||
// MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableMelee, true);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty character with ID
|
||||
/// Create an empty character with ID
|
||||
/// </summary>
|
||||
internal SyncedPed(int id)
|
||||
{
|
||||
ID = id;
|
||||
LastSynced = Main.Ticked;
|
||||
LastSynced = Ticked;
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
if (Owner == null) { OwnerID = OwnerID; return; }
|
||||
if (IsPlayer)
|
||||
if (Owner == null)
|
||||
{
|
||||
RenderNameTag();
|
||||
OwnerID = OwnerID;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if all data avalible
|
||||
if (!IsReady) { return; }
|
||||
if (IsPlayer) RenderNameTag();
|
||||
|
||||
// Check if all data available
|
||||
if (!IsReady) return;
|
||||
|
||||
// Skip update if no new sync message has arrived.
|
||||
if (!NeedUpdate) { return; }
|
||||
if (!NeedUpdate) return;
|
||||
|
||||
if (MainPed == null || !MainPed.Exists())
|
||||
{
|
||||
if (!CreateCharacter())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to update state
|
||||
if (LastFullSynced >= LastUpdated)
|
||||
{
|
||||
if (MainPed != null && (Model != MainPed.Model.Hash))
|
||||
{
|
||||
if (MainPed != null && Model != MainPed.Model.Hash)
|
||||
if (!CreateCharacter())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (((byte)BlipColor == 255) && (PedBlip != null))
|
||||
if (!Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255;
|
||||
if ((byte)BlipColor == 255 && PedBlip != null)
|
||||
{
|
||||
PedBlip.Delete();
|
||||
PedBlip = null;
|
||||
}
|
||||
else if (((byte)BlipColor != 255) && PedBlip == null)
|
||||
else if ((byte)BlipColor != 255 && PedBlip == null)
|
||||
{
|
||||
PedBlip = MainPed.AddBlip();
|
||||
|
||||
@@ -90,26 +82,15 @@ namespace RageCoop.Client
|
||||
PedBlip.Sprite = BlipSprite;
|
||||
PedBlip.Scale = BlipScale;
|
||||
}
|
||||
|
||||
if (PedBlip != null)
|
||||
{
|
||||
if (PedBlip.Color != BlipColor)
|
||||
{
|
||||
PedBlip.Color = BlipColor;
|
||||
}
|
||||
if (PedBlip.Sprite != BlipSprite)
|
||||
{
|
||||
PedBlip.Sprite = BlipSprite;
|
||||
}
|
||||
if (IsPlayer)
|
||||
{
|
||||
PedBlip.Name = Owner.Username;
|
||||
}
|
||||
if (PedBlip.Color != BlipColor) PedBlip.Color = BlipColor;
|
||||
if (PedBlip.Sprite != BlipSprite) PedBlip.Sprite = BlipSprite;
|
||||
if (IsPlayer) PedBlip.Name = Owner.Username;
|
||||
}
|
||||
|
||||
if (!Clothes.SequenceEqual(_lastClothes))
|
||||
{
|
||||
SetClothes();
|
||||
}
|
||||
if (!Clothes.SequenceEqual(_lastClothes)) SetClothes();
|
||||
|
||||
CheckCurrentWeapon();
|
||||
}
|
||||
@@ -119,16 +100,12 @@ namespace RageCoop.Client
|
||||
if (Health > 0)
|
||||
{
|
||||
if (IsPlayer)
|
||||
{
|
||||
MainPed.Resurrect();
|
||||
}
|
||||
else
|
||||
{
|
||||
SyncEvents.TriggerPedKilled(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (IsPlayer && (MainPed.Health != Health))
|
||||
else if (IsPlayer && MainPed.Health != Health)
|
||||
{
|
||||
MainPed.Health = Health;
|
||||
|
||||
@@ -146,13 +123,18 @@ namespace RageCoop.Client
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MainPed.IsInVehicle()) { MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut); return; }
|
||||
if (MainPed.IsInVehicle())
|
||||
{
|
||||
MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayOnFoot();
|
||||
}
|
||||
|
||||
if (IsSpeaking)
|
||||
{
|
||||
if (Main.Ticked - LastSpeakingTime < 10)
|
||||
if (Ticked - LastSpeakingTime < 10)
|
||||
{
|
||||
DisplaySpeaking(true);
|
||||
}
|
||||
@@ -165,27 +147,36 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
|
||||
LastUpdated = Main.Ticked;
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
|
||||
private void RenderNameTag()
|
||||
{
|
||||
if (!Owner.DisplayNameTag || (MainPed == null) || !MainPed.IsVisible || !MainPed.IsInRange(Main.PlayerPosition, 40f))
|
||||
{
|
||||
if (!Owner.DisplayNameTag || !Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible)
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 targetPos = MainPed.Bones[Bone.IKHead].Position;
|
||||
float dist = Call<Vector3>(GET_GAMEPLAY_CAM_COORD).DistanceToSquared(MainPed.Position);
|
||||
if (dist > 10 * 10f)
|
||||
return;
|
||||
|
||||
float scale = 1f - (0.8f * (float)Math.Sqrt(dist)) / 20f;
|
||||
float fontSize = 0.6f * scale;
|
||||
float frameTime = Call<float>(GET_FRAME_TIME);
|
||||
Vector3 headPos = MainPed.Bones[Bone.IKHead].Position;
|
||||
headPos.Z += 0.5f;
|
||||
headPos += Velocity * frameTime;
|
||||
|
||||
Point toDraw = default;
|
||||
if (Util.WorldToScreen(targetPos, ref toDraw))
|
||||
if (Util.WorldToScreen(headPos, ref toDraw))
|
||||
{
|
||||
toDraw.Y -= 100;
|
||||
new ScaledText(toDraw, Owner.Username, 0.4f, GTA.UI.Font.ChaletLondon)
|
||||
_nameTag ??= new ScaledText(toDraw, Owner.Username, fontSize, Font.ChaletLondon)
|
||||
{
|
||||
Outline = true,
|
||||
Alignment = GTA.UI.Alignment.Center,
|
||||
Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White,
|
||||
}.Draw();
|
||||
Alignment = Alignment.Center,
|
||||
Outline = true
|
||||
};
|
||||
_nameTag.Position = toDraw;
|
||||
_nameTag.Scale = fontSize;
|
||||
_nameTag.Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White;
|
||||
_nameTag.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +186,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (MainPed.Exists())
|
||||
{
|
||||
// Main.Logger.Debug($"Removing ped {ID}. Reason:CreateCharacter");
|
||||
// Log.Debug($"Removing ped {ID}. Reason:CreateCharacter");
|
||||
MainPed.Kill();
|
||||
MainPed.MarkAsNoLongerNeeded();
|
||||
MainPed.Delete();
|
||||
@@ -209,16 +200,14 @@ namespace RageCoop.Client
|
||||
PedBlip.Delete();
|
||||
PedBlip = null;
|
||||
}
|
||||
|
||||
if (!Model.IsLoaded)
|
||||
{
|
||||
Model.Request();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((MainPed = Util.CreatePed(Model, Position)) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((MainPed = Util.CreatePed(Model, Position)) == null) return false;
|
||||
|
||||
Model.MarkAsNoLongerNeeded();
|
||||
|
||||
@@ -226,17 +215,17 @@ namespace RageCoop.Client
|
||||
MainPed.CanWrithe = false;
|
||||
MainPed.CanBeDraggedOutOfVehicle = true;
|
||||
MainPed.IsOnlyDamagedByPlayer = false;
|
||||
MainPed.RelationshipGroup = Main.SyncedPedsGroup;
|
||||
MainPed.RelationshipGroup = SyncedPedsGroup;
|
||||
MainPed.IsFireProof = false;
|
||||
MainPed.IsExplosionProof = false;
|
||||
|
||||
Function.Call(Hash.SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
|
||||
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
|
||||
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
|
||||
Function.Call(Hash.SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
|
||||
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
|
||||
Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
|
||||
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
|
||||
Call(SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
|
||||
Call(SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
|
||||
Call(SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
|
||||
Call(SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
|
||||
Call(SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
|
||||
// Call(_SET_PED_CAN_PLAY_INJURED_ANIMS, false);
|
||||
Call(SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
|
||||
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater, false);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
|
||||
@@ -251,8 +240,8 @@ namespace RageCoop.Client
|
||||
|
||||
SetClothes();
|
||||
|
||||
if (IsPlayer) { MainPed.IsInvincible = true; }
|
||||
if (IsInvincible) { MainPed.IsInvincible = true; }
|
||||
if (IsPlayer) MainPed.IsInvincible = true;
|
||||
if (IsInvincible) MainPed.IsInvincible = true;
|
||||
|
||||
lock (EntityPool.PedsLock)
|
||||
{
|
||||
@@ -266,25 +255,27 @@ namespace RageCoop.Client
|
||||
private void SetClothes()
|
||||
{
|
||||
for (byte i = 0; i < 12; i++)
|
||||
{
|
||||
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i], (int)Clothes[i + 12], (int)Clothes[i + 24]);
|
||||
}
|
||||
Call(SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i],
|
||||
(int)Clothes[i + 12], (int)Clothes[i + 24]);
|
||||
_lastClothes = Clothes;
|
||||
}
|
||||
|
||||
|
||||
private void DisplayOnFoot()
|
||||
{
|
||||
|
||||
if (IsInParachuteFreeFall)
|
||||
{
|
||||
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
|
||||
MainPed.Quaternion = Rotation.ToQuaternion();
|
||||
MainPed.Rotation = Rotation;
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
|
||||
{
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, LoadAnim("skydive@base"), "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
// Skip update if animation is not loaded
|
||||
var dict = LoadAnim("skydive@base");
|
||||
if (dict == null) return;
|
||||
Call(TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -296,33 +287,36 @@ namespace RageCoop.Client
|
||||
model.Request(1000);
|
||||
if (model != null)
|
||||
{
|
||||
ParachuteProp = World.CreateProp(model, MainPed.ReadPosition(), MainPed.ReadRotation(), false, false);
|
||||
ParachuteProp = World.CreateProp(model, MainPed.ReadPosition(), MainPed.ReadRotation(), false,
|
||||
false);
|
||||
model.MarkAsNoLongerNeeded();
|
||||
ParachuteProp.IsPositionFrozen = true;
|
||||
ParachuteProp.IsCollisionEnabled = false;
|
||||
|
||||
ParachuteProp.AttachTo(MainPed.Bones[Bone.SkelSpine2], new Vector3(3.6f, 0f, 0f), new Vector3(0f, 90f, 0f));
|
||||
ParachuteProp.AttachTo(MainPed.Bones[Bone.SkelSpine2], new Vector3(3.6f, 0f, 0f),
|
||||
new Vector3(0f, 90f, 0f));
|
||||
}
|
||||
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
MainPed.Task.ClearSecondary();
|
||||
}
|
||||
|
||||
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
|
||||
MainPed.Quaternion = Rotation.ToQuaternion();
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3))
|
||||
MainPed.Rotation = Rotation;
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person",
|
||||
"chute_idle_right", 3))
|
||||
{
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim("skydive@parachute@first_person"), "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
var dict = LoadAnim("skydive@parachute@first_person");
|
||||
if (dict == null) return;
|
||||
Call(TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ParachuteProp != null)
|
||||
{
|
||||
if (ParachuteProp.Exists())
|
||||
{
|
||||
ParachuteProp.Delete();
|
||||
}
|
||||
if (ParachuteProp.Exists()) ParachuteProp.Delete();
|
||||
ParachuteProp = null;
|
||||
}
|
||||
|
||||
@@ -330,17 +324,15 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (Velocity.Z < 0)
|
||||
{
|
||||
string anim = Velocity.Z < -2f ? "slide_climb_down" : "climb_down";
|
||||
var anim = Velocity.Z < -2f ? "slide_climb_down" : "climb_down";
|
||||
if (_currentAnimation[1] != anim)
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_currentAnimation[1] = anim;
|
||||
}
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", anim, 3))
|
||||
{
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", anim, 3))
|
||||
MainPed.Task.PlayAnimation("laddersbase", anim, 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -352,10 +344,9 @@ namespace RageCoop.Client
|
||||
_currentAnimation[1] = "base_left_hand_up";
|
||||
}
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "base_left_hand_up", 3))
|
||||
{
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase",
|
||||
"base_left_hand_up", 3))
|
||||
MainPed.Task.PlayAnimation("laddersbase", "base_left_hand_up", 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -365,17 +356,16 @@ namespace RageCoop.Client
|
||||
_currentAnimation[1] = "climb_up";
|
||||
}
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up", 3))
|
||||
{
|
||||
MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up",
|
||||
3)) MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
}
|
||||
|
||||
SmoothTransition();
|
||||
return;
|
||||
}
|
||||
else if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
|
||||
|
||||
if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_currentAnimation[1] = "";
|
||||
@@ -383,28 +373,18 @@ namespace RageCoop.Client
|
||||
|
||||
if (IsVaulting)
|
||||
{
|
||||
if (!MainPed.IsVaulting)
|
||||
{
|
||||
MainPed.Task.Climb();
|
||||
}
|
||||
if (!MainPed.IsVaulting) MainPed.Task.Climb();
|
||||
|
||||
SmoothTransition();
|
||||
|
||||
return;
|
||||
}
|
||||
if (!IsVaulting && MainPed.IsVaulting)
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
}
|
||||
|
||||
if (!IsVaulting && MainPed.IsVaulting) MainPed.Task.ClearAllImmediately();
|
||||
|
||||
if (IsOnFire && !MainPed.IsOnFire)
|
||||
{
|
||||
Function.Call(Hash.START_ENTITY_FIRE, MainPed);
|
||||
}
|
||||
else if (!IsOnFire && MainPed.IsOnFire)
|
||||
{
|
||||
Function.Call(Hash.STOP_ENTITY_FIRE, MainPed);
|
||||
}
|
||||
Call(START_ENTITY_FIRE, MainPed);
|
||||
else if (!IsOnFire && MainPed.IsOnFire) Call(STOP_ENTITY_FIRE, MainPed);
|
||||
|
||||
if (IsJumping)
|
||||
{
|
||||
@@ -417,42 +397,35 @@ namespace RageCoop.Client
|
||||
SmoothTransition();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastIsJumping = false;
|
||||
|
||||
if (IsRagdoll || Health == 0)
|
||||
{
|
||||
if (!MainPed.IsRagdoll)
|
||||
{
|
||||
MainPed.Ragdoll();
|
||||
}
|
||||
if (!MainPed.IsRagdoll) MainPed.Ragdoll();
|
||||
SmoothTransition();
|
||||
if (!_lastRagdoll)
|
||||
{
|
||||
_lastRagdoll = true;
|
||||
_lastRagdollTime = Main.Ticked;
|
||||
_lastRagdollTime = Ticked;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (MainPed.IsRagdoll)
|
||||
{
|
||||
if (Speed == 0)
|
||||
{
|
||||
MainPed.CancelRagdoll();
|
||||
}
|
||||
else
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
}
|
||||
_lastRagdoll = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsReloading)
|
||||
{
|
||||
if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun))
|
||||
{
|
||||
MainPed.Task.ReloadWeapon();
|
||||
}
|
||||
if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun)) MainPed.Task.ReloadWeapon();
|
||||
/*
|
||||
if (!_isPlayingAnimation)
|
||||
{
|
||||
@@ -469,10 +442,7 @@ namespace RageCoop.Client
|
||||
}
|
||||
else if (IsInCover)
|
||||
{
|
||||
if (!_lastInCover)
|
||||
{
|
||||
Function.Call(Hash.TASK_STAY_IN_COVER, MainPed.Handle);
|
||||
}
|
||||
if (!_lastInCover) Call(TASK_STAY_IN_COVER, MainPed.Handle);
|
||||
|
||||
_lastInCover = true;
|
||||
if (IsAiming)
|
||||
@@ -506,99 +476,110 @@ namespace RageCoop.Client
|
||||
|
||||
private void CheckCurrentWeapon()
|
||||
{
|
||||
if (MainPed.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash || !WeaponComponents.Compare(_lastWeaponComponents) || (Speed <= 3 && _weaponObj?.IsVisible != true))
|
||||
if (MainPed.VehicleWeapon != VehicleWeapon) MainPed.VehicleWeapon = VehicleWeapon;
|
||||
var compChanged = WeaponComponents != null && WeaponComponents.Count != 0 && WeaponComponents != _lastWeaponComponents && !WeaponComponents.Compare(_lastWeaponComponents);
|
||||
if (_lastWeaponHash != CurrentWeapon || compChanged)
|
||||
{
|
||||
new WeaponAsset(CurrentWeaponHash).Request();
|
||||
|
||||
MainPed.Weapons.RemoveAll();
|
||||
_weaponObj = Entity.FromHandle(Function.Call<int>(Hash.CREATE_WEAPON_OBJECT, CurrentWeaponHash, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
|
||||
if (_weaponObj == null) { return; }
|
||||
if (CurrentWeaponHash != (uint)WeaponHash.Unarmed)
|
||||
if (_lastWeaponHash == WeaponHash.Unarmed && WeaponObj?.Exists() == true)
|
||||
{
|
||||
if (WeaponComponents != null && WeaponComponents.Count != 0)
|
||||
WeaponObj.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
var model = Call<uint>(GET_WEAPONTYPE_MODEL, CurrentWeapon);
|
||||
if (!Call<bool>(HAS_MODEL_LOADED, model))
|
||||
{
|
||||
foreach (KeyValuePair<uint, bool> comp in WeaponComponents)
|
||||
Call(REQUEST_MODEL, model);
|
||||
return;
|
||||
}
|
||||
if (WeaponObj?.Exists() == true)
|
||||
WeaponObj.Delete();
|
||||
MainPed.Weapons.RemoveAll();
|
||||
WeaponObj = Entity.FromHandle(Call<int>(CREATE_WEAPON_OBJECT, CurrentWeapon, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
|
||||
}
|
||||
|
||||
if (compChanged)
|
||||
{
|
||||
foreach (var comp in WeaponComponents)
|
||||
{
|
||||
if (comp.Value)
|
||||
{
|
||||
if (comp.Value)
|
||||
{
|
||||
Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, _weaponObj, comp.Key);
|
||||
}
|
||||
Call(GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, WeaponObj.Handle, comp.Key);
|
||||
}
|
||||
}
|
||||
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _weaponObj, MainPed.Handle);
|
||||
_lastWeaponComponents = WeaponComponents;
|
||||
}
|
||||
_lastWeaponComponents = WeaponComponents;
|
||||
}
|
||||
if (Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeaponHash) != WeaponTint)
|
||||
{
|
||||
Function.Call<int>(Hash.SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeaponHash, WeaponTint);
|
||||
Call(GIVE_WEAPON_OBJECT_TO_PED, WeaponObj.Handle, MainPed.Handle);
|
||||
_lastWeaponHash = CurrentWeapon;
|
||||
}
|
||||
|
||||
if (Call<int>(GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon) != WeaponTint)
|
||||
Call<int>(SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon, WeaponTint);
|
||||
}
|
||||
|
||||
private void DisplayAiming()
|
||||
{
|
||||
if (Velocity == default)
|
||||
{
|
||||
MainPed.Task.AimAt(AimCoords, 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
Function.Call(Hash.TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
|
||||
Position.X + Velocity.X, Position.Y + Velocity.Y, Position.Z + Velocity.Z,
|
||||
AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 0x3F000000, 0x40800000, false, 512, false, 0);
|
||||
|
||||
}
|
||||
Call(TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
|
||||
Position.X + Velocity.X, Position.Y + Velocity.Y, Position.Z + Velocity.Z,
|
||||
AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 0x3F000000, 0x40800000, false, 512, false, 0);
|
||||
SmoothTransition();
|
||||
}
|
||||
|
||||
private void WalkTo()
|
||||
{
|
||||
MainPed.Task.ClearAll();
|
||||
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
|
||||
Vector3 predictPosition = Predict(Position) + Velocity;
|
||||
float range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
|
||||
Call(SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
|
||||
var predictPosition = Predict(Position) + Velocity;
|
||||
var range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
|
||||
|
||||
switch (Speed)
|
||||
{
|
||||
case 1:
|
||||
if (!MainPed.IsWalking || range > 0.25f)
|
||||
{
|
||||
float nrange = range * 2;
|
||||
if (nrange > 1.0f)
|
||||
{
|
||||
nrange = 1.0f;
|
||||
}
|
||||
var nrange = range * 2;
|
||||
if (nrange > 1.0f) nrange = 1.0f;
|
||||
|
||||
MainPed.Task.GoStraightTo(predictPosition);
|
||||
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, nrange);
|
||||
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, nrange);
|
||||
}
|
||||
LastMoving = true;
|
||||
|
||||
_lastMoving = true;
|
||||
break;
|
||||
case 2:
|
||||
if (!MainPed.IsRunning || range > 0.50f)
|
||||
{
|
||||
MainPed.Task.RunTo(predictPosition, true);
|
||||
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
|
||||
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
|
||||
}
|
||||
LastMoving = true;
|
||||
|
||||
_lastMoving = true;
|
||||
break;
|
||||
case 3:
|
||||
if (!MainPed.IsSprinting || range > 0.75f)
|
||||
{
|
||||
Function.Call(Hash.TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X, predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
|
||||
Function.Call(Hash.SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f);
|
||||
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
|
||||
Call(TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X,
|
||||
predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
|
||||
Call(SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f);
|
||||
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
|
||||
}
|
||||
LastMoving = true;
|
||||
|
||||
_lastMoving = true;
|
||||
break;
|
||||
default:
|
||||
if (LastMoving)
|
||||
if (_lastMoving)
|
||||
{
|
||||
MainPed.Task.StandStill(2000);
|
||||
LastMoving = false;
|
||||
_lastMoving = false;
|
||||
}
|
||||
|
||||
if (MainPed.IsTaskActive(TaskType.CTaskDiveToGround)) MainPed.Task.ClearAll();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
SmoothTransition();
|
||||
}
|
||||
|
||||
@@ -612,26 +593,28 @@ namespace RageCoop.Client
|
||||
MainPed.PositionNoOffset = predicted;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(localRagdoll || MainPed.IsDead))
|
||||
{
|
||||
if (!IsAiming && !MainPed.IsGettingUp)
|
||||
{
|
||||
var cur = MainPed.Heading;
|
||||
var diff = Heading - cur;
|
||||
if (diff > 180) { diff -= 360; }
|
||||
else if (diff < -180) { diff += 360; }
|
||||
if (diff > 180)
|
||||
diff -= 360;
|
||||
else if (diff < -180) diff += 360;
|
||||
|
||||
MainPed.Heading = cur + diff / 2;
|
||||
}
|
||||
|
||||
MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
|
||||
}
|
||||
else if (Main.Ticked - _lastRagdollTime < 10)
|
||||
else if (Ticked - _lastRagdollTime < 10)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (IsRagdoll)
|
||||
{
|
||||
var helper = new GTA.NaturalMotion.ApplyImpulseHelper(MainPed);
|
||||
var helper = new ApplyImpulseHelper(MainPed);
|
||||
var head = MainPed.Bones[Bone.SkelHead];
|
||||
var rightFoot = MainPed.Bones[Bone.SkelRightFoot];
|
||||
var leftFoot = MainPed.Bones[Bone.SkelLeftFoot];
|
||||
@@ -639,7 +622,7 @@ namespace RageCoop.Client
|
||||
// 20:head, 3:left foot, 6:right foot, 17:right hand,
|
||||
|
||||
amount = 20 * (Predict(HeadPosition) - head.Position);
|
||||
if (amount.Length() > 50) { amount = amount.Normalized * 50; }
|
||||
if (amount.Length() > 50) amount = amount.Normalized * 50;
|
||||
helper.EqualizeAmount = 1;
|
||||
helper.PartIndex = 20;
|
||||
helper.Impulse = amount;
|
||||
@@ -647,7 +630,7 @@ namespace RageCoop.Client
|
||||
helper.Stop();
|
||||
|
||||
amount = 20 * (Predict(RightFootPosition) - rightFoot.Position);
|
||||
if (amount.Length() > 50) { amount = amount.Normalized * 50; }
|
||||
if (amount.Length() > 50) amount = amount.Normalized * 50;
|
||||
helper.EqualizeAmount = 1;
|
||||
helper.PartIndex = 6;
|
||||
helper.Impulse = amount;
|
||||
@@ -655,7 +638,7 @@ namespace RageCoop.Client
|
||||
helper.Stop();
|
||||
|
||||
amount = 20 * (Predict(LeftFootPosition) - leftFoot.Position);
|
||||
if (amount.Length() > 50) { amount = amount.Normalized * 50; }
|
||||
if (amount.Length() > 50) amount = amount.Normalized * 50;
|
||||
helper.EqualizeAmount = 1;
|
||||
helper.PartIndex = 3;
|
||||
helper.Impulse = amount;
|
||||
@@ -666,36 +649,37 @@ namespace RageCoop.Client
|
||||
{
|
||||
// localRagdoll
|
||||
var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
|
||||
if (force.Length() > 20) { force = force.Normalized * 20; }
|
||||
MainPed.ApplyForce(force);
|
||||
if (force.Length() > 20) force = force.Normalized * 20;
|
||||
MainPed.ApplyWorldForceCenterOfMass(force, ForceType.InternalImpulse, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayInVehicle()
|
||||
{
|
||||
if (CurrentVehicle?.MainVehicle == null) { return; }
|
||||
if (CurrentVehicle?.MainVehicle == null) return;
|
||||
switch (Speed)
|
||||
{
|
||||
// In vehicle
|
||||
case 4:
|
||||
if (MainPed.CurrentVehicle != CurrentVehicle.MainVehicle || MainPed.SeatIndex != Seat || (!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
|
||||
{
|
||||
if (MainPed.CurrentVehicle != CurrentVehicle.MainVehicle || MainPed.SeatIndex != Seat ||
|
||||
(!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
|
||||
MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
|
||||
}
|
||||
if (MainPed.IsOnTurretSeat())
|
||||
Call(TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y,
|
||||
AimCoords.Z);
|
||||
|
||||
// Drive-by
|
||||
if (VehicleWeapon == VehicleWeaponHash.Invalid)
|
||||
{
|
||||
// Function.Call(Hash.SET_VEHICLE_TURRET_SPEED_THIS_FRAME, MainPed.CurrentVehicle, 100);
|
||||
Function.Call(Hash.TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y, AimCoords.Z);
|
||||
}
|
||||
if (MainPed.VehicleWeapon == VehicleWeaponHash.Invalid)
|
||||
{
|
||||
// World.DrawMarker(MarkerType.DebugSphere,AimCoords,default,default,new Vector3(0.2f,0.2f,0.2f),Color.AliceBlue);
|
||||
if (IsAiming)
|
||||
{
|
||||
Function.Call(Hash.SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z);
|
||||
Call(SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y,
|
||||
AimCoords.Z);
|
||||
if (!_lastDriveBy)
|
||||
{
|
||||
_lastDriveBy = true;
|
||||
Function.Call(Hash.TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z, 1, 100, 1, FiringPattern.SingleShot);
|
||||
Call(TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z,
|
||||
100000, 100, 1, FiringPattern.SingleShot);
|
||||
}
|
||||
}
|
||||
else if (_lastDriveBy || MainPed.IsTaskActive(TaskType.CTaskAimGunVehicleDriveBy))
|
||||
@@ -703,33 +687,25 @@ namespace RageCoop.Client
|
||||
MainPed.Task.ClearAll();
|
||||
_lastDriveBy = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
else if (MainPed.VehicleWeapon != (VehicleWeaponHash)CurrentWeaponHash)
|
||||
{
|
||||
MainPed.VehicleWeapon = (VehicleWeaponHash)CurrentWeaponHash;
|
||||
}
|
||||
break;
|
||||
// Entering vehicle
|
||||
case 5:
|
||||
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle || MainPed.GetSeatTryingToEnter() != Seat)
|
||||
{
|
||||
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5, EnterVehicleFlags.AllowJacking);
|
||||
}
|
||||
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle ||
|
||||
MainPed.GetSeatTryingToEnter() != Seat)
|
||||
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5,
|
||||
EnterVehicleFlags.JackAnyone);
|
||||
break;
|
||||
|
||||
// Leaving vehicle
|
||||
case 6:
|
||||
if (!MainPed.IsTaskActive(TaskType.CTaskExitVehicle))
|
||||
{
|
||||
MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.Length() > 5f ? LeaveVehicleFlags.BailOut : LeaveVehicleFlags.None);
|
||||
}
|
||||
MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.LengthSquared() > 5 * 5
|
||||
? LeaveVehicleFlags.BailOut
|
||||
: LeaveVehicleFlags.None);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, P,true, 0);
|
||||
return Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, P);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
-62
@@ -1,116 +1,131 @@
|
||||
using GTA;
|
||||
using System.Diagnostics;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract class SyncedEntity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current player is responsible for syncing this entity.
|
||||
/// </summary>
|
||||
public bool IsLocal
|
||||
{
|
||||
get => OwnerID == Main.LocalPlayerID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Network ID for this entity
|
||||
/// </summary>
|
||||
public int ID { get; internal set; }
|
||||
private float _accumulatedOff;
|
||||
|
||||
|
||||
private int _ownerID;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current player is responsible for syncing this entity.
|
||||
/// </summary>
|
||||
public bool IsLocal => OwnerID == LocalPlayerID;
|
||||
|
||||
/// <summary>
|
||||
/// Network ID for this entity
|
||||
/// </summary>
|
||||
public int ID { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int OwnerID
|
||||
{
|
||||
get => _ownerID;
|
||||
internal set
|
||||
{
|
||||
if (value == _ownerID && Owner != null) { return; }
|
||||
if (value == _ownerID && Owner != null) return;
|
||||
_ownerID = value;
|
||||
Owner = PlayerList.GetPlayer(value);
|
||||
if (this is SyncedPed && Owner != null)
|
||||
{
|
||||
Owner.Character = ((SyncedPed)this);
|
||||
}
|
||||
if (this is SyncedPed && Owner != null) Owner.Character = (SyncedPed)this;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual Player Owner { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsOutOfSync
|
||||
{
|
||||
get => Main.Ticked - LastSynced > 200 && ID != 0;
|
||||
}
|
||||
internal bool IsReady
|
||||
{
|
||||
get => LastSynced > 0 || LastFullSynced == 0;
|
||||
}
|
||||
public bool IsOutOfSync => Ticked - LastSynced > 200 && ID != 0;
|
||||
|
||||
internal bool IsReady => LastSynced > 0 || LastFullSynced == 0;
|
||||
|
||||
internal bool IsInvincible { get; set; } = false;
|
||||
internal bool NeedUpdate
|
||||
{
|
||||
get => LastSynced >= LastUpdated;
|
||||
}
|
||||
#region LAST STATE
|
||||
/// <summary>
|
||||
/// Last time a new sync message arrived.
|
||||
/// </summary>
|
||||
public ulong LastSynced { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Last time a new sync message arrived.
|
||||
/// </summary>
|
||||
public ulong LastFullSynced { get; internal set; } = 0;
|
||||
/// <summary>
|
||||
/// Last time the local entity has been updated,
|
||||
/// </summary>
|
||||
public ulong LastUpdated { get; set; } = 0;
|
||||
|
||||
|
||||
internal Stopwatch LastSentStopWatch { get; set; } = Stopwatch.StartNew();
|
||||
#endregion
|
||||
internal bool NeedUpdate => LastSynced >= LastUpdated;
|
||||
|
||||
public bool SendNextFrame { get; set; } = false;
|
||||
public bool SendFullNextFrame { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected internal bool _lastFrozen = false;
|
||||
internal Model Model { get; set; }
|
||||
internal Vector3 Position { get; set; }
|
||||
internal Vector3 Rotation { get; set; }
|
||||
internal Quaternion Quaternion { get; set; }
|
||||
internal Vector3 Velocity { get; set; }
|
||||
public Stopwatch LastSyncedStopWatch = new Stopwatch();
|
||||
internal abstract void Update();
|
||||
|
||||
internal void PauseUpdate(ulong frames)
|
||||
{
|
||||
LastUpdated = Main.Ticked + frames;
|
||||
LastUpdated = Ticked + frames;
|
||||
}
|
||||
|
||||
protected Vector3 Predict(Vector3 input)
|
||||
{
|
||||
return (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Velocity + input;
|
||||
return Diff() + input;
|
||||
}
|
||||
private float _accumulatedOff = 0;
|
||||
protected Vector3 Diff()
|
||||
{
|
||||
return (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Velocity;
|
||||
}
|
||||
|
||||
protected bool IsOff(float thisOff, float tolerance = 3, float limit = 30)
|
||||
{
|
||||
_accumulatedOff += thisOff - tolerance;
|
||||
if (_accumulatedOff < 0) { _accumulatedOff = 0; }
|
||||
if (_accumulatedOff < 0)
|
||||
{
|
||||
_accumulatedOff = 0;
|
||||
}
|
||||
else if (_accumulatedOff >= limit)
|
||||
{
|
||||
_accumulatedOff = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region LAST STATE
|
||||
|
||||
public void SetLastSynced(bool full)
|
||||
{
|
||||
LastSyncInterval = LastSentStopWatch.ElapsedMilliseconds;
|
||||
LastSynced = Ticked;
|
||||
if (full)
|
||||
{
|
||||
LastFullSynced = Ticked;
|
||||
}
|
||||
LastSyncedStopWatch.Restart();
|
||||
}
|
||||
|
||||
public Stopwatch LastSyncedStopWatch = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
/// The interval between last sync and the earlier one
|
||||
/// </summary>
|
||||
public long LastSyncInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Last time a new sync message arrived.
|
||||
/// </summary>
|
||||
public ulong LastSynced { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Last time a new sync message arrived.
|
||||
/// </summary>
|
||||
public ulong LastFullSynced { get; internal set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Last time the local entity has been updated,
|
||||
/// </summary>
|
||||
public ulong LastUpdated { get; set; }
|
||||
|
||||
|
||||
internal Stopwatch LastSentStopWatch { get; set; } = Stopwatch.StartNew();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-55
@@ -7,57 +7,18 @@ namespace RageCoop.Client
|
||||
{
|
||||
internal class SyncedProjectile : SyncedEntity
|
||||
{
|
||||
public ProjectileDataFlags Flags { private get; set; } = ProjectileDataFlags.None;
|
||||
|
||||
public readonly Vector3 Origin;
|
||||
private bool _firstSend = false;
|
||||
private bool _firstSend;
|
||||
|
||||
|
||||
public bool IsValid { get; private set; } = true;
|
||||
public new bool IsLocal { get; private set; } = false;
|
||||
public Projectile MainProjectile { get; set; }
|
||||
public SyncedEntity Shooter { get; set; }
|
||||
public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded);
|
||||
|
||||
internal override Player Owner => Shooter.Owner;
|
||||
/// <summary>
|
||||
/// Invalid property for projectile.
|
||||
/// </summary>
|
||||
private new int OwnerID { set { } }
|
||||
public WeaponHash WeaponHash { get; set; }
|
||||
private WeaponAsset Asset { get; set; }
|
||||
public void ExtractData(ref Packets.ProjectileSync p)
|
||||
{
|
||||
p.Position = MainProjectile.Position;
|
||||
p.Velocity = MainProjectile.Velocity;
|
||||
p.Rotation = MainProjectile.Rotation;
|
||||
p.ID = ID;
|
||||
p.ShooterID = Shooter.ID;
|
||||
p.WeaponHash = (uint)MainProjectile.WeaponHash;
|
||||
p.Flags = ProjectileDataFlags.None;
|
||||
if (MainProjectile.IsDead)
|
||||
{
|
||||
p.Flags |= ProjectileDataFlags.Exploded;
|
||||
}
|
||||
if (MainProjectile.AttachedEntity != null)
|
||||
{
|
||||
p.Flags |= ProjectileDataFlags.IsAttached;
|
||||
}
|
||||
if (Shooter is SyncedVehicle)
|
||||
{
|
||||
p.Flags |= ProjectileDataFlags.IsShotByVehicle;
|
||||
}
|
||||
if (_firstSend)
|
||||
{
|
||||
p.Flags |= ProjectileDataFlags.IsAttached;
|
||||
_firstSend = false;
|
||||
}
|
||||
|
||||
}
|
||||
public SyncedProjectile(Projectile p)
|
||||
{
|
||||
var owner = p.OwnerEntity;
|
||||
if (owner == null) { IsValid = false; return; }
|
||||
if (owner == null)
|
||||
{
|
||||
IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ID = EntityPool.RequestNewID();
|
||||
MainProjectile = p;
|
||||
Origin = p.Position;
|
||||
@@ -65,12 +26,13 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (shooter.MainPed != null
|
||||
&& (p.AttachedEntity == shooter.MainPed.Weapons.CurrentWeaponObject
|
||||
|| p.AttachedEntity == shooter.MainPed))
|
||||
|| p.AttachedEntity == shooter.MainPed))
|
||||
{
|
||||
// Reloading
|
||||
IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Shooter = shooter;
|
||||
IsLocal = shooter.IsLocal;
|
||||
}
|
||||
@@ -84,37 +46,87 @@ namespace RageCoop.Client
|
||||
IsValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
public SyncedProjectile(int id)
|
||||
{
|
||||
ID = id;
|
||||
IsLocal = false;
|
||||
}
|
||||
|
||||
public ProjectileDataFlags Flags { private get; set; } = ProjectileDataFlags.None;
|
||||
|
||||
|
||||
public bool IsValid { get; } = true;
|
||||
public new bool IsLocal { get; }
|
||||
public Projectile MainProjectile { get; set; }
|
||||
public SyncedEntity Shooter { get; set; }
|
||||
public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded);
|
||||
|
||||
internal override Player Owner => Shooter.Owner;
|
||||
|
||||
/// <summary>
|
||||
/// Invalid property for projectile.
|
||||
/// </summary>
|
||||
private new int OwnerID
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
public WeaponHash WeaponHash { get; set; }
|
||||
private WeaponAsset Asset { get; set; }
|
||||
|
||||
public void ExtractData(ref Packets.ProjectileSync p)
|
||||
{
|
||||
p.Position = MainProjectile.Position;
|
||||
p.Velocity = MainProjectile.Velocity;
|
||||
p.Rotation = MainProjectile.Rotation;
|
||||
p.ID = ID;
|
||||
p.ShooterID = Shooter.ID;
|
||||
p.WeaponHash = (uint)MainProjectile.WeaponHash;
|
||||
p.Flags = ProjectileDataFlags.None;
|
||||
if (MainProjectile.IsDead) p.Flags |= ProjectileDataFlags.Exploded;
|
||||
if (MainProjectile.AttachedEntity != null) p.Flags |= ProjectileDataFlags.IsAttached;
|
||||
if (Shooter is SyncedVehicle) p.Flags |= ProjectileDataFlags.IsShotByVehicle;
|
||||
if (_firstSend)
|
||||
{
|
||||
p.Flags |= ProjectileDataFlags.IsAttached;
|
||||
_firstSend = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
|
||||
// Skip update if no new sync message has arrived.
|
||||
if (!NeedUpdate) { return; }
|
||||
if (!NeedUpdate) return;
|
||||
|
||||
if (MainProjectile == null || !MainProjectile.Exists())
|
||||
{
|
||||
CreateProjectile();
|
||||
return;
|
||||
}
|
||||
|
||||
MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position);
|
||||
MainProjectile.Rotation = Rotation;
|
||||
LastUpdated = Main.Ticked;
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
|
||||
private void CreateProjectile()
|
||||
{
|
||||
Asset = new WeaponAsset(WeaponHash);
|
||||
if (!Asset.IsLoaded) { Asset.Request(); return; }
|
||||
if (Shooter == null) { return; }
|
||||
if (!Asset.IsLoaded)
|
||||
{
|
||||
Asset.Request();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Shooter == null) return;
|
||||
Entity owner;
|
||||
owner = (Shooter as SyncedPed)?.MainPed ?? (Entity)(Shooter as SyncedVehicle)?.MainVehicle;
|
||||
Position = (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Shooter.Velocity + Position;
|
||||
Position = (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Shooter.Velocity +
|
||||
Position;
|
||||
var end = Position + Velocity;
|
||||
Function.Call(Hash.SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z, end.X, end.Y, end.Z, 0, 1, WeaponHash, owner?.Handle ?? 0, 1, 0, -1);
|
||||
Call(SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z,
|
||||
end.X, end.Y, end.Z, 0, 1, WeaponHash, owner?.Handle ?? 0, 1, 0, -1);
|
||||
var ps = World.GetAllProjectiles();
|
||||
MainProjectile = ps[ps.Length - 1];
|
||||
MainProjectile.Position = Position;
|
||||
@@ -123,4 +135,4 @@ namespace RageCoop.Client
|
||||
EntityPool.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-13
@@ -3,7 +3,7 @@
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronized prop, mostly owned by server
|
||||
/// Synchronized prop, mostly owned by server
|
||||
/// </summary>
|
||||
public class SyncedProp : SyncedEntity
|
||||
{
|
||||
@@ -11,31 +11,35 @@ namespace RageCoop.Client
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The real entity
|
||||
/// The real entity
|
||||
/// </summary>
|
||||
public Prop MainProp { get; set; }
|
||||
internal new int OwnerID
|
||||
{
|
||||
get
|
||||
{
|
||||
// alwayse owned by server
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal new int OwnerID =>
|
||||
// alwayse owned by server
|
||||
0;
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
if (!NeedUpdate) return;
|
||||
if (!Model.IsLoaded)
|
||||
{
|
||||
Model.Request();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeedUpdate) { return; }
|
||||
if (MainProp == null || !MainProp.Exists())
|
||||
{
|
||||
MainProp = World.CreateProp(Model, Position, Rotation, false, false);
|
||||
MainProp.IsInvincible = true;
|
||||
}
|
||||
|
||||
MainProp.Position = Position;
|
||||
MainProp.Rotation = Rotation;
|
||||
MainProp.SetFrozen(true);
|
||||
LastUpdated = Main.Ticked;
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
public partial class SyncedVehicle
|
||||
{
|
||||
public Vehicle MainVehicle { get; internal set; }
|
||||
|
||||
|
||||
#region -- SYNC DATA --
|
||||
internal VehicleData VD;
|
||||
internal VehicleDataFull VDF;
|
||||
internal VehicleDataVar VDV;
|
||||
|
||||
#endregion
|
||||
|
||||
#region FLAGS
|
||||
|
||||
internal bool EngineRunning => VD.Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning);
|
||||
internal bool Transformed => VD.Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
|
||||
internal bool HornActive => VD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
|
||||
internal bool LightsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreLightsOn);
|
||||
internal bool BrakeLightsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
|
||||
internal bool HighBeamsOn => VD.Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
|
||||
internal bool SireneActive => VD.Flags.HasVehFlag(VehicleDataFlags.IsSirenActive);
|
||||
internal bool IsDead => VD.Flags.HasVehFlag(VehicleDataFlags.IsDead);
|
||||
internal bool IsDeluxoHovering => VD.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering);
|
||||
|
||||
#endregion
|
||||
|
||||
#region FIXED-DATA
|
||||
|
||||
internal bool IsMotorcycle;
|
||||
internal bool IsAircraft;
|
||||
internal bool HasRocketBoost;
|
||||
internal bool HasParachute;
|
||||
internal bool HasRoof;
|
||||
internal bool IsSubmarineCar;
|
||||
internal bool IsDeluxo;
|
||||
internal bool IsTrain;
|
||||
internal ushort AvalibleExtras;
|
||||
|
||||
[DebugTunable]
|
||||
static float RotCalMult = 10f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region PRIVATE
|
||||
private VehicleData _lastVD;
|
||||
private VehicleDataFull _lastVDF;
|
||||
private VehicleDataVar _lastVDV;
|
||||
private Vector3 _predictedPosition;
|
||||
internal bool _lastTransformed => _lastVD.Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
|
||||
internal bool _lastHornActive => _lastVD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
|
||||
|
||||
#endregion
|
||||
|
||||
#region OUTGOING
|
||||
|
||||
internal float LastNozzleAngle { get; set; }
|
||||
|
||||
internal float LastEngineHealth { get; set; }
|
||||
internal Vector3 LastVelocity { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Core;
|
||||
using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// A synchronized vehicle instance
|
||||
/// </summary>
|
||||
public partial class SyncedVehicle : SyncedEntity
|
||||
{
|
||||
internal override void Update()
|
||||
{
|
||||
// Check if all data available
|
||||
if (!IsReady || Owner == null) return;
|
||||
|
||||
// Check existence
|
||||
if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model != Model)
|
||||
if (!CreateVehicle())
|
||||
return;
|
||||
|
||||
|
||||
DisplayVehicle();
|
||||
// Skip update if no new sync message has arrived.
|
||||
if (!NeedUpdate) return;
|
||||
|
||||
if (VD.SteeringAngle != MainVehicle.SteeringAngle)
|
||||
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * VD.SteeringAngle);
|
||||
MainVehicle.ThrottlePower = VD.ThrottlePower;
|
||||
MainVehicle.BrakePower = VD.BrakePower;
|
||||
|
||||
if (IsDead)
|
||||
{
|
||||
if (MainVehicle.IsDead) return;
|
||||
|
||||
MainVehicle.Explode();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MainVehicle.IsDead)
|
||||
WorldThread.Delay(() =>
|
||||
{
|
||||
if (MainVehicle.IsDead && !IsDead) MainVehicle.Repair();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (MainVehicle.IsOnFire)
|
||||
{
|
||||
if (!VD.Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
|
||||
}
|
||||
else if (VD.Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
|
||||
{
|
||||
Call(START_ENTITY_FIRE, MainVehicle);
|
||||
}
|
||||
|
||||
if (EngineRunning != MainVehicle.IsEngineRunning) MainVehicle.IsEngineRunning = EngineRunning;
|
||||
|
||||
if (LightsOn != MainVehicle.AreLightsOn) MainVehicle.AreLightsOn = LightsOn;
|
||||
|
||||
if (HighBeamsOn != MainVehicle.AreHighBeamsOn) MainVehicle.AreHighBeamsOn = HighBeamsOn;
|
||||
|
||||
if (!IsAircraft)
|
||||
{
|
||||
if (MainVehicle.HasSiren && SireneActive != MainVehicle.IsSirenActive)
|
||||
MainVehicle.IsSirenActive = SireneActive;
|
||||
|
||||
if (HornActive)
|
||||
{
|
||||
if (!_lastHornActive)
|
||||
{
|
||||
MainVehicle.SoundHorn(99999);
|
||||
}
|
||||
}
|
||||
else if (_lastVD.Flags.HasVehFlag(VehicleDataFlags.IsHornActive))
|
||||
{
|
||||
MainVehicle.SoundHorn(1);
|
||||
}
|
||||
|
||||
if (HasRocketBoost && VD.Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
|
||||
MainVehicle.IsRocketBoostActive)
|
||||
MainVehicle.IsRocketBoostActive = VD.Flags.HasVehFlag(VehicleDataFlags.IsRocketBoostActive);
|
||||
if (HasParachute && VD.Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
|
||||
!MainVehicle.IsParachuteDeployed)
|
||||
MainVehicle.StartParachuting(false);
|
||||
if (IsSubmarineCar)
|
||||
{
|
||||
if (Transformed)
|
||||
{
|
||||
if (!_lastTransformed)
|
||||
{
|
||||
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
|
||||
}
|
||||
}
|
||||
else if (_lastTransformed)
|
||||
{
|
||||
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
|
||||
}
|
||||
}
|
||||
else if (IsDeluxo)
|
||||
{
|
||||
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
|
||||
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(VD.DeluxoWingRatio);
|
||||
}
|
||||
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
|
||||
MainVehicle.LockStatus = VD.LockStatus;
|
||||
}
|
||||
_lastVD = VD;
|
||||
|
||||
if (LastFullSynced >= LastUpdated)
|
||||
{
|
||||
if (IsAircraft)
|
||||
{
|
||||
if (VDF.LandingGear != (byte)MainVehicle.LandingGearState)
|
||||
MainVehicle.LandingGearState = (VehicleLandingGearState)VDF.LandingGear;
|
||||
}
|
||||
if (HasRoof && MainVehicle.RoofState != (VehicleRoofState)VDF.RoofState)
|
||||
MainVehicle.RoofState = (VehicleRoofState)VDF.RoofState;
|
||||
|
||||
if (VD.Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
|
||||
if (VDF.Colors != _lastVDF.Colors)
|
||||
{
|
||||
Call(SET_VEHICLE_COLOURS, MainVehicle, VDF.Colors.Item1, VDF.Colors.Item2);
|
||||
}
|
||||
|
||||
MainVehicle.EngineHealth = VDF.EngineHealth;
|
||||
|
||||
if (VDF.ToggleModsMask != _lastVDF.ToggleModsMask)
|
||||
{
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (VDF.ToggleModsMask & (1 << i)) != 0);
|
||||
}
|
||||
}
|
||||
if (VDF.Livery != _lastVDF.Livery)
|
||||
{
|
||||
Call(SET_VEHICLE_LIVERY, MainVehicle, VDF.Livery);
|
||||
}
|
||||
|
||||
if (VDF.HeadlightColor != _lastVDF.HeadlightColor)
|
||||
{
|
||||
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, VDF.HeadlightColor);
|
||||
}
|
||||
|
||||
if (!CoreUtils.StructCmp(VDF.DamageModel, _lastVDF.DamageModel))
|
||||
{
|
||||
MainVehicle.SetDamageModel(VDF.DamageModel);
|
||||
}
|
||||
|
||||
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != VDF.RadioStation)
|
||||
Util.SetPlayerRadioIndex(MainVehicle.Handle, VDF.RadioStation);
|
||||
|
||||
if (VDF.ExtrasMask != _lastVDF.ExtrasMask)
|
||||
{
|
||||
for (int i = 1; i < 15; i++)
|
||||
{
|
||||
var flag = (ushort)(1 << i);
|
||||
var hasExtra = (AvalibleExtras & (ushort)(1 << i)) != 0;
|
||||
if (!hasExtra)
|
||||
continue;
|
||||
|
||||
var on = (VDF.ExtrasMask & flag) != 0;
|
||||
Call(SET_VEHICLE_EXTRA, MainVehicle.Handle, i, !on);
|
||||
}
|
||||
}
|
||||
if (VDV.Mods != null && (_lastVDV.Mods == null || !VDV.Mods.SequenceEqual(_lastVDV.Mods)))
|
||||
{
|
||||
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
|
||||
|
||||
foreach (var mod in VDV.Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
|
||||
}
|
||||
if (VDV.LicensePlate != _lastVDV.LicensePlate)
|
||||
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, VDV.LicensePlate);
|
||||
|
||||
_lastVDF = VDF;
|
||||
_lastVDV = VDV;
|
||||
}
|
||||
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
|
||||
private void DisplayVehicle()
|
||||
{
|
||||
_predictedPosition = Predict(Position);
|
||||
var current = MainVehicle.ReadPosition();
|
||||
var distSquared = current.DistanceToSquared(_predictedPosition);
|
||||
var cali = _predictedPosition - current;
|
||||
if (!IsTrain) cali += 0.5f * (Velocity - MainVehicle.Velocity);
|
||||
if (distSquared > 10 * 10)
|
||||
{
|
||||
MainVehicle.Position = _predictedPosition;
|
||||
MainVehicle.Velocity = Velocity;
|
||||
MainVehicle.Quaternion = Quaternion;
|
||||
return;
|
||||
}
|
||||
|
||||
var stopped = Velocity == Vector3.Zero;
|
||||
|
||||
// Calibrate position
|
||||
if (distSquared > 0.03 * 0.03)
|
||||
{
|
||||
if (IsTrain || distSquared > 20 * 20) MainVehicle.Velocity = Velocity + cali;
|
||||
else MainVehicle.ApplyWorldForceCenterOfMass(cali, ForceType.InternalImpulse, true);
|
||||
}
|
||||
|
||||
if (NeedUpdate || stopped)
|
||||
{
|
||||
// Calibrate rotation
|
||||
var diff = Quaternion.Diff(MainVehicle.ReadQuaternion());
|
||||
MainVehicle.WorldRotationVelocity = diff.ToEulerAngles() * RotCalMult;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateVehicle()
|
||||
{
|
||||
MainVehicle?.Delete();
|
||||
MainVehicle = Util.CreateVehicle(Model, Position);
|
||||
if (!Model.IsInCdImage)
|
||||
// GTA.UI.Notification.Show($"~r~(Vehicle)Model ({CurrentVehicleModelHash}) cannot be loaded!");
|
||||
return false;
|
||||
if (MainVehicle == null)
|
||||
{
|
||||
Model.Request();
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (EntityPool.VehiclesLock)
|
||||
{
|
||||
EntityPool.Add(this);
|
||||
}
|
||||
|
||||
MainVehicle.Quaternion = Quaternion;
|
||||
if (MainVehicle.HasRoof) MainVehicle.RoofState = (VehicleRoofState)VDF.RoofState;
|
||||
foreach (var w in MainVehicle.Wheels) w.Fix();
|
||||
if (IsInvincible) MainVehicle.IsInvincible = true;
|
||||
SetUpFixedData();
|
||||
Model.MarkAsNoLongerNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region -- CONSTRUCTORS --
|
||||
|
||||
/// <summary>
|
||||
/// Create a local entity (outgoing sync)
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
internal SyncedVehicle(Vehicle v)
|
||||
{
|
||||
ID = EntityPool.RequestNewID();
|
||||
MainVehicle = v;
|
||||
MainVehicle.CanPretendOccupants = false;
|
||||
OwnerID = LocalPlayerID;
|
||||
SetUpFixedData();
|
||||
}
|
||||
|
||||
internal void SetUpFixedData()
|
||||
{
|
||||
if (MainVehicle == null) return;
|
||||
|
||||
IsAircraft = MainVehicle.IsAircraft;
|
||||
IsMotorcycle = MainVehicle.IsMotorcycle;
|
||||
HasRocketBoost = MainVehicle.HasRocketBoost;
|
||||
HasParachute = MainVehicle.HasParachute;
|
||||
HasRoof = MainVehicle.HasRoof;
|
||||
IsSubmarineCar = MainVehicle.IsSubmarineCar;
|
||||
IsDeluxo = MainVehicle.Model == 1483171323;
|
||||
IsTrain = MainVehicle.IsTrain;
|
||||
AvalibleExtras = 0;
|
||||
for (int i = 1; i < 15; i++)
|
||||
{
|
||||
if (Call<bool>(DOES_EXTRA_EXIST, MainVehicle.Handle, i))
|
||||
AvalibleExtras |= (ushort)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty VehicleEntity
|
||||
/// </summary>
|
||||
internal SyncedVehicle()
|
||||
{
|
||||
}
|
||||
|
||||
internal SyncedVehicle(int id)
|
||||
{
|
||||
ID = id;
|
||||
LastSynced = Ticked;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region -- PEDALING --
|
||||
|
||||
/*
|
||||
* Thanks to @oldnapalm.
|
||||
*/
|
||||
|
||||
private string PedalingAnimDict()
|
||||
{
|
||||
switch ((VehicleHash)Model)
|
||||
{
|
||||
case VehicleHash.Bmx:
|
||||
return "veh@bicycle@bmx@front@base";
|
||||
case VehicleHash.Cruiser:
|
||||
return "veh@bicycle@cruiserfront@base";
|
||||
case VehicleHash.Scorcher:
|
||||
return "veh@bicycle@mountainfront@base";
|
||||
default:
|
||||
return "veh@bicycle@roadfront@base";
|
||||
}
|
||||
}
|
||||
|
||||
private string PedalingAnimName(bool fast)
|
||||
{
|
||||
return fast ? "fast_pedal_char" : "cruise_pedal_char";
|
||||
}
|
||||
|
||||
private void StartPedalingAnim(bool fast)
|
||||
{
|
||||
MainVehicle.Driver?.Task.PlayAnimation(PedalingAnimDict(), PedalingAnimName(fast), 8.0f, -8.0f, -1,
|
||||
AnimationFlags.Loop | AnimationFlags.Secondary, 1.0f);
|
||||
}
|
||||
|
||||
private void StopPedalingAnim(bool fast)
|
||||
{
|
||||
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Security.Cryptography;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using SHVDN;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class EntityPool
|
||||
internal static unsafe class EntityPool
|
||||
{
|
||||
public static object PedsLock = new object();
|
||||
#if BENCHMARK
|
||||
private static Stopwatch PerfCounter=new Stopwatch();
|
||||
private static Stopwatch PerfCounter2=Stopwatch.StartNew();
|
||||
#endif
|
||||
|
||||
#region ACTIVE INSTANCES
|
||||
|
||||
public static Dictionary<int, SyncedPed> PedsByID = new Dictionary<int, SyncedPed>();
|
||||
@@ -41,68 +40,70 @@ namespace RageCoop.Client
|
||||
public static object BlipsLock = new object();
|
||||
|
||||
#endregion
|
||||
|
||||
public static void Cleanup(bool keepPlayer = true, bool keepMine = true)
|
||||
{
|
||||
foreach (var ped in PedsByID.Values)
|
||||
foreach (var ped in PedsByID.Values.ToArray())
|
||||
{
|
||||
if ((keepPlayer && (ped.ID == Main.LocalPlayerID)) || (keepMine && (ped.OwnerID == Main.LocalPlayerID))) { continue; }
|
||||
if ((keepPlayer && ped.ID == LocalPlayerID) ||
|
||||
(keepMine && ped.OwnerID == LocalPlayerID)) continue;
|
||||
RemovePed(ped.ID);
|
||||
}
|
||||
|
||||
PedsByID.Clear();
|
||||
PedsByHandle.Clear();
|
||||
|
||||
foreach (int id in new List<int>(VehiclesByID.Keys))
|
||||
foreach (var id in VehiclesByID.Keys.ToArray())
|
||||
{
|
||||
if (keepMine && (VehiclesByID[id].OwnerID == Main.LocalPlayerID)) { continue; }
|
||||
if (keepMine && VehiclesByID[id].OwnerID == LocalPlayerID) continue;
|
||||
RemoveVehicle(id);
|
||||
}
|
||||
|
||||
VehiclesByID.Clear();
|
||||
VehiclesByHandle.Clear();
|
||||
|
||||
foreach (var p in ProjectilesByID.Values)
|
||||
{
|
||||
if (p.Shooter.ID != Main.LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
|
||||
{
|
||||
foreach (var p in ProjectilesByID.Values.ToArray())
|
||||
if (p.Shooter.ID != LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
|
||||
p.MainProjectile.Delete();
|
||||
}
|
||||
}
|
||||
ProjectilesByID.Clear();
|
||||
ProjectilesByHandle.Clear();
|
||||
|
||||
foreach (var p in ServerProps.Values)
|
||||
{
|
||||
p?.MainProp?.Delete();
|
||||
}
|
||||
foreach (var p in ServerProps.Values) p?.MainProp?.Delete();
|
||||
ServerProps.Clear();
|
||||
|
||||
foreach (var b in ServerBlips.Values)
|
||||
{
|
||||
if (b.Exists())
|
||||
{
|
||||
b.Delete();
|
||||
}
|
||||
}
|
||||
ServerBlips.Clear();
|
||||
}
|
||||
|
||||
#region PEDS
|
||||
public static SyncedPed GetPedByID(int id) => PedsByID.TryGetValue(id, out var p) ? p : null;
|
||||
public static SyncedPed GetPedByHandle(int handle) => PedsByHandle.TryGetValue(handle, out var p) ? p : null;
|
||||
public static List<int> GetPedIDs() => new List<int>(PedsByID.Keys);
|
||||
|
||||
public static SyncedPed GetPedByID(int id)
|
||||
=> PedsByID.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public static SyncedPed GetPedByHandle(int handle)
|
||||
=> PedsByHandle.TryGetValue(handle, out var p) ? p : null;
|
||||
|
||||
public static List<int> GetPedIDs()
|
||||
{
|
||||
return new List<int>(PedsByID.Keys);
|
||||
}
|
||||
|
||||
public static bool AddPlayer()
|
||||
{
|
||||
Ped p = Game.Player.Character;
|
||||
var p = Game.Player.Character;
|
||||
// var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
|
||||
// Function.Call(Hash.SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
|
||||
SyncedPed player = GetPedByID(Main.LocalPlayerID);
|
||||
// Call(SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
|
||||
var player = GetPedByID(LocalPlayerID);
|
||||
if (player == null)
|
||||
{
|
||||
Main.Logger.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
|
||||
SyncedPed c = new SyncedPed(p);
|
||||
Main.LocalPlayerID = c.OwnerID = c.ID;
|
||||
Log.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
|
||||
var c = new SyncedPed(p);
|
||||
LocalPlayerID = c.OwnerID = c.ID;
|
||||
Add(c);
|
||||
Main.Logger.Debug($"Local player ID is:{c.ID}");
|
||||
PlayerList.SetPlayer(c.ID, Main.Settings.Username);
|
||||
Log.Debug($"Local player ID is:{c.ID}");
|
||||
PlayerList.SetPlayer(c.ID, Settings.Username);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -119,278 +120,253 @@ namespace RageCoop.Client
|
||||
|
||||
// Re-add
|
||||
PedsByHandle.Remove(pair.Key);
|
||||
if (PedsByHandle.ContainsKey(p.Handle))
|
||||
{
|
||||
RemovePed(PedsByHandle[p.Handle].ID);
|
||||
}
|
||||
if (PedsByHandle.ContainsKey(p.Handle)) RemovePed(PedsByHandle[p.Handle].ID);
|
||||
PedsByHandle.Add(p.Handle, player);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Add(SyncedPed c)
|
||||
{
|
||||
if (PedsByID.ContainsKey(c.ID))
|
||||
{
|
||||
PedsByID[c.ID] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
PedsByID.Add(c.ID, c);
|
||||
}
|
||||
if (c.MainPed == null) { return; }
|
||||
if (c.MainPed == null) return;
|
||||
if (PedsByHandle.ContainsKey(c.MainPed.Handle))
|
||||
{
|
||||
PedsByHandle[c.MainPed.Handle] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
PedsByHandle.Add(c.MainPed.Handle, c);
|
||||
}
|
||||
if (c.IsLocal)
|
||||
{
|
||||
API.Events.InvokePedSpawned(c);
|
||||
}
|
||||
if (c.IsLocal) API.Events.InvokePedSpawned(c);
|
||||
}
|
||||
|
||||
public static void RemovePed(int id, string reason = "Cleanup")
|
||||
{
|
||||
if (PedsByID.ContainsKey(id))
|
||||
{
|
||||
SyncedPed c = PedsByID[id];
|
||||
var c = PedsByID[id];
|
||||
var p = c.MainPed;
|
||||
if (p != null)
|
||||
{
|
||||
if (PedsByHandle.ContainsKey(p.Handle))
|
||||
{
|
||||
PedsByHandle.Remove(p.Handle);
|
||||
}
|
||||
// Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}");
|
||||
if (PedsByHandle.ContainsKey(p.Handle)) PedsByHandle.Remove(p.Handle);
|
||||
// Log.Debug($"Removing ped {c.ID}. Reason:{reason}");
|
||||
p.AttachedBlip?.Delete();
|
||||
p.Kill();
|
||||
p.Model.MarkAsNoLongerNeeded();
|
||||
p.MarkAsNoLongerNeeded();
|
||||
p.Delete();
|
||||
}
|
||||
|
||||
c.PedBlip?.Delete();
|
||||
c.ParachuteProp?.Delete();
|
||||
PedsByID.Remove(id);
|
||||
if (c.IsLocal)
|
||||
{
|
||||
API.Events.InvokePedDeleted(c);
|
||||
}
|
||||
if (c.IsLocal) API.Events.InvokePedDeleted(c);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region VEHICLES
|
||||
public static SyncedVehicle GetVehicleByID(int id) => VehiclesByID.TryGetValue(id, out var v) ? v : null;
|
||||
public static SyncedVehicle GetVehicleByHandle(int handle) => VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
|
||||
public static List<int> GetVehicleIDs() => new List<int>(VehiclesByID.Keys);
|
||||
|
||||
public static SyncedVehicle GetVehicleByID(int id)
|
||||
=> VehiclesByID.TryGetValue(id, out var v) ? v : null;
|
||||
|
||||
public static SyncedVehicle GetVehicleByHandle(int handle)
|
||||
=> VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
|
||||
|
||||
public static List<int> GetVehicleIDs()
|
||||
{
|
||||
return new List<int>(VehiclesByID.Keys);
|
||||
}
|
||||
|
||||
public static void Add(SyncedVehicle v)
|
||||
{
|
||||
if (VehiclesByID.ContainsKey(v.ID))
|
||||
{
|
||||
VehiclesByID[v.ID] = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
VehiclesByID.Add(v.ID, v);
|
||||
}
|
||||
if (v.MainVehicle == null) { return; }
|
||||
if (v.MainVehicle == null) return;
|
||||
if (VehiclesByHandle.ContainsKey(v.MainVehicle.Handle))
|
||||
{
|
||||
VehiclesByHandle[v.MainVehicle.Handle] = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
VehiclesByHandle.Add(v.MainVehicle.Handle, v);
|
||||
}
|
||||
if (v.IsLocal)
|
||||
{
|
||||
API.Events.InvokeVehicleSpawned(v);
|
||||
}
|
||||
if (v.IsLocal) API.Events.InvokeVehicleSpawned(v);
|
||||
}
|
||||
|
||||
public static void RemoveVehicle(int id, string reason = "Cleanup")
|
||||
{
|
||||
if (VehiclesByID.ContainsKey(id))
|
||||
{
|
||||
SyncedVehicle v = VehiclesByID[id];
|
||||
var v = VehiclesByID[id];
|
||||
var veh = v.MainVehicle;
|
||||
if (veh != null)
|
||||
{
|
||||
if (VehiclesByHandle.ContainsKey(veh.Handle))
|
||||
{
|
||||
VehiclesByHandle.Remove(veh.Handle);
|
||||
}
|
||||
// Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
|
||||
if (VehiclesByHandle.ContainsKey(veh.Handle)) VehiclesByHandle.Remove(veh.Handle);
|
||||
// Log.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
|
||||
veh.AttachedBlip?.Delete();
|
||||
veh.Model.MarkAsNoLongerNeeded();
|
||||
veh.MarkAsNoLongerNeeded();
|
||||
veh.Delete();
|
||||
}
|
||||
|
||||
VehiclesByID.Remove(id);
|
||||
if (v.IsLocal) { API.Events.InvokeVehicleDeleted(v); }
|
||||
if (v.IsLocal) API.Events.InvokeVehicleDeleted(v);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PROJECTILES
|
||||
|
||||
public static SyncedProjectile GetProjectileByID(int id)
|
||||
{
|
||||
return ProjectilesByID.TryGetValue(id, out var p) ? p : null;
|
||||
}
|
||||
=> ProjectilesByID.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public static void Add(SyncedProjectile p)
|
||||
{
|
||||
if (!p.IsValid) { return; }
|
||||
if (!p.IsValid) return;
|
||||
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank)
|
||||
{
|
||||
Networking.SendBullet(p.Position, p.Position + p.Velocity, (uint)VehicleWeaponHash.Tank, ((SyncedVehicle)p.Shooter).MainVehicle.Driver.GetSyncEntity().ID);
|
||||
Networking.SendBullet(((SyncedVehicle)p.Shooter).MainVehicle.Driver.GetSyncEntity().ID, (uint)VehicleWeaponHash.Tank, p.Position + p.Velocity);
|
||||
return;
|
||||
}
|
||||
if (ProjectilesByID.ContainsKey(p.ID))
|
||||
{
|
||||
ProjectilesByID[p.ID] = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProjectilesByID.Add(p.ID, p);
|
||||
}
|
||||
if (p.MainProjectile == null) { return; }
|
||||
if (p.MainProjectile == null) return;
|
||||
if (ProjectilesByHandle.ContainsKey(p.MainProjectile.Handle))
|
||||
{
|
||||
ProjectilesByHandle[p.MainProjectile.Handle] = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProjectilesByHandle.Add(p.MainProjectile.Handle, p);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveProjectile(int id, string reason)
|
||||
{
|
||||
if (ProjectilesByID.ContainsKey(id))
|
||||
{
|
||||
SyncedProjectile sp = ProjectilesByID[id];
|
||||
var sp = ProjectilesByID[id];
|
||||
var p = sp.MainProjectile;
|
||||
if (p != null)
|
||||
{
|
||||
if (ProjectilesByHandle.ContainsKey(p.Handle))
|
||||
{
|
||||
ProjectilesByHandle.Remove(p.Handle);
|
||||
}
|
||||
Main.Logger.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
|
||||
if (ProjectilesByHandle.ContainsKey(p.Handle)) ProjectilesByHandle.Remove(p.Handle);
|
||||
Log.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
|
||||
p.Explode();
|
||||
}
|
||||
|
||||
ProjectilesByID.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool PedExists(int id) => PedsByID.ContainsKey(id);
|
||||
public static bool VehicleExists(int id) => VehiclesByID.ContainsKey(id);
|
||||
public static bool ProjectileExists(int id) => ProjectilesByID.ContainsKey(id);
|
||||
public static bool PedExists(int id)
|
||||
{
|
||||
return PedsByID.ContainsKey(id);
|
||||
}
|
||||
|
||||
public static bool VehicleExists(int id)
|
||||
{
|
||||
return VehiclesByID.ContainsKey(id);
|
||||
}
|
||||
|
||||
public static bool ProjectileExists(int id)
|
||||
{
|
||||
return ProjectilesByID.ContainsKey(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SERVER OBJECTS
|
||||
|
||||
public static SyncedProp GetPropByID(int id)
|
||||
=> ServerProps.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public static Blip GetBlipByID(int id)
|
||||
=> ServerBlips.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
#endregion
|
||||
|
||||
private static int vehStateIndex;
|
||||
private static int pedStateIndex;
|
||||
private static int vehStatesPerFrame;
|
||||
private static int pedStatesPerFrame;
|
||||
private static int i;
|
||||
public static Ped[] allPeds = new Ped[0];
|
||||
public static Vehicle[] allVehicles = new Vehicle[0];
|
||||
public static Projectile[] allProjectiles = new Projectile[0];
|
||||
|
||||
public static void DoSync()
|
||||
{
|
||||
UpdateTargets();
|
||||
#if BENCHMARK
|
||||
PerfCounter.Restart();
|
||||
Debug.TimeStamps[TimeStamp.CheckProjectiles]=PerfCounter.ElapsedTicks;
|
||||
#endif
|
||||
allPeds = World.GetAllPeds();
|
||||
allVehicles = World.GetAllVehicles();
|
||||
allProjectiles = World.GetAllProjectiles();
|
||||
vehStatesPerFrame = allVehicles.Length * 2 / (int)Game.FPS + 1;
|
||||
pedStatesPerFrame = allPeds.Length * 2 / (int)Game.FPS + 1;
|
||||
#if BENCHMARK
|
||||
|
||||
Debug.TimeStamps[TimeStamp.GetAllEntities]=PerfCounter.ElapsedTicks;
|
||||
#endif
|
||||
var allPeds = NativeMemory.GetPedHandles();
|
||||
var allVehicles = NativeMemory.GetVehicleHandles();
|
||||
var allProjectiles = NativeMemory.GetProjectileHandles();
|
||||
vehStatesPerFrame = allVehicles.Length * 2 / (int)FPS + 1;
|
||||
pedStatesPerFrame = allPeds.Length * 2 / (int)FPS + 1;
|
||||
|
||||
|
||||
|
||||
lock (ProjectilesLock)
|
||||
{
|
||||
foreach (var p in allProjectiles)
|
||||
if (!ProjectilesByHandle.ContainsKey(p))
|
||||
Add(new SyncedProjectile(Projectile.FromHandle(p)));
|
||||
|
||||
foreach (Projectile p in allProjectiles)
|
||||
{
|
||||
if (!ProjectilesByHandle.ContainsKey(p.Handle))
|
||||
{
|
||||
Add(new SyncedProjectile(p));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SyncedProjectile p in ProjectilesByID.Values.ToArray())
|
||||
{
|
||||
foreach (var p in ProjectilesByID.Values.ToArray())
|
||||
// Outgoing sync
|
||||
if (p.IsLocal)
|
||||
{
|
||||
if (p.MainProjectile.AttachedEntity == null)
|
||||
{
|
||||
// Prevent projectiles from exploding next to vehicle
|
||||
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank || (p.MainProjectile.OwnerEntity?.EntityType == EntityType.Vehicle && p.MainProjectile.Position.DistanceTo(p.Origin) < 2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank ||
|
||||
(p.MainProjectile.OwnerEntity?.EntityType == EntityType.Vehicle &&
|
||||
p.MainProjectile.Position.DistanceTo(p.Origin) < 2)) continue;
|
||||
Networking.SendProjectile(p);
|
||||
}
|
||||
}
|
||||
else // Incoming sync
|
||||
{
|
||||
if (p.Exploded || p.IsOutOfSync)
|
||||
{
|
||||
RemoveProjectile(p.ID, "OutOfSync | Exploded");
|
||||
}
|
||||
else
|
||||
{
|
||||
p.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
i = -1;
|
||||
|
||||
lock (PedsLock)
|
||||
{
|
||||
AddPlayer();
|
||||
|
||||
foreach (Ped p in allPeds)
|
||||
foreach (var p in allPeds)
|
||||
{
|
||||
SyncedPed c = GetPedByHandle(p.Handle);
|
||||
if (c == null && (p != Game.Player.Character))
|
||||
var c = GetPedByHandle(p);
|
||||
if (c == null && p != Game.Player.Character.Handle)
|
||||
{
|
||||
if (allPeds.Length > Main.Settings.WorldPedSoftLimit && p.PopulationType == EntityPopulationType.RandomAmbient)
|
||||
var type = Util.GetPopulationType(p);
|
||||
if (allPeds.Length > Settings.WorldPedSoftLimit
|
||||
&& type == EntityPopulationType.RandomAmbient
|
||||
&& !Call<bool>(IS_PED_IN_ANY_VEHICLE, p, 0))
|
||||
{
|
||||
p.Delete();
|
||||
Util.DeleteEntity(p);
|
||||
continue;
|
||||
}
|
||||
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
|
||||
c = new SyncedPed(p);
|
||||
|
||||
// Log.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
|
||||
c = new SyncedPed((Ped)Entity.FromHandle(p));
|
||||
|
||||
Add(c);
|
||||
}
|
||||
}
|
||||
#if BENCHMARK
|
||||
|
||||
Debug.TimeStamps[TimeStamp.AddPeds]=PerfCounter.ElapsedTicks;
|
||||
#endif
|
||||
var ps = PedsByID.Values.ToArray();
|
||||
pedStateIndex += pedStatesPerFrame;
|
||||
if (pedStateIndex >= ps.Length)
|
||||
{
|
||||
pedStateIndex = 0;
|
||||
}
|
||||
if (pedStateIndex >= ps.Length) pedStateIndex = 0;
|
||||
|
||||
foreach (SyncedPed c in ps)
|
||||
foreach (var c in ps)
|
||||
{
|
||||
i++;
|
||||
if ((c.MainPed != null) && (!c.MainPed.Exists()))
|
||||
if (c.MainPed != null && !c.MainPed.Exists())
|
||||
{
|
||||
RemovePed(c.ID, "non-existent");
|
||||
continue;
|
||||
@@ -399,111 +375,81 @@ namespace RageCoop.Client
|
||||
// Outgoing sync
|
||||
if (c.IsLocal)
|
||||
{
|
||||
#if BENCHMARK
|
||||
var start = PerfCounter2.ElapsedTicks;
|
||||
#endif
|
||||
// event check
|
||||
SyncEvents.Check(c);
|
||||
|
||||
Networking.SendPed(c, (i - pedStateIndex) < pedStatesPerFrame);
|
||||
#if BENCHMARK
|
||||
Debug.TimeStamps[TimeStamp.SendPed]=PerfCounter2.ElapsedTicks-start;
|
||||
#endif
|
||||
Networking.SendPed(c, i - pedStateIndex < pedStatesPerFrame);
|
||||
}
|
||||
else // Incoming sync
|
||||
{
|
||||
#if BENCHMARK
|
||||
var start = PerfCounter2.ElapsedTicks;
|
||||
#endif
|
||||
c.Update();
|
||||
if (c.IsOutOfSync)
|
||||
{
|
||||
RemovePed(c.ID, "OutOfSync");
|
||||
}
|
||||
#if BENCHMARK
|
||||
Debug.TimeStamps[TimeStamp.UpdatePed]=PerfCounter2.ElapsedTicks-start;
|
||||
#endif
|
||||
if (c.IsOutOfSync) RemovePed(c.ID, "OutOfSync");
|
||||
}
|
||||
}
|
||||
#if BENCHMARK
|
||||
Debug.TimeStamps[TimeStamp.PedTotal]=PerfCounter.ElapsedTicks;
|
||||
#endif
|
||||
}
|
||||
var check = Main.Ticked % 100 == 0;
|
||||
|
||||
var check = Ticked % 100 == 0;
|
||||
i = -1;
|
||||
lock (VehiclesLock)
|
||||
{
|
||||
foreach (Vehicle veh in allVehicles)
|
||||
{
|
||||
if (!VehiclesByHandle.ContainsKey(veh.Handle))
|
||||
foreach (var veh in allVehicles)
|
||||
if (!VehiclesByHandle.ContainsKey(veh))
|
||||
{
|
||||
if (allVehicles.Length > Main.Settings.WorldVehicleSoftLimit)
|
||||
var cveh = (Vehicle)Entity.FromHandle(veh);
|
||||
if (cveh == null)
|
||||
continue;
|
||||
if (allVehicles.Length > Settings.WorldVehicleSoftLimit)
|
||||
{
|
||||
var type = veh.PopulationType;
|
||||
if (type == EntityPopulationType.RandomAmbient || type == EntityPopulationType.RandomParked)
|
||||
var type = cveh.PopulationType;
|
||||
if (type == EntityPopulationType.RandomAmbient
|
||||
|| type == EntityPopulationType.RandomParked)
|
||||
{
|
||||
foreach (var p in veh.Occupants)
|
||||
foreach (var p in cveh.Occupants)
|
||||
{
|
||||
p.Delete();
|
||||
var c = GetPedByHandle(p.Handle);
|
||||
if (c != null)
|
||||
{
|
||||
RemovePed(c.ID, "ThrottleTraffic");
|
||||
}
|
||||
if (c != null) RemovePed(c.ID, "ThrottleTraffic");
|
||||
}
|
||||
veh.Delete();
|
||||
|
||||
cveh.Delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
|
||||
// Log.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
|
||||
|
||||
Add(new SyncedVehicle(veh));
|
||||
Add(new SyncedVehicle(cveh));
|
||||
}
|
||||
}
|
||||
#if BENCHMARK
|
||||
Debug.TimeStamps[TimeStamp.AddVehicles]=PerfCounter.ElapsedTicks;
|
||||
#endif
|
||||
var vs = VehiclesByID.Values.ToArray();
|
||||
vehStateIndex += vehStatesPerFrame;
|
||||
if (vehStateIndex >= vs.Length)
|
||||
{
|
||||
vehStateIndex = 0;
|
||||
}
|
||||
if (vehStateIndex >= vs.Length) vehStateIndex = 0;
|
||||
|
||||
foreach (SyncedVehicle v in vs)
|
||||
foreach (var v in vs)
|
||||
{
|
||||
i++;
|
||||
if ((v.MainVehicle != null) && (!v.MainVehicle.Exists()))
|
||||
if (v.MainVehicle != null && !v.MainVehicle.Exists())
|
||||
{
|
||||
RemoveVehicle(v.ID, "non-existent");
|
||||
continue;
|
||||
}
|
||||
if (check)
|
||||
{
|
||||
v.SetUpFixedData();
|
||||
}
|
||||
|
||||
if (check) v.SetUpFixedData();
|
||||
// Outgoing sync
|
||||
if (v.IsLocal)
|
||||
{
|
||||
if (!v.MainVehicle.IsVisible) { continue; }
|
||||
if (!v.MainVehicle.IsVisible) continue;
|
||||
SyncEvents.Check(v);
|
||||
|
||||
Networking.SendVehicle(v, (i - vehStateIndex) < vehStatesPerFrame);
|
||||
Networking.SendVehicle(v, i - vehStateIndex < vehStatesPerFrame);
|
||||
}
|
||||
else // Incoming sync
|
||||
{
|
||||
v.Update();
|
||||
if (v.IsOutOfSync)
|
||||
{
|
||||
RemoveVehicle(v.ID, "OutOfSync");
|
||||
}
|
||||
if (v.IsOutOfSync) RemoveVehicle(v.ID, "OutOfSync");
|
||||
}
|
||||
}
|
||||
|
||||
#if BENCHMARK
|
||||
Debug.TimeStamps[TimeStamp.VehicleTotal]=PerfCounter.ElapsedTicks;
|
||||
#endif
|
||||
}
|
||||
|
||||
Networking.Peer.FlushSendQueue();
|
||||
}
|
||||
|
||||
@@ -511,52 +457,38 @@ namespace RageCoop.Client
|
||||
{
|
||||
Networking.Targets = new List<NetConnection>(PlayerList.Players.Count) { Networking.ServerConnection };
|
||||
foreach (var p in PlayerList.Players.Values.ToArray())
|
||||
{
|
||||
if (p.HasDirectConnection && p.Position.DistanceTo(Main.PlayerPosition) < 500)
|
||||
{
|
||||
if (p.HasDirectConnection && p.Position.DistanceTo(PlayerPosition) < 500)
|
||||
Networking.Targets.Add(p.Connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAllFromPlayer(int playerPedId)
|
||||
{
|
||||
foreach (SyncedPed p in PedsByID.Values.ToArray())
|
||||
{
|
||||
foreach (var p in PedsByID.Values.ToArray())
|
||||
if (p.OwnerID == playerPedId)
|
||||
{
|
||||
RemovePed(p.ID);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SyncedVehicle v in VehiclesByID.Values.ToArray())
|
||||
{
|
||||
foreach (var v in VehiclesByID.Values.ToArray())
|
||||
if (v.OwnerID == playerPedId)
|
||||
{
|
||||
RemoveVehicle(v.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int RequestNewID()
|
||||
{
|
||||
int ID = 0;
|
||||
while ((ID == 0) || PedsByID.ContainsKey(ID) || VehiclesByID.ContainsKey(ID) || ProjectilesByID.ContainsKey(ID))
|
||||
var ID = 0;
|
||||
while (ID == 0 || PedsByID.ContainsKey(ID) || VehiclesByID.ContainsKey(ID) ||
|
||||
ProjectilesByID.ContainsKey(ID))
|
||||
{
|
||||
byte[] rngBytes = new byte[4];
|
||||
var rngBytes = new byte[4];
|
||||
|
||||
RandomNumberGenerator.Create().GetBytes(rngBytes);
|
||||
|
||||
// Convert the bytes into an integer
|
||||
ID = BitConverter.ToInt32(rngBytes, 0);
|
||||
}
|
||||
|
||||
return ID;
|
||||
}
|
||||
private static void SetBudget(int b)
|
||||
{
|
||||
Function.Call(Hash.SET_PED_POPULATION_BUDGET, b); // 0 - 3
|
||||
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, b); // 0 - 3
|
||||
}
|
||||
|
||||
public static string DumpDebug()
|
||||
{
|
||||
return $"\nID_Peds: {PedsByID.Count}" +
|
||||
@@ -568,6 +500,7 @@ namespace RageCoop.Client
|
||||
$"\npedStatesPerFrame: {pedStatesPerFrame}" +
|
||||
$"\nvehStatesPerFrame: {vehStatesPerFrame}";
|
||||
}
|
||||
|
||||
public static class ThreadSafe
|
||||
{
|
||||
public static void Add(SyncedVehicle v)
|
||||
@@ -577,6 +510,7 @@ namespace RageCoop.Client
|
||||
EntityPool.Add(v);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(SyncedPed p)
|
||||
{
|
||||
lock (PedsLock)
|
||||
@@ -584,6 +518,7 @@ namespace RageCoop.Client
|
||||
EntityPool.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(SyncedProjectile sp)
|
||||
{
|
||||
lock (ProjectilesLock)
|
||||
@@ -593,4 +528,4 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,28 @@
|
||||
using NAudio.Wave;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Voice
|
||||
{
|
||||
private static bool _stopping = false;
|
||||
private static WaveInEvent _waveIn;
|
||||
private static readonly BufferedWaveProvider _waveProvider = new BufferedWaveProvider(new WaveFormat(16000, 16, 1));
|
||||
|
||||
private static readonly BufferedWaveProvider _waveProvider =
|
||||
new BufferedWaveProvider(new WaveFormat(16000, 16, 1));
|
||||
|
||||
private static Thread _thread;
|
||||
|
||||
public static bool WasInitialized() => _thread != null;
|
||||
public static bool IsRecording() => _waveIn != null;
|
||||
public static bool WasInitialized()
|
||||
{
|
||||
return _thread != null;
|
||||
}
|
||||
|
||||
public static bool IsRecording()
|
||||
{
|
||||
return _waveIn != null;
|
||||
}
|
||||
|
||||
public static void ClearAll()
|
||||
{
|
||||
_waveProvider.ClearBuffer();
|
||||
@@ -20,7 +31,8 @@ namespace RageCoop.Client
|
||||
|
||||
if (_thread != null && _thread.IsAlive)
|
||||
{
|
||||
_thread.Abort();
|
||||
_stopping = true;
|
||||
_thread.Join();
|
||||
_thread = null;
|
||||
}
|
||||
}
|
||||
@@ -41,23 +53,17 @@ namespace RageCoop.Client
|
||||
return;
|
||||
|
||||
// I tried without thread but the game will lag without
|
||||
_thread = new Thread(new ThreadStart(() =>
|
||||
_thread = ThreadManager.CreateThread(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (!_stopping && !IsUnloading)
|
||||
using (var wo = new WaveOutEvent())
|
||||
{
|
||||
wo.Init(_waveProvider);
|
||||
wo.Play();
|
||||
|
||||
while (wo.PlaybackState == PlaybackState.Playing)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
while (wo.PlaybackState == PlaybackState.Playing) Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}));
|
||||
_thread.Start();
|
||||
},"Voice");
|
||||
}
|
||||
|
||||
public static void StartRecording()
|
||||
@@ -90,4 +96,4 @@ namespace RageCoop.Client
|
||||
Networking.SendVoiceMessage(e.Buffer, e.BytesRecorded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<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>
|
||||
@@ -1,19 +1,19 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using RageCoop.Core;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using NativeMemory = SHVDN.NativeMemory;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal unsafe class MemPatch
|
||||
{
|
||||
private readonly IntPtr _address;
|
||||
private readonly byte[] _data;
|
||||
private readonly byte[] _orginal;
|
||||
private readonly IntPtr _address;
|
||||
|
||||
public MemPatch(byte* address, byte[] data)
|
||||
{
|
||||
_data = data;
|
||||
@@ -21,10 +21,12 @@ namespace RageCoop.Client
|
||||
_address = (IntPtr)address;
|
||||
Marshal.Copy((IntPtr)address, _orginal, 0, data.Length);
|
||||
}
|
||||
|
||||
public void Install()
|
||||
{
|
||||
Marshal.Copy(_data, 0, _address, _data.Length);
|
||||
}
|
||||
|
||||
public void Uninstall()
|
||||
{
|
||||
Marshal.Copy(_orginal, 0, _address, _orginal.Length);
|
||||
@@ -36,68 +38,95 @@ namespace RageCoop.Client
|
||||
public static MemPatch VignettingPatch;
|
||||
public static MemPatch VignettingCallPatch;
|
||||
public static MemPatch TimeScalePatch;
|
||||
|
||||
static Memory()
|
||||
{
|
||||
// Weapon/radio wheel slow-mo patch
|
||||
// Thanks @CamxxCore, https://github.com/CamxxCore/GTAVWeaponWheelMod
|
||||
// Thanks to @CamxxCore, https://github.com/CamxxCore/GTAVWeaponWheelMod
|
||||
var result = NativeMemory.FindPattern("\x38\x51\x64\x74\x19", "xxxxx");
|
||||
if (result == null) { throw new NotSupportedException("Can't find memory pattern to patch weapon/radio slow-mo"); }
|
||||
if (result == null)
|
||||
throw new NotSupportedException("Can't find memory pattern to patch weapon/radio slowdown");
|
||||
var address = result + 26;
|
||||
address = address + *(int*)address + 4u;
|
||||
VignettingPatch = new MemPatch(address, new byte[] { RET, 0x90, 0x90, 0x90, 0x90 });
|
||||
VignettingCallPatch = new MemPatch(result + 8, new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90 });
|
||||
TimeScalePatch = new MemPatch(result + 34, new byte[] { XOR_32_64, 0xD2 });
|
||||
|
||||
}
|
||||
|
||||
public static void ApplyPatches()
|
||||
{
|
||||
VignettingPatch.Install();
|
||||
VignettingCallPatch.Install();
|
||||
TimeScalePatch.Install();
|
||||
}
|
||||
|
||||
public static void RestorePatches()
|
||||
{
|
||||
VignettingPatch.Uninstall();
|
||||
VignettingCallPatch.Uninstall();
|
||||
TimeScalePatch.Uninstall();
|
||||
}
|
||||
#region PATCHES
|
||||
#endregion
|
||||
#region OFFSET-CONST
|
||||
public const int PositionOffset = 144;
|
||||
public const int VelocityOffset = 800;
|
||||
public const int MatrixOffset = 96;
|
||||
#endregion
|
||||
#region OPCODE
|
||||
private const byte XOR_32_64 = 0x31;
|
||||
private const byte RET = 0xC3;
|
||||
#endregion
|
||||
public static Vector3 ReadPosition(this Entity e) => ReadVector3(e.MemoryAddress + PositionOffset);
|
||||
public static Quaternion ReadQuaternion(this Entity e) => Quaternion.RotationMatrix(e.Matrix);
|
||||
public static Vector3 ReadRotation(this Entity e) => e.ReadQuaternion().ToEulerDegrees();
|
||||
public static Vector3 ReadVelocity(this Ped e) => ReadVector3(e.MemoryAddress + VelocityOffset);
|
||||
|
||||
public static Vector3 ReadPosition(this Entity e)
|
||||
{
|
||||
return ReadVector3(e.MemoryAddress + PositionOffset);
|
||||
}
|
||||
|
||||
public static Quaternion ReadQuaternion(this Entity e)
|
||||
{
|
||||
return Quaternion.RotationMatrix(e.Matrix);
|
||||
}
|
||||
|
||||
public static Vector3 ReadRotation(this Entity e)
|
||||
{
|
||||
return e.ReadQuaternion().ToEulerDegrees();
|
||||
}
|
||||
|
||||
public static Vector3 ReadVelocity(this Ped e)
|
||||
{
|
||||
return ReadVector3(e.MemoryAddress + VelocityOffset);
|
||||
}
|
||||
|
||||
public static Vector3 ReadVector3(IntPtr address)
|
||||
{
|
||||
float* ptr = (float*)address.ToPointer();
|
||||
return new Vector3()
|
||||
var ptr = (float*)address.ToPointer();
|
||||
return new Vector3
|
||||
{
|
||||
X = *ptr,
|
||||
Y = ptr[1],
|
||||
Z = ptr[2]
|
||||
};
|
||||
}
|
||||
|
||||
public static List<int> FindOffset(float toSearch, IntPtr start, int range = 1000, float tolerance = 0.01f)
|
||||
{
|
||||
var foundOffsets = new List<int>(100);
|
||||
for (int i = 0; i <= range; i++)
|
||||
for (var i = 0; i <= range; i++)
|
||||
{
|
||||
var val = NativeMemory.ReadFloat(start + i);
|
||||
if (Math.Abs(val - toSearch) < tolerance)
|
||||
{
|
||||
foundOffsets.Add(i);
|
||||
}
|
||||
if (Math.Abs(val - toSearch) < tolerance) foundOffsets.Add(i);
|
||||
}
|
||||
|
||||
return foundOffsets;
|
||||
}
|
||||
|
||||
#region PATCHES
|
||||
|
||||
#endregion
|
||||
|
||||
#region OFFSET-CONST
|
||||
|
||||
public const int PositionOffset = 144;
|
||||
public const int VelocityOffset = 800;
|
||||
public const int MatrixOffset = 96;
|
||||
|
||||
#endregion
|
||||
|
||||
#region OPCODE
|
||||
|
||||
private const byte XOR_32_64 = 0x31;
|
||||
private const byte RET = 0xC3;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,52 @@
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 80)]
|
||||
public struct HeadBlendData
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int ShapeFirst;
|
||||
[FieldOffset(0)] public int ShapeFirst;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public int ShapeSecond;
|
||||
[FieldOffset(8)] public int ShapeSecond;
|
||||
|
||||
[FieldOffset(16)]
|
||||
public int ShapeThird;
|
||||
[FieldOffset(16)] public int ShapeThird;
|
||||
|
||||
[FieldOffset(24)]
|
||||
public int SkinFirst;
|
||||
[FieldOffset(24)] public int SkinFirst;
|
||||
|
||||
[FieldOffset(32)]
|
||||
public int SkinSecond;
|
||||
[FieldOffset(32)] public int SkinSecond;
|
||||
|
||||
[FieldOffset(40)]
|
||||
public int SkinThird;
|
||||
[FieldOffset(40)] public int SkinThird;
|
||||
|
||||
[FieldOffset(48)]
|
||||
public float ShapeMix;
|
||||
[FieldOffset(48)] public float ShapeMix;
|
||||
|
||||
[FieldOffset(56)]
|
||||
public float SkinMix;
|
||||
[FieldOffset(56)] public float SkinMix;
|
||||
|
||||
[FieldOffset(64)]
|
||||
public float ThirdMix;
|
||||
[FieldOffset(64)] public float ThirdMix;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 24)]
|
||||
public struct NativeVector3
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public float X;
|
||||
[FieldOffset(0)] public float X;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public float Y;
|
||||
[FieldOffset(8)] public float Y;
|
||||
|
||||
[FieldOffset(16)]
|
||||
public float Z;
|
||||
[FieldOffset(16)] public float Z;
|
||||
|
||||
public static implicit operator Vector3(NativeVector3 vec)
|
||||
{
|
||||
return new Vector3() { X = vec.X, Y = vec.Y, Z = vec.Z };
|
||||
return new Vector3 { X = vec.X, Y = vec.Y, Z = vec.Z };
|
||||
}
|
||||
|
||||
public static implicit operator NativeVector3(Vector3 vec)
|
||||
{
|
||||
return new NativeVector3() { X = vec.X, Y = vec.Y, Z = vec.Z };
|
||||
return new NativeVector3 { X = vec.X, Y = vec.Y, Z = vec.Z };
|
||||
}
|
||||
}
|
||||
|
||||
public static class NativeCaller
|
||||
{
|
||||
// These are borrowed from ScriptHookVDotNet's
|
||||
@@ -80,8 +70,9 @@ namespace RageCoop.Client
|
||||
public static unsafe R Invoke<R>(ulong hash) where R : unmanaged
|
||||
{
|
||||
NativeInit(hash);
|
||||
return *(R*)(NativeCall());
|
||||
return *(R*)NativeCall();
|
||||
}
|
||||
|
||||
public static unsafe R Invoke<R>(Hash hash, params object[] args)
|
||||
where R : unmanaged
|
||||
{
|
||||
@@ -90,45 +81,50 @@ namespace RageCoop.Client
|
||||
foreach (var arg in arguments)
|
||||
NativePush(arg);
|
||||
|
||||
return *(R*)(NativeCall());
|
||||
return *(R*)NativeCall();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper function that converts an array of primitive values to a native stack.
|
||||
/// Helper function that converts an array of primitive values to a native stack.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
private static unsafe ulong[] ConvertPrimitiveArguments(object[] args)
|
||||
{
|
||||
var result = new ulong[args.Length];
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
for (var i = 0; i < args.Length; ++i)
|
||||
{
|
||||
if (args[i] is bool valueBool)
|
||||
{
|
||||
result[i] = valueBool ? 1ul : 0ul;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is byte valueByte)
|
||||
{
|
||||
result[i] = valueByte;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is int valueInt32)
|
||||
{
|
||||
result[i] = (ulong)valueInt32;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is ulong valueUInt64)
|
||||
{
|
||||
result[i] = valueUInt64;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is float valueFloat)
|
||||
{
|
||||
result[i] = *(ulong*)&valueFloat;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is IntPtr valueIntPtr)
|
||||
{
|
||||
result[i] = (ulong)valueIntPtr.ToInt64();
|
||||
@@ -141,5 +137,4 @@ namespace RageCoop.Client
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -460,6 +460,6 @@
|
||||
_0x1D19C622 = 454,
|
||||
_0xB68D3EAB = 455,
|
||||
CPED_CONFIG_FLAG_CanBeIncapacitated = 456,
|
||||
_0x4BD5EBAD = 457,
|
||||
_0x4BD5EBAD = 457
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class PedExtensions
|
||||
internal static class PedExtensions
|
||||
{
|
||||
|
||||
public static bool IsBetween<T>(this T item, T start, T end)
|
||||
{
|
||||
return Comparer<T>.Default.Compare(item, start) >= 0 && Comparer<T>.Default.Compare(item, end) <= 0;
|
||||
@@ -17,17 +16,11 @@ namespace RageCoop.Client
|
||||
|
||||
public static bool Compare<T, Y>(this Dictionary<T, Y> item, Dictionary<T, Y> item2)
|
||||
{
|
||||
if (item == null || item2 == null || item.Count != item2.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (item == null || item2 == null || item.Count != item2.Count) return false;
|
||||
|
||||
foreach (KeyValuePair<T, Y> pair in item)
|
||||
foreach (var pair in item)
|
||||
{
|
||||
if (item2.TryGetValue(pair.Key, out Y value) && Equals(value, pair.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item2.TryGetValue(pair.Key, out var value) && Equals(value, pair.Value)) continue;
|
||||
|
||||
// TryGetValue() or Equals failed
|
||||
return false;
|
||||
@@ -38,37 +31,141 @@ namespace RageCoop.Client
|
||||
}
|
||||
|
||||
|
||||
public static Vector3 RaycastEverything(Vector2 screenCoord)
|
||||
{
|
||||
var camPos = GameplayCamera.Position;
|
||||
var camRot = GameplayCamera.Rotation;
|
||||
const float raycastToDist = 100.0f;
|
||||
const float raycastFromDist = 1f;
|
||||
|
||||
var target3D = ScreenRelToWorld(camPos, camRot, screenCoord);
|
||||
var source3D = camPos;
|
||||
|
||||
Entity ignoreEntity = Game.Player.Character;
|
||||
if (Game.Player.Character.IsInVehicle()) ignoreEntity = Game.Player.Character.CurrentVehicle;
|
||||
|
||||
var dir = target3D - source3D;
|
||||
dir.Normalize();
|
||||
var raycastResults = World.Raycast(source3D + dir * raycastFromDist,
|
||||
source3D + dir * raycastToDist,
|
||||
IntersectFlags.Everything,
|
||||
ignoreEntity);
|
||||
|
||||
if (raycastResults.DidHit) return raycastResults.HitPosition;
|
||||
|
||||
return camPos + dir * raycastToDist;
|
||||
}
|
||||
|
||||
public static Vector3 ScreenRelToWorld(Vector3 camPos, Vector3 camRot, Vector2 coord)
|
||||
{
|
||||
var camForward = camRot.ToDirection();
|
||||
var rotUp = camRot + new Vector3(10, 0, 0);
|
||||
var rotDown = camRot + new Vector3(-10, 0, 0);
|
||||
var rotLeft = camRot + new Vector3(0, 0, -10);
|
||||
var rotRight = camRot + new Vector3(0, 0, 10);
|
||||
|
||||
var camRight = rotRight.ToDirection() - rotLeft.ToDirection();
|
||||
var camUp = rotUp.ToDirection() - rotDown.ToDirection();
|
||||
|
||||
double rollRad = -camRot.Y.ToRadians();
|
||||
|
||||
var camRightRoll = camRight * (float)Math.Cos(rollRad) - camUp * (float)Math.Sin(rollRad);
|
||||
var camUpRoll = camRight * (float)Math.Sin(rollRad) + camUp * (float)Math.Cos(rollRad);
|
||||
|
||||
var point3D = camPos + camForward * 10.0f + camRightRoll + camUpRoll;
|
||||
if (!WorldToScreenRel(point3D, out var point2D)) return camPos + camForward * 10.0f;
|
||||
|
||||
var point3DZero = camPos + camForward * 10.0f;
|
||||
if (!WorldToScreenRel(point3DZero, out var point2DZero)) return camPos + camForward * 10.0f;
|
||||
|
||||
const double eps = 0.001;
|
||||
if (Math.Abs(point2D.X - point2DZero.X) < eps || Math.Abs(point2D.Y - point2DZero.Y) < eps)
|
||||
return camPos + camForward * 10.0f;
|
||||
|
||||
var scaleX = (coord.X - point2DZero.X) / (point2D.X - point2DZero.X);
|
||||
var scaleY = (coord.Y - point2DZero.Y) / (point2D.Y - point2DZero.Y);
|
||||
|
||||
return camPos + camForward * 10.0f + camRightRoll * scaleX + camUpRoll * scaleY;
|
||||
}
|
||||
|
||||
public static bool WorldToScreenRel(Vector3 worldCoords, out Vector2 screenCoords)
|
||||
{
|
||||
var num1 = new OutputArgument();
|
||||
var num2 = new OutputArgument();
|
||||
|
||||
if (!Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y,
|
||||
worldCoords.Z, num1, num2))
|
||||
{
|
||||
screenCoords = new Vector2();
|
||||
return false;
|
||||
}
|
||||
|
||||
screenCoords = new Vector2((num1.GetResult<float>() - 0.5f) * 2, (num2.GetResult<float>() - 0.5f) * 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void StayInCover(this Ped p)
|
||||
{
|
||||
Call(TASK_STAY_IN_COVER, p);
|
||||
}
|
||||
|
||||
public static bool IsTurretSeat(this Vehicle veh, int seat)
|
||||
{
|
||||
if (Call<bool>(IS_TURRET_SEAT, veh, seat)) return true;
|
||||
if (!Call<bool>(DOES_VEHICLE_HAVE_WEAPONS, veh.Handle)) return false;
|
||||
|
||||
switch (seat)
|
||||
{
|
||||
case -1:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Rhino
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Khanjari
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.FireTruck
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Riot2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
|
||||
case 0:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dune3;
|
||||
case 1:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical3
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.HalfTrack
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
|
||||
case 2:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
|
||||
case 3:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Limo2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dinghy5;
|
||||
case 7:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Insurgent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsOnTurretSeat(this Ped P)
|
||||
{
|
||||
if (P.CurrentVehicle == null) return false;
|
||||
return IsTurretSeat(P.CurrentVehicle, (int)P.SeatIndex);
|
||||
}
|
||||
|
||||
|
||||
#region PED
|
||||
|
||||
public static byte GetPedSpeed(this Ped ped)
|
||||
{
|
||||
|
||||
if (ped.IsSittingInVehicle())
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
if (ped.IsTaskActive(TaskType.CTaskEnterVehicle))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
if (ped.IsTaskActive(TaskType.CTaskExitVehicle))
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
if (ped.IsWalking)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (ped.IsRunning)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
if (ped.IsSprinting)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
if (ped.IsSittingInVehicle()) return 4;
|
||||
if (ped.IsTaskActive(TaskType.CTaskEnterVehicle)) return 5;
|
||||
if (ped.IsTaskActive(TaskType.CTaskExitVehicle)) return 6;
|
||||
if (ped.IsWalking) return 1;
|
||||
if (ped.IsRunning) return 2;
|
||||
if (ped.IsSprinting) return 3;
|
||||
|
||||
|
||||
return 0;
|
||||
@@ -80,91 +177,50 @@ namespace RageCoop.Client
|
||||
var result = new byte[36];
|
||||
for (byte i = 0; i < 12; i++)
|
||||
{
|
||||
result[i] = (byte)Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
|
||||
result[i + 12] = (byte)Function.Call<short>(Hash.GET_PED_TEXTURE_VARIATION, ped.Handle, i);
|
||||
result[i + 24] = (byte)Function.Call<short>(Hash.GET_PED_PALETTE_VARIATION, ped.Handle, i);
|
||||
result[i] = (byte)Call<short>(GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
|
||||
result[i + 12] = (byte)Call<short>(GET_PED_TEXTURE_VARIATION, ped.Handle, i);
|
||||
result[i + 24] = (byte)Call<short>(GET_PED_PALETTE_VARIATION, ped.Handle, i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static PedDataFlags GetPedFlags(this Ped ped)
|
||||
{
|
||||
PedDataFlags flags = PedDataFlags.None;
|
||||
var flags = PedDataFlags.None;
|
||||
|
||||
if (ped.IsAiming || ped.IsOnTurretSeat())
|
||||
{
|
||||
flags |= PedDataFlags.IsAiming;
|
||||
}
|
||||
if (ped.IsAiming || ped.IsOnTurretSeat()) flags |= PedDataFlags.IsAiming;
|
||||
|
||||
|
||||
if (ped.IsReloading)
|
||||
{
|
||||
flags |= PedDataFlags.IsReloading;
|
||||
}
|
||||
if (ped.IsReloading) flags |= PedDataFlags.IsReloading;
|
||||
|
||||
if (ped.IsJumping)
|
||||
{
|
||||
flags |= PedDataFlags.IsJumping;
|
||||
}
|
||||
if (ped.IsJumping) flags |= PedDataFlags.IsJumping;
|
||||
|
||||
// Fake death
|
||||
if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer))
|
||||
{
|
||||
flags |= PedDataFlags.IsRagdoll;
|
||||
}
|
||||
if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer)) flags |= PedDataFlags.IsRagdoll;
|
||||
|
||||
if (ped.IsOnFire)
|
||||
{
|
||||
flags |= PedDataFlags.IsOnFire;
|
||||
}
|
||||
if (ped.IsOnFire) flags |= PedDataFlags.IsOnFire;
|
||||
|
||||
if (ped.IsInParachuteFreeFall)
|
||||
{
|
||||
flags |= PedDataFlags.IsInParachuteFreeFall;
|
||||
}
|
||||
if (ped.IsInParachuteFreeFall) flags |= PedDataFlags.IsInParachuteFreeFall;
|
||||
|
||||
if (ped.ParachuteState == ParachuteState.Gliding)
|
||||
{
|
||||
flags |= PedDataFlags.IsParachuteOpen;
|
||||
}
|
||||
if (ped.ParachuteState == ParachuteState.Gliding) flags |= PedDataFlags.IsParachuteOpen;
|
||||
|
||||
bool climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
|
||||
if (climbingLadder)
|
||||
{
|
||||
flags |= PedDataFlags.IsOnLadder;
|
||||
}
|
||||
var climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
|
||||
if (climbingLadder) flags |= PedDataFlags.IsOnLadder;
|
||||
|
||||
if (ped.IsVaulting && !climbingLadder)
|
||||
{
|
||||
flags |= PedDataFlags.IsVaulting;
|
||||
}
|
||||
if (ped.IsVaulting && !climbingLadder) flags |= PedDataFlags.IsVaulting;
|
||||
|
||||
if (ped.IsInCover || ped.IsGoingIntoCover)
|
||||
{
|
||||
flags |= PedDataFlags.IsInCover;
|
||||
if (ped.IsInCoverFacingLeft)
|
||||
{
|
||||
flags |= PedDataFlags.IsInCover;
|
||||
}
|
||||
if (!Function.Call<bool>(Hash.IS_PED_IN_HIGH_COVER, ped))
|
||||
{
|
||||
flags |= PedDataFlags.IsInLowCover;
|
||||
}
|
||||
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire))
|
||||
{
|
||||
flags |= PedDataFlags.IsBlindFiring;
|
||||
}
|
||||
if (ped.IsInCoverFacingLeft) flags |= PedDataFlags.IsInCover;
|
||||
if (!Call<bool>(IS_PED_IN_HIGH_COVER, ped)) flags |= PedDataFlags.IsInLowCover;
|
||||
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire)) flags |= PedDataFlags.IsBlindFiring;
|
||||
}
|
||||
|
||||
if (ped.IsInvincible)
|
||||
{
|
||||
flags |= PedDataFlags.IsInvincible;
|
||||
}
|
||||
if (ped.IsInvincible) flags |= PedDataFlags.IsInvincible;
|
||||
|
||||
if (Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped))
|
||||
{
|
||||
flags |= PedDataFlags.IsInStealthMode;
|
||||
}
|
||||
if (Call<bool>(GET_PED_STEALTH_MOVEMENT, ped)) flags |= PedDataFlags.IsInStealthMode;
|
||||
|
||||
|
||||
return flags;
|
||||
@@ -253,259 +309,110 @@ namespace RageCoop.Client
|
||||
case WeaponHash.MG:
|
||||
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
|
||||
default:
|
||||
Main.Logger.Warning($"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
|
||||
Log.Warning(
|
||||
$"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<string, int> _vehicleDoors=new()
|
||||
{
|
||||
{ "door_dside_f", -1 },
|
||||
{ "door_pside_f", 0 },
|
||||
{ "door_dside_r", 1 },
|
||||
{ "door_pside_r", 2 }
|
||||
};
|
||||
public static VehicleSeat GetNearestSeat(this Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
|
||||
{
|
||||
float num = 99f;
|
||||
int result = -2;
|
||||
Dictionary<string, int> dictionary = new Dictionary<string, int>();
|
||||
dictionary.Add("door_dside_f", -1);
|
||||
dictionary.Add("door_pside_f", 0);
|
||||
dictionary.Add("door_dside_r", 1);
|
||||
dictionary.Add("door_pside_r", 2);
|
||||
foreach (string text in dictionary.Keys)
|
||||
var num = 99f;
|
||||
var result = -2;
|
||||
foreach (var text in _vehicleDoors.Keys)
|
||||
{
|
||||
bool flag = veh.Bones[text].Position != Vector3.Zero;
|
||||
var flag = veh.Bones[text].Position != Vector3.Zero;
|
||||
if (flag)
|
||||
{
|
||||
float num2 = ped.Position.DistanceTo(Function.Call<Vector3>(Hash.GET_WORLD_POSITION_OF_ENTITY_BONE, new InputArgument[]
|
||||
{
|
||||
veh,
|
||||
veh.Bones[text].Index
|
||||
}));
|
||||
bool flag2 = (num2 < distanceToignoreDoors) && (num2 < num) && IsSeatUsableByPed(ped, veh, dictionary[text]);
|
||||
var num2 = ped.Position.DistanceTo(Call<Vector3>(GET_WORLD_POSITION_OF_ENTITY_BONE, veh, veh.Bones[text].Index));
|
||||
var flag2 = num2 < distanceToignoreDoors && num2 < num &&
|
||||
IsSeatUsableByPed(ped, veh, _vehicleDoors[text]);
|
||||
if (flag2)
|
||||
{
|
||||
num = num2;
|
||||
result = dictionary[text];
|
||||
result = _vehicleDoors[text];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (VehicleSeat)result;
|
||||
}
|
||||
|
||||
public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat)
|
||||
{
|
||||
VehicleSeat seat = (VehicleSeat)_seat;
|
||||
bool result = false;
|
||||
bool flag = veh.IsSeatFree(seat);
|
||||
var seat = (VehicleSeat)_seat;
|
||||
var result = false;
|
||||
var flag = veh.IsSeatFree(seat);
|
||||
if (flag)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
bool isDead = veh.GetPedOnSeat(seat).IsDead;
|
||||
var isDead = veh.GetPedOnSeat(seat).IsDead;
|
||||
if (isDead)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = Function.Call<int>(Hash.GET_RELATIONSHIP_BETWEEN_PEDS, new InputArgument[]
|
||||
{
|
||||
ped,
|
||||
veh.GetPedOnSeat(seat)
|
||||
});
|
||||
bool flag2 = num > 2;
|
||||
if (flag2)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
var num = Call<int>(GET_RELATIONSHIP_BETWEEN_PEDS, ped, veh.GetPedOnSeat(seat));
|
||||
var flag2 = num > 2;
|
||||
if (flag2) result = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsTaskActive(this Ped p, TaskType task)
|
||||
{
|
||||
return Function.Call<bool>(Hash.GET_IS_TASK_ACTIVE, p.Handle, task);
|
||||
return Call<bool>(GET_IS_TASK_ACTIVE, p.Handle, task);
|
||||
}
|
||||
|
||||
public static Vector3 GetAimCoord(this Ped p)
|
||||
{
|
||||
var weapon = p.Weapons.CurrentWeaponObject;
|
||||
Prop weapon;
|
||||
|
||||
var v = p.CurrentVehicle;
|
||||
// Rhino
|
||||
if (v != null && v.Model.Hash == 782665360)
|
||||
EntityBone b;
|
||||
if (p.IsOnTurretSeat())
|
||||
{
|
||||
return v.Bones[35].Position + v.Bones[35].ForwardVector * 100;
|
||||
if ((b = p.CurrentVehicle.GetMuzzleBone(p.VehicleWeapon)) != null)
|
||||
return b.Position + b.ForwardVector * 50;
|
||||
return GetLookingCoord(p);
|
||||
}
|
||||
if (p.IsOnTurretSeat()) { return p.GetLookingCoord(); }
|
||||
if (weapon != null)
|
||||
|
||||
if ((weapon = p.Weapons.CurrentWeaponObject) != null)
|
||||
{
|
||||
// Not very accurate, but doesn't matter
|
||||
Vector3 dir = weapon.RightVector;
|
||||
var dir = weapon.RightVector;
|
||||
return weapon.Position + dir * 20;
|
||||
|
||||
}
|
||||
|
||||
return GetLookingCoord(p);
|
||||
}
|
||||
|
||||
public static Vector3 GetLookingCoord(this Ped p)
|
||||
{
|
||||
if (p == Main.P && Function.Call<int>(Hash.GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
|
||||
{
|
||||
if (p == P && Call<int>(GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
|
||||
return RaycastEverything(default);
|
||||
}
|
||||
EntityBone b = p.Bones[Bone.FacialForehead];
|
||||
Vector3 v = b.UpVector.Normalized;
|
||||
var v = b.UpVector.Normalized;
|
||||
return b.Position + 200 * v;
|
||||
}
|
||||
|
||||
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
|
||||
{
|
||||
return (VehicleSeat)Function.Call<int>(Hash.GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
|
||||
return (VehicleSeat)Call<int>(GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static Vector3 RaycastEverything(Vector2 screenCoord)
|
||||
{
|
||||
Vector3 camPos = GameplayCamera.Position;
|
||||
Vector3 camRot = GameplayCamera.Rotation;
|
||||
const float raycastToDist = 100.0f;
|
||||
const float raycastFromDist = 1f;
|
||||
|
||||
Vector3 target3D = ScreenRelToWorld(camPos, camRot, screenCoord);
|
||||
Vector3 source3D = camPos;
|
||||
|
||||
Entity ignoreEntity = Game.Player.Character;
|
||||
if (Game.Player.Character.IsInVehicle())
|
||||
{
|
||||
ignoreEntity = Game.Player.Character.CurrentVehicle;
|
||||
}
|
||||
|
||||
Vector3 dir = target3D - source3D;
|
||||
dir.Normalize();
|
||||
RaycastResult raycastResults = World.Raycast(source3D + dir * raycastFromDist,
|
||||
source3D + dir * raycastToDist,
|
||||
IntersectFlags.Everything,
|
||||
ignoreEntity);
|
||||
|
||||
if (raycastResults.DidHit)
|
||||
{
|
||||
return raycastResults.HitPosition;
|
||||
}
|
||||
|
||||
return camPos + dir * raycastToDist;
|
||||
}
|
||||
public static Vector3 ScreenRelToWorld(Vector3 camPos, Vector3 camRot, Vector2 coord)
|
||||
{
|
||||
Vector3 camForward = camRot.ToDirection();
|
||||
Vector3 rotUp = camRot + new Vector3(10, 0, 0);
|
||||
Vector3 rotDown = camRot + new Vector3(-10, 0, 0);
|
||||
Vector3 rotLeft = camRot + new Vector3(0, 0, -10);
|
||||
Vector3 rotRight = camRot + new Vector3(0, 0, 10);
|
||||
|
||||
Vector3 camRight = rotRight.ToDirection() - rotLeft.ToDirection();
|
||||
Vector3 camUp = rotUp.ToDirection() - rotDown.ToDirection();
|
||||
|
||||
double rollRad = -camRot.Y.ToRadians();
|
||||
|
||||
Vector3 camRightRoll = camRight * (float)Math.Cos(rollRad) - camUp * (float)Math.Sin(rollRad);
|
||||
Vector3 camUpRoll = camRight * (float)Math.Sin(rollRad) + camUp * (float)Math.Cos(rollRad);
|
||||
|
||||
Vector3 point3D = camPos + camForward * 10.0f + camRightRoll + camUpRoll;
|
||||
if (!WorldToScreenRel(point3D, out Vector2 point2D))
|
||||
{
|
||||
return camPos + camForward * 10.0f;
|
||||
}
|
||||
|
||||
Vector3 point3DZero = camPos + camForward * 10.0f;
|
||||
if (!WorldToScreenRel(point3DZero, out Vector2 point2DZero))
|
||||
{
|
||||
return camPos + camForward * 10.0f;
|
||||
}
|
||||
|
||||
const double eps = 0.001;
|
||||
if (Math.Abs(point2D.X - point2DZero.X) < eps || Math.Abs(point2D.Y - point2DZero.Y) < eps)
|
||||
{
|
||||
return camPos + camForward * 10.0f;
|
||||
}
|
||||
|
||||
float scaleX = (coord.X - point2DZero.X) / (point2D.X - point2DZero.X);
|
||||
float scaleY = (coord.Y - point2DZero.Y) / (point2D.Y - point2DZero.Y);
|
||||
|
||||
return camPos + camForward * 10.0f + camRightRoll * scaleX + camUpRoll * scaleY;
|
||||
}
|
||||
public static bool WorldToScreenRel(Vector3 worldCoords, out Vector2 screenCoords)
|
||||
{
|
||||
OutputArgument num1 = new OutputArgument();
|
||||
OutputArgument num2 = new OutputArgument();
|
||||
|
||||
if (!Function.Call<bool>(Hash.GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y, worldCoords.Z, num1, num2))
|
||||
{
|
||||
screenCoords = new Vector2();
|
||||
return false;
|
||||
}
|
||||
|
||||
screenCoords = new Vector2((num1.GetResult<float>() - 0.5f) * 2, (num2.GetResult<float>() - 0.5f) * 2);
|
||||
return true;
|
||||
}
|
||||
public static void StayInCover(this Ped p)
|
||||
{
|
||||
Function.Call(Hash.TASK_STAY_IN_COVER, p);
|
||||
}
|
||||
|
||||
public static bool IsTurretSeat(this Vehicle veh, int seat)
|
||||
{
|
||||
if (!Function.Call<bool>(Hash.DOES_VEHICLE_HAVE_WEAPONS, veh.Handle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (seat)
|
||||
{
|
||||
case -1:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Rhino
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Khanjari
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.FireTruck
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Riot2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
|
||||
case 0:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dune3;
|
||||
case 1:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical3
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.HalfTrack
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
|
||||
case 2:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
|
||||
case 3:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Limo2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dinghy5;
|
||||
case 7:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Insurgent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static bool IsOnTurretSeat(this Ped P)
|
||||
{
|
||||
if (P.CurrentVehicle == null) { return false; }
|
||||
return IsTurretSeat(P.CurrentVehicle, (int)P.SeatIndex);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// WARNING: values can change after a game update
|
||||
// if R* adds in the middle!
|
||||
// This is up-to-date for b2372
|
||||
|
||||
internal enum TaskType
|
||||
{
|
||||
CTaskHandsUp = 0,
|
||||
@@ -435,4 +436,4 @@ internal enum TaskType
|
||||
CTaskVehiclePullAlongside = 528,
|
||||
CTaskVehicleTransformToSubmarine = 529,
|
||||
CTaskAnimatedFallback = 530
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Client.Menus;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
internal class WorldThread : Script
|
||||
{
|
||||
public static Script Instance;
|
||||
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
|
||||
private static bool _trafficEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public WorldThread()
|
||||
{
|
||||
Instance = this;
|
||||
Aborted += (e) => { DoQueuedActions(); ChangeTraffic(true); };
|
||||
}
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
while (Game.IsLoading)
|
||||
Yield();
|
||||
|
||||
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
|
||||
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
|
||||
}
|
||||
protected override void OnTick()
|
||||
{
|
||||
base.OnTick();
|
||||
|
||||
if (_sleeping)
|
||||
{
|
||||
Game.Pause(true);
|
||||
while (_sleeping)
|
||||
{
|
||||
// Don't wait longer than 5 seconds or the game will crash
|
||||
Thread.Sleep(4500);
|
||||
Yield();
|
||||
}
|
||||
Game.Pause(false);
|
||||
}
|
||||
|
||||
if (Game.IsLoading) return;
|
||||
|
||||
try
|
||||
{
|
||||
CoopMenu.MenuPool.Process();
|
||||
DoQueuedActions();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
|
||||
if (!Networking.IsOnServer) return;
|
||||
|
||||
Game.DisableControlThisFrame(Control.FrontendPause);
|
||||
|
||||
if (Settings.DisableAlternatePause) Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
|
||||
// Sets a value that determines how aggressive the ocean waves will be.
|
||||
// Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
|
||||
Call(SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
|
||||
|
||||
if (Settings.ShowEntityOwnerName)
|
||||
unsafe
|
||||
{
|
||||
int handle;
|
||||
if (Call<bool>(GET_ENTITY_PLAYER_IS_FREE_AIMING_AT, 0, &handle))
|
||||
{
|
||||
var entity = Entity.FromHandle(handle);
|
||||
if (entity != null)
|
||||
{
|
||||
var owner = "invalid";
|
||||
if (entity.EntityType == EntityType.Vehicle)
|
||||
owner = (entity as Vehicle).GetSyncEntity()?.Owner?.Username ?? "unknown";
|
||||
if (entity.EntityType == EntityType.Ped)
|
||||
owner = (entity as Ped).GetSyncEntity()?.Owner?.Username ?? "unknown";
|
||||
Screen.ShowHelpTextThisFrame("Entity owner: " + owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_trafficEnabled)
|
||||
{
|
||||
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
|
||||
Call(SET_PED_POPULATION_BUDGET, 0);
|
||||
Call(SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
|
||||
Call(SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
|
||||
Call(SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
|
||||
Call(SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
|
||||
Call(SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Traffic(bool enable)
|
||||
{
|
||||
ChangeTraffic(enable);
|
||||
_trafficEnabled = enable;
|
||||
}
|
||||
|
||||
private static void ChangeTraffic(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
Call(REMOVE_SCENARIO_BLOCKING_AREAS);
|
||||
Call(SET_CREATE_RANDOM_COPS, true);
|
||||
Call(SET_RANDOM_TRAINS, true);
|
||||
Call(SET_RANDOM_BOATS, true);
|
||||
Call(SET_GARBAGE_TRUCKS, true);
|
||||
Call(SET_PED_POPULATION_BUDGET, 3); // 0 - 3
|
||||
Call(SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
|
||||
Call(SET_ALL_VEHICLE_GENERATORS_ACTIVE);
|
||||
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
|
||||
Call(SET_NUMBER_OF_PARKED_VEHICLES, -1);
|
||||
Call(SET_DISTANT_CARS_ENABLED, true);
|
||||
Call(DISABLE_VEHICLE_DISTANTLIGHTS, false);
|
||||
}
|
||||
else if (Networking.IsOnServer)
|
||||
{
|
||||
Call(ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f,
|
||||
1000.0f, 0, 1, 1, 1);
|
||||
Call(SET_CREATE_RANDOM_COPS, false);
|
||||
Call(SET_RANDOM_TRAINS, false);
|
||||
Call(SET_RANDOM_BOATS, false);
|
||||
Call(SET_GARBAGE_TRUCKS, false);
|
||||
Call(DELETE_ALL_TRAINS);
|
||||
Call(SET_PED_POPULATION_BUDGET, 0);
|
||||
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
|
||||
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
|
||||
Call(SET_FAR_DRAW_VEHICLES, false);
|
||||
Call(SET_NUMBER_OF_PARKED_VEHICLES, 0);
|
||||
Call(SET_DISTANT_CARS_ENABLED, false);
|
||||
Call(DISABLE_VEHICLE_DISTANTLIGHTS, true);
|
||||
foreach (var ped in World.GetAllPeds())
|
||||
{
|
||||
if (ped == Game.Player.Character) continue;
|
||||
var c = EntityPool.GetPedByHandle(ped.Handle);
|
||||
if (c == null || (c.IsLocal && ped.Handle != Game.Player.Character.Handle &&
|
||||
ped.PopulationType != EntityPopulationType.Mission))
|
||||
{
|
||||
Log.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
|
||||
ped.CurrentVehicle?.Delete();
|
||||
ped.Kill();
|
||||
ped.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var veh in World.GetAllVehicles())
|
||||
{
|
||||
var v = veh.GetSyncEntity();
|
||||
if (v.MainVehicle == Game.Player.LastVehicle ||
|
||||
v.MainVehicle == Game.Player.Character.CurrentVehicle)
|
||||
// Don't delete player's vehicle
|
||||
continue;
|
||||
if (v == null || (v.IsLocal && veh.PopulationType != EntityPopulationType.Mission))
|
||||
// Log.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
|
||||
|
||||
veh.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Delay(Action a, int time)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
QueueAction(a);
|
||||
});
|
||||
}
|
||||
|
||||
internal static void DoQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
foreach (var action in QueuedActions.ToArray())
|
||||
try
|
||||
{
|
||||
if (action()) QueuedActions.Remove(action);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a">
|
||||
/// An action to be executed with a return value indicating whether the action can be removed after
|
||||
/// execution.
|
||||
/// </param>
|
||||
internal static void QueueAction(Func<bool> a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void QueueAction(Action a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(() =>
|
||||
{
|
||||
a();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all queued actions
|
||||
/// </summary>
|
||||
internal static void ClearQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Clear();
|
||||
}
|
||||
}
|
||||
private static bool _sleeping;
|
||||
[ConsoleCommand("Put the game to sleep state by blocking main thread, press any key in the debug console to resume")]
|
||||
public static void Sleep()
|
||||
{
|
||||
if (_sleeping)
|
||||
throw new InvalidOperationException("Already in sleep state");
|
||||
|
||||
_sleeping = true;
|
||||
Task.Run(() =>
|
||||
{
|
||||
System.Console.WriteLine("Press any key to put the game out of sleep state");
|
||||
System.Console.ReadKey();
|
||||
System.Console.WriteLine("Game resumed");
|
||||
_sleeping = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user