97 Commits

Author SHA1 Message Date
4fbdd86566 Use struct to serialize vehicle data
This gonna reduce the tedious work needed to add a new sync, also beings a performance boost
Ped, projectile sync will be updated later
2023-03-26 15:36:15 +08:00
826e80c5d8 Reintroduce compact vector types 2023-03-26 12:03:06 +08:00
3944d41e85 Merge branch '1.6-pre-alpha' of https://github.com/RAGECOOP/RAGECOOP-V into 1.6-pre-alpha 2023-03-23 22:30:42 +08:00
114e59a3bd Add toggle mods sync 2023-03-23 22:30:39 +08:00
db7146e00e Add Xenon Headlights Color Sync (#45) 2023-03-20 23:13:43 +08:00
e6718cec58 Don't sync non-existent extras 2023-03-20 18:20:07 +08:00
6f2c0077cb Add vehicle extras sync 2023-03-20 18:05:14 +08:00
5f1aadbdb5 Don't use dictionary to store vehicle mods 2023-03-20 17:14:52 +08:00
7cd0fd22af Use tuple to store vehicle colors
Array allocation is somewhat expensive
2023-03-20 17:02:56 +08:00
e6c6e5ceff Update some data stuff 2023-03-19 20:35:42 +08:00
bda1dac90b Smoother sync, I guess 2023-03-19 17:47:00 +08:00
0bc89a9bf3 Fix broken radio sync 2023-03-19 16:58:07 +08:00
7e019cc112 Don't throw if muzzle bone was not found 2023-03-19 15:22:38 +08:00
713e005975 Ditch Newtonsoft.Json in favor of System.Text.Json 2023-03-19 15:13:24 +08:00
0cb1098819 Weapon sync cleanup 1 2023-03-19 13:43:46 +08:00
ba8c2c741d Fix broken vehicle weapon sync when no impact is found 2023-03-19 13:26:18 +08:00
feb20c47f4 Back to manual assembly versioning 2023-03-18 16:02:40 +08:00
7f4aa47943 Update shvdn 2023-03-18 15:58:50 +08:00
525bd495b4 Fix weid nametag size 2023-03-18 15:58:41 +08:00
cd70256315 Tunable debug values 2023-03-18 15:58:31 +08:00
920b5d15ba New nametag rendering 2023-03-15 15:58:03 +08:00
fd4b5f8afb Fix wrong settings description 2023-03-15 11:42:32 +08:00
bdcdf75a8e Update shvdnc 2023-03-12 13:33:59 +08:00
f100261e88 Update stuff 2023-03-11 17:08:07 +08:00
cea49dae2b PInvoke code cleanup 2023-03-11 16:39:20 +08:00
f44715cdf4 Cleanup math and benchmark stuff 2023-03-11 16:09:22 +08:00
57c9c49757 Merge pull request #44 from RAGECOOP/net7-shvdnc
Use SHVDNC as new runtime
2023-03-11 15:30:14 +08:00
df9efb6210 Use quaternion differentiation to calibrate rotation
Hopefully fixes the wobbling issue
2023-03-11 15:22:25 +08:00
cc92d1ad2b Apply positional calibration force to center of mass 2023-03-08 18:33:40 +08:00
63f11053c7 Use InternalImpulse to calibrate entity position 2023-03-08 18:27:23 +08:00
11c7ee19a3 Dynamically resolve data path 2023-03-08 11:09:47 +08:00
211ab4ee7c Cleanup project file 2023-03-07 21:01:08 +08:00
83f7cbc963 Working client resource system 2023-03-07 20:17:53 +08:00
2451131e36 Prepare for CoreCLR version 2023-03-06 21:54:41 +08:00
0e5271b322 Basically working resource system 2023-02-27 11:54:02 +08:00
6e2b4aff2f Fix stuff 2023-02-16 18:56:56 +08:00
e5f426690f Introduce CustomEventHandler for more flexible invocation 2023-02-15 11:38:02 +08:00
e4f432b593 Cleanup and rewrite some bullshit 2023-02-13 20:44:50 +08:00
f555fef48d Optimize imports 2023-02-13 17:51:18 +08:00
ac07edfe68 Client API remoting stuff 2023-02-12 22:06:57 +08:00
fb654f0e56 Update submodules 2023-02-12 22:05:38 +08:00
718814c491 Fix crashing due to bug in BufferWriter.ToByteArray()
Renamed the serialisers and added CustomEvents to unit test to be more future-proof
2023-02-03 13:37:20 +08:00
b4f86719ce Join network thread before unload 2023-02-03 13:30:37 +08:00
6792559b5b Update Lidgren submodule 2023-02-03 13:13:59 +08:00
ffb31b0986 Initialise submodules first in build actions 2023-02-01 21:38:58 +08:00
f1b9bf0571 Some works for the new resource system
Rewrite some parts of CustomEvent
Expose some API as dll entry
2023-02-01 21:29:25 +08:00
d4df041c44 Add Lidgren.Network and SHVDNC as submodule 2023-02-01 13:23:09 +08:00
a7ea28281e Some fancy classes for faster serialization 2023-01-31 20:23:52 +08:00
6a1ebcf9fe Fix build commands 2023-01-28 23:19:05 +08:00
a79f9f2a69 Fix dll 2023-01-28 23:12:02 +08:00
798776c822 GC: avoid unnecessary instance creation 2023-01-28 22:25:11 +08:00
9cb7b28d8f Hardlink 2023-01-28 20:57:05 +08:00
cac2385c35 Initial migration commit to .NET 7
Menu, sync and other stuff except resource system should be working.
We're far from finished
2023-01-28 20:51:29 +08:00
0112140f0e Fix build copy 2022-12-04 21:11:33 +08:00
174f711e42 Delete subprocess folder after build 2022-12-04 20:57:42 +08:00
3a9068f060 Various weapon fixes and some change on vehicle sync 2022-12-04 19:34:54 +08:00
4cc5054b91 Fix installer 2022-11-30 20:25:07 +08:00
a9b05c62ff Cleanup and update installer 2022-11-30 20:17:06 +08:00
952ab4f7b3 Update to work with latest build of SHVDN 2022-11-30 19:08:52 +08:00
598790dedd Save blip and nametag settings 2022-11-20 16:29:11 +08:00
979be4cfa8 blah 2022-11-20 15:56:46 +08:00
7dec978e9a Improved vehicle sync 2022-11-20 15:55:11 +08:00
78f0dfaebb Add option to disable blip and nametag display at client side 2022-11-19 14:26:31 +08:00
e6c8d4df46 "stuff" 2022-11-19 12:54:22 +08:00
279492e0bd Hmm... 2022-11-19 12:05:21 +08:00
478305112f Changed some variables from ServerInfo to an integer. .NET updated to version 7.0
Not compatible with the current MasterServer. `https://test.ragecoop.online/` is compatible
2022-11-19 00:01:38 +01:00
745b212b42 cleanup 2022-11-16 17:40:07 +08:00
50153d860a Tidy up weapon code and data dumper 2022-11-16 17:37:31 +08:00
822a69573f Remove update menu 2022-11-13 17:06:27 +08:00
23e4151b2e Use Direct2D for CEF testing 2022-11-13 12:41:26 +08:00
71195e93c2 Okay boomer 2022-11-05 18:43:59 +08:00
8961eb102b Initial CEF and DirectX overlay implementation (PoC, unfinished)
Still pretty unstable, game might crash at times.
Revamp build system and other small fixes
2022-11-05 18:35:39 +08:00
2828b9b74f Clean up 2022-10-23 19:02:39 +08:00
6b34ab6e36 DataDumper 2022-10-21 19:41:38 +08:00
69419d41e0 Improved weapon and turret sync 2022-10-19 19:07:46 +08:00
a50ae062d8 Fix resource loading 2022-10-19 19:07:29 +08:00
9e0ce58bd2 Don't delete peds in vehicle 2022-10-15 18:23:12 +08:00
806f1d194a Longer waiting time for resource loading 2022-10-15 18:18:36 +08:00
4e6bab1b53 blah 2022-10-15 18:14:35 +08:00
64692bc161 Update 2022-10-15 17:16:47 +08:00
8d27c072ca Change package format to iso9660 2022-10-15 17:06:19 +08:00
d7c0abdfc2 Bump version 2022-10-15 15:54:46 +08:00
d0b6bbaa3a Update installer 2022-10-15 15:30:58 +08:00
411b199a98 Unload fix and small tweaks 2022-10-15 13:52:49 +08:00
b48b15b652 Tidy up 2022-10-15 12:15:19 +08:00
e83480b7f9 Update action 2022-10-15 11:57:26 +08:00
d1b4f23992 Restructure solution 2022-10-15 11:51:18 +08:00
42c0ef2159 Load in client in seperate domain
Rewrote logger for more flexible options
Complete client resource loading
2022-10-15 11:09:17 +08:00
8701ac703e blah 2022-10-10 16:45:58 +08:00
fe53e01a4a ClientScript loading logic 2022-10-09 23:35:30 +08:00
617dbc9812 Change settings and data directory 2022-10-09 22:07:52 +08:00
1606d25fed Queue action in WorldThread 2022-10-09 11:15:09 +08:00
5585876005 API remoting 2022-10-08 23:49:48 +08:00
54c7f4ce92 ResourceDomain 2022-10-08 12:43:24 +08:00
a062322dda Fix queued events 2022-10-05 18:10:56 +08:00
4599c558e4 Add back Hash method for compatabilit 2022-10-05 16:09:43 +08:00
71f7f4b257 Use implicit operator for CustomEventHash, add CustomEventFlags
Resource rebuild will be required, but no code change is needed
2022-10-05 15:53:57 +08:00
265 changed files with 359913 additions and 14678 deletions

4
.editorconfig Normal file
View File

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

View File

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

View File

@ -1,32 +0,0 @@
name: Build and Push Docker Image to GHCR
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set repo owner to lowercase
run: echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image to GHCR
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ghcr.io/${{ env.REPO_OWNER }}/ragecoop-v:latest
dockerfile: Dockerfile

View File

@ -1,14 +1,16 @@
name: Nightly-build name: Nightly-build
on: push on:
push:
branches: [ "dev-nightly" ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: windows-latest
strategy: strategy:
matrix: matrix:
dotnet-version: ['6.0.x'] dotnet-version: ['7.0.x']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -16,30 +18,72 @@ jobs:
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v2
with: with:
dotnet-version: ${{ matrix.dotnet-version }} dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies - name: Restore dependencies
run: dotnet restore run: dotnet restore
- name: Restore nuget packages - name: Restore nuget packages
run: | run: nuget restore
sudo apt update
sudo apt install -y nuget - name: Build artifacts
nuget restore run: .\build.cmd
- name: Build client and installer
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop - name: Build server for different platforms
- name: Build server win-x64 run: .\publish-server.cmd
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release
- name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: bin/Release/Client files: bin/Release/Client
dest: RageCoop.Client.zip dest: RageCoop.Client.zip
- uses: vimtor/action-zip@v1
with:
files: bin/API
dest: bin/Artifacts/SDK.zip
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: bin/Release/Server/win-x64 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 - uses: vimtor/action-zip@v1
with: with:
files: bin/Release/Server/linux-x64 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: bin/Artifacts/RageCoop.Server-linux-arm.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm64
dest: bin/Artifacts/RageCoop.Server-linux-arm64.zip
- name: Deploy binaries
uses: Sardelka9515/deploy-nightly@v0.7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/RAGECOOP/GTAV-RESOURCES/releases/75463254/assets{?name,label}
release_id: 75463254
asset_path: bin/Artifacts
asset_content_type: application/zip
max_releases: 7
- uses: actions/checkout@v2

3
.gitignore vendored
View File

@ -2,5 +2,4 @@
**/obj **/obj
**/packages **/packages
**/.vs **/.vs
**/.vscode **/.vscode
**/.idea

6
.gitmodules vendored Normal file
View File

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

View File

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

View File

@ -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;
}
}
}

View File

@ -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)
{
;
}
}
}

130
Client/CefHost/Program.cs Normal file
View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RageCoop.Client.CefHost")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RageCoop.Client.CefHost")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ba750e08-5e41-4b56-8ad5-875716d2ccea")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

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

View File

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

View File

@ -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
Client/Data/Animations.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

329
Client/Data/WeaponHash.cs Normal file
View File

@ -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,
}

2891
Client/Data/Weapons.json Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -3,13 +3,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RageCoop.Client.Installer"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="376" Width="617" Background="#FFBABABA"> Title="MainWindow" Height="376" Width="617" Background="#FFBABABA">
<Grid> <Grid>
<Grid.Background> <Grid.Background>
<ImageBrush ImageSource="/bg.png" Opacity="0.2"/> <ImageBrush ImageSource="/bg.png" Opacity="0.2" />
</Grid.Background> </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> </Grid>
</Window> </Window>

View File

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

View File

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

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

24
Client/Loader/Console.cs Normal file
View File

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

View File

@ -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
}
}

51
Client/Loader/Main.cs Normal file
View File

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

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RageCoop.Client.Loader")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RageCoop.Client.Loader")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("fc8cbdbb-6dc3-43af-b34d-092e476410a5")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RageCoop.Client.Loader</RootNamespace>
<AssemblyName>RageCoop.Client.Loader</AssemblyName>
<OutPutPath>..\..\bin\$(Configuration)\Client\Loader</OutPutPath>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ScriptHookVDotNet3, Version=3.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>True</SpecificVersion>
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.asi</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Console.cs" />
<Compile Include="LoaderContext.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}
}

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

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

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

View File

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

View File

@ -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
{
}
}

View File

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

View File

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

View File

@ -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
}
}

View File

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

View File

@ -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;
}
}
}

420
Client/Scripts/Main.cs Normal file
View File

@ -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;
}
}
}
}

View File

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

View File

@ -0,0 +1,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);
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -1,50 +1,49 @@
using GTA.UI; using System;
using LemonUI.Menus;
using Newtonsoft.Json;
using RageCoop.Core;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading; using System.Threading;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client.Menus namespace RageCoop.Client.Menus
{ {
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
internal static class ServersMenu internal static class ServersMenu
{ {
private static Thread GetServersThread; private static Thread GetServersThread;
internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list") internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list")
{ {
UseMouse = false, 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; internal static NativeItem ResultItem = null;
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
static ServersMenu() static ServersMenu()
{ {
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0); Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0); Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) => Menu.Opening += (object sender, CancelEventArgs e) =>
{ {
CleanUpList(); CleanUpList();
Menu.Add(ResultItem = new NativeItem("Loading...")); Menu.Add(ResultItem = new NativeItem("Loading..."));
// Prevent freezing // Prevent freezing
GetServersThread = new Thread(() => GetAllServers()); GetServersThread = ThreadManager.CreateThread(() => GetAllServers(),"GetServers");
GetServersThread.Start();
};
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
{
CleanUpList();
}; };
Menu.Closing += (object sender, CancelEventArgs e) => { CleanUpList(); };
} }
private static void CleanUpList() private static void CleanUpList()
@ -56,27 +55,34 @@ namespace RageCoop.Client.Menus
private static void GetAllServers() private static void GetAllServers()
{ {
List<ServerInfo> serverList = null; List<ServerInfo> serverList = null;
var realUrl = Main.Settings.MasterServer; var realUrl = Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl)); serverList = null;
try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
catch (Exception ex) { Log.Error(ex); }
// Need to be processed in main thread // Need to be processed in main thread
Main.QueueAction(() => API.QueueAction(() =>
{ {
if (serverList == null) if (serverList == null)
{ {
ResultItem.Title = "Something went wrong!"; ResultItem.Title = "Something went wrong!";
return; return;
} }
if (serverList.Count == 0) if (serverList.Count == 0)
{ {
ResultItem.Title = "No server was found!"; ResultItem.Title = "No server was found!";
return; return;
} }
CleanUpList(); CleanUpList();
foreach (ServerInfo server in serverList) foreach (ServerInfo server in serverList)
{ {
string address = $"{server.address}:{server.port}"; string address = $"{server.address}:{server.port}";
NativeItem tmpItem = new NativeItem($"[{server.country}] {server.name}", $"~b~{address}~s~~n~~g~Version {server.version}~s~") { AltTitle = $"[{server.players}/{server.maxPlayers}]" }; NativeItem tmpItem =
new NativeItem($"[{server.country}] {server.name}",
$"~b~{address}~s~~n~~g~Version {server.version}.x~s~")
{ AltTitle = $"[{server.players}/{server.maxPlayers}]" };
tmpItem.Activated += (object sender, EventArgs e) => tmpItem.Activated += (object sender, EventArgs e) =>
{ {
try try
@ -91,13 +97,14 @@ namespace RageCoop.Client.Menus
throw new Exception("Failed to obtain ZeroTier network IP"); throw new Exception("Failed to obtain ZeroTier network IP");
} }
} }
Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server)); Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server));
#if !NON_INTERACTIVE #if !NON_INTERACTIVE
CoopMenu.ServerIpItem.AltTitle = address; CoopMenu.ServerIpItem.AltTitle = address;
CoopMenu.Menu.Visible = true; CoopMenu.Menu.Visible = true;
#endif #endif
Main.Settings.LastServerAddress = address; Settings.LastServerAddress = address;
Util.SaveSettings(); Util.SaveSettings();
} }
catch (Exception ex) catch (Exception ex)
@ -105,7 +112,8 @@ namespace RageCoop.Client.Menus
Notification.Show($"~r~{ex.Message}"); Notification.Show($"~r~{ex.Message}");
if (server.useZT) 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) private static string DownloadString(string url)
{ {
try try
@ -122,12 +131,12 @@ namespace RageCoop.Client.Menus
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new WebClient(); var client = new HttpClient();
return client.DownloadString(url); return client.GetStringAsync(url).GetAwaiter().GetResult();
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.QueueAction(() => API.QueueAction(() =>
{ {
ResultItem.Title = "Download failed!"; ResultItem.Title = "Download failed!";
ResultItem.Description = ex.Message; ResultItem.Description = ex.Message;

View File

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

View File

@ -1,9 +1,8 @@
using GTA; using System;
using GTA.Native;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Windows.Forms; using GTA;
using GTA.Native;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -11,18 +10,21 @@ namespace RageCoop.Client
{ {
private readonly Scaleform MainScaleForm; private readonly Scaleform MainScaleForm;
public Chat()
{
MainScaleForm = new Scaleform("multiplayer_chat");
}
public string CurrentInput { get; set; } public string CurrentInput { get; set; }
private bool CurrentFocused { get; set; } private bool CurrentFocused { get; set; }
public bool Focused public bool Focused
{ {
get => CurrentFocused; get => CurrentFocused;
set set
{ {
if (value && Hidden) if (value && Hidden) Hidden = false;
{
Hidden = false;
}
MainScaleForm.CallFunction("SET_FOCUS", value ? 2 : 1, 2, "ALL"); MainScaleForm.CallFunction("SET_FOCUS", value ? 2 : 1, 2, "ALL");
@ -33,6 +35,7 @@ namespace RageCoop.Client
private ulong LastMessageTime { get; set; } private ulong LastMessageTime { get; set; }
private bool CurrentHidden { get; set; } private bool CurrentHidden { get; set; }
private bool Hidden private bool Hidden
{ {
get => CurrentHidden; get => CurrentHidden;
@ -40,10 +43,7 @@ namespace RageCoop.Client
{ {
if (value) if (value)
{ {
if (!CurrentHidden) if (!CurrentHidden) MainScaleForm.CallFunction("hide");
{
MainScaleForm.CallFunction("hide");
}
} }
else if (CurrentHidden) else if (CurrentHidden)
{ {
@ -54,11 +54,6 @@ namespace RageCoop.Client
} }
} }
public Chat()
{
MainScaleForm = new Scaleform("multiplayer_chat");
}
public void Init() public void Init()
{ {
MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL"); MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL");
@ -72,22 +67,13 @@ namespace RageCoop.Client
public void Tick() public void Tick()
{ {
if ((Util.GetTickCount64() - LastMessageTime) > 15000 && !Focused && !Hidden) if (Util.GetTickCount64() - LastMessageTime > 15000 && !Focused && !Hidden) Hidden = true;
{
Hidden = true;
}
if (!Hidden) if (!Hidden) MainScaleForm.Render2D();
{
MainScaleForm.Render2D();
}
if (!CurrentFocused) if (!CurrentFocused) return;
{
return;
}
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, 0); Call(DISABLE_ALL_CONTROL_ACTIONS, 0);
} }
public void AddMessage(string sender, string msg) public void AddMessage(string sender, string msg)
@ -107,20 +93,12 @@ namespace RageCoop.Client
} }
if (key == Keys.PageUp) if (key == Keys.PageUp)
{
MainScaleForm.CallFunction("PAGE_UP"); 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) if (keyChar.Length == 0) return;
{
return;
}
switch (keyChar[0]) switch (keyChar[0])
{ {
@ -130,14 +108,12 @@ namespace RageCoop.Client
CurrentInput = CurrentInput.Remove(CurrentInput.Length - 1); CurrentInput = CurrentInput.Remove(CurrentInput.Length - 1);
MainScaleForm.CallFunction("DELETE_TEXT"); MainScaleForm.CallFunction("DELETE_TEXT");
} }
return; return;
case (char)13: case (char)13:
MainScaleForm.CallFunction("ADD_TEXT", "ENTER"); MainScaleForm.CallFunction("ADD_TEXT", "ENTER");
if (!string.IsNullOrWhiteSpace(CurrentInput)) if (!string.IsNullOrWhiteSpace(CurrentInput)) Networking.SendChatMessage(CurrentInput);
{
Networking.SendChatMessage(CurrentInput);
}
Focused = false; Focused = false;
CurrentInput = ""; CurrentInput = "";
@ -151,19 +127,19 @@ namespace RageCoop.Client
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern int ToUnicodeEx(uint virtualKeyCode, uint scanCode, byte[] keyboardState, 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, StringBuilder receivingBuffer,
int bufferSize, uint flags, IntPtr kblayout); 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) public static string GetCharFromKey(Keys key, bool shift, bool altGr)
{ {
StringBuilder buf = new StringBuilder(256); var buf = new StringBuilder(256);
byte[] keyboardState = new byte[256]; var keyboardState = new byte[256];
if (shift) if (shift) keyboardState[(int)Keys.ShiftKey] = 0xff;
{
keyboardState[(int)Keys.ShiftKey] = 0xff;
}
if (altGr) if (altGr)
{ {
@ -171,8 +147,8 @@ namespace RageCoop.Client
keyboardState[(int)Keys.Menu] = 0xff; 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(); return buf.ToString();
} }
} }
} }

View File

@ -1,88 +1,94 @@
using RageCoop.Core; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using static RageCoop.Client.Shared;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static class DownloadManager 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() static DownloadManager()
{ {
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) => Networking.RequestHandlers.Add(PacketType.FileTransferRequest, data =>
{ {
var fr = new Packets.FileTransferRequest(); var fr = new Packets.FileTransferRequest();
fr.Deserialize(data); fr.Deserialize(data);
if (fr.Name.EndsWith(".res")) if (fr.Name.EndsWith(".res")) _resources.Add(fr.Name);
{ return new Packets.FileTransferResponse
_resources.Add(fr.Name);
}
return new Packets.FileTransferResponse()
{ {
ID = fr.ID, 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); packet.Deserialize(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}"); Log.Debug($"Finalizing download:{packet.ID}");
Complete(packet.ID); Complete(packet.ID);
// Inform the server that the download is completed // Inform the server that the download is completed
return new Packets.FileTransferResponse() return new Packets.FileTransferResponse
{ {
ID = packet.ID, ID = packet.ID,
Response = FileResponse.Completed Response = FileResponse.Completed
}; };
}); });
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, (data) => Networking.RequestHandlers.Add(PacketType.AllResourcesSent, data =>
{ {
try try
{ {
Main.Resources.Load(ResourceFolder, _resources.ToArray()); Directory.CreateDirectory(ResourceFolder);
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.Loaded }; MainRes.Load(ResourceFolder, _resources.ToArray());
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.Loaded };
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger.Error("Error occurred when loading server resource:"); Log.Error("Error occurred when loading server resource");
Main.Logger.Error(ex); Log.Error(ex);
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed }; 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>(); public static string ResourceFolder => Path.GetFullPath(Path.Combine(DataPath, "Resources",
private static readonly HashSet<string> _resources = new HashSet<string>(); API.ServerEndPoint.ToString().Replace(":", ".")));
public static event EventHandler<string> DownloadCompleted;
public static bool AddFile(int id, string name, long length) public static bool AddFile(int id, string name, long length)
{ {
var path = $"{ResourceFolder}\\{name}"; 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)) if (!Directory.Exists(Directory.GetParent(path).FullName))
{
Directory.CreateDirectory(Directory.GetParent(path).FullName); Directory.CreateDirectory(Directory.GetParent(path).FullName);
}
if (FileAlreadyExists(ResourceFolder, name, length)) 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)); DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
return false; return false;
} }
/* /*
if (!name.EndsWith(".zip")) if (!name.EndsWith(".zip"))
{ {
Main.Logger.Error($"File download blocked! [{name}]"); Log.Error($"File download blocked! [{name}]");
return false; return false;
} }
*/ */
lock (InProgressDownloads) lock (InProgressDownloads)
{ {
InProgressDownloads.Add(id, new DownloadFile() InProgressDownloads.Add(id, new DownloadFile
{ {
FileID = id, FileID = id,
FileName = name, FileName = name,
@ -90,11 +96,12 @@ namespace RageCoop.Client
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite) Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
}); });
} }
return true; return true;
} }
/// <summary> /// <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> /// </summary>
/// <param name="folder"></param> /// <param name="folder"></param>
/// <param name="name"></param> /// <param name="name"></param>
@ -102,15 +109,11 @@ namespace RageCoop.Client
/// <returns></returns> /// <returns></returns>
private static bool FileAlreadyExists(string folder, string name, long length) private static bool FileAlreadyExists(string folder, string name, long length)
{ {
string filePath = $"{folder}\\{name}"; var filePath = $"{folder}\\{name}";
if (File.Exists(filePath)) if (File.Exists(filePath))
{ {
if (new FileInfo(filePath).Length == length) if (new FileInfo(filePath).Length == length) return true;
{
return true;
}
// Delete the file because the length is wrong (maybe the file was updated) // Delete the file because the length is wrong (maybe the file was updated)
File.Delete(filePath); File.Delete(filePath);
} }
@ -122,31 +125,25 @@ namespace RageCoop.Client
{ {
lock (InProgressDownloads) lock (InProgressDownloads)
{ {
if (InProgressDownloads.TryGetValue(id, out DownloadFile file)) if (InProgressDownloads.TryGetValue(id, out var file))
{
file.Stream.Write(chunk, 0, chunk.Length); file.Stream.Write(chunk, 0, chunk.Length);
}
else else
{ Log.Trace($"Received unhandled file chunk:{id}");
Main.Logger.Trace($"Received unhandled file chunk:{id}");
}
} }
} }
public static void Complete(int id) public static void Complete(int id)
{ {
if (InProgressDownloads.TryGetValue(id, out var f))
if (InProgressDownloads.TryGetValue(id, out DownloadFile f))
{ {
InProgressDownloads.Remove(id); InProgressDownloads.Remove(id);
f.Dispose(); f.Dispose();
Main.Logger.Info($"Download finished:{f.FileName}"); Log.Info($"Download finished:{f.FileName}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName)); DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
} }
else else
{ {
Main.Logger.Error($"Download not found! {id}"); Log.Error($"Download not found! {id}");
} }
} }
@ -154,24 +151,22 @@ namespace RageCoop.Client
{ {
lock (InProgressDownloads) lock (InProgressDownloads)
{ {
foreach (var file in InProgressDownloads.Values) foreach (var file in InProgressDownloads.Values) file.Dispose();
{
file.Dispose();
}
InProgressDownloads.Clear(); InProgressDownloads.Clear();
} }
_resources.Clear();
_resources.Clear();
} }
} }
internal class DownloadFile : IDisposable internal class DownloadFile : IDisposable
{ {
public int FileID { get; set; } = 0; public int FileID { get; set; }
public string FileName { get; set; } = string.Empty; 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 long FileWritten { get; set; } = 0;
public FileStream Stream { get; set; } public FileStream Stream { get; set; }
public void Dispose() public void Dispose()
{ {
if (Stream != null) if (Stream != null)
@ -182,4 +177,4 @@ namespace RageCoop.Client
} }
} }
} }
} }

View File

@ -1,14 +1,14 @@
using Lidgren.Network; using System;
using RageCoop.Core;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Timers; using System.Timers;
using Lidgren.Network;
using RageCoop.Core;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static partial class HolePunch internal static class HolePunch
{ {
static HolePunch() static HolePunch()
{ {
@ -22,25 +22,26 @@ namespace RageCoop.Client
{ {
try try
{ {
if (!Networking.IsOnServer) { return; } if (!Networking.IsOnServer) return;
foreach (var p in PlayerList.Players.Values.ToArray()) foreach (var p in PlayerList.Players.Values.ToArray())
{ if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null ||
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null || p.Connection.Status == NetConnectionStatus.Disconnected)) 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(); var msg = Networking.Peer.CreateMessage();
new Packets.HolePunch new Packets.HolePunch
{ {
Puncher = Main.LocalPlayerID, Puncher = LocalPlayerID,
Status = p.HolePunchStatus Status = p.HolePunchStatus
}.Pack(msg); }.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) 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)) 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.InternalEndPoint = CoreUtils.StringToEndPoint(p.TargetInternal);
player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal); player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal);
player.ConnectWhenPunched = p.Connect; player.ConnectWhenPunched = p.Connect;
} }
else 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) 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)) 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); puncher.HolePunchStatus = (byte)(p.Status + 1);
if (p.Status >= 3) if (p.Status >= 3)
{ {
Main.Logger.Debug("HolePunch sucess: " + from + ", " + puncher.ID); Log.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
if (puncher.ConnectWhenPunched && (puncher.Connection == null || puncher.Connection.Status == NetConnectionStatus.Disconnected)) 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(); 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); puncher.Connection = Networking.Peer.Connect(from, msg);
Networking.Peer.FlushSendQueue(); Networking.Peer.FlushSendQueue();
} }
@ -80,4 +83,4 @@ namespace RageCoop.Client
} }
} }
} }
} }

View File

@ -1,56 +1,62 @@
using GTA.UI; using System;
using Lidgren.Network;
using RageCoop.Core;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GTA.UI;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static partial class Networking 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; 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; CoopMenu.Menu.Visible = false;
if (IsConnecting) if (IsConnecting)
{ {
_publicKeyReceived.Set(); _publicKeyReceived.Set();
IsConnecting = false; IsConnecting = false;
Main.QueueAction(() => Notification.Show("Connection has been canceled")); API.QueueAction(() =>
Peer?.Shutdown("Bye"); Notification.Show("Connection has been canceled"));
Peer.Shutdown("bye");
} }
else if (IsOnServer) else if (IsOnServer)
{ {
Peer?.Shutdown("Bye"); Peer.Shutdown("bye");
} }
else else
{ {
Peer?.Dispose();
IsConnecting = true; IsConnecting = true;
password = password ?? Main.Settings.Password; password ??= Settings.Password;
username = username ?? Main.Settings.Username; username ??= Settings.Username;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP // 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0") var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{ {
AutoFlushSendQueue = false, AutoFlushSendQueue = false,
AcceptIncomingConnections = true, AcceptIncomingConnections = true,
@ -61,31 +67,31 @@ namespace RageCoop.Client
config.SimulatedMinimumLatency = SimulatedLatency; config.SimulatedMinimumLatency = SimulatedLatency;
config.SimulatedRandomLatency = 0; config.SimulatedRandomLatency = 0;
#endif #endif
config.EnableMessageType(NetIncomingMessageType.UnconnectedData); config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess); 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) if (idx != -1)
{ {
ip[0] = address.Substring(0, idx); ip[0] = address.Substring(0, idx);
ip[1] = address.Substring(idx + 1); ip[1] = address.Substring(idx + 1);
} }
if (ip.Length != 2) if (ip.Length != 2) throw new Exception("Malformed URL");
{
throw new Exception("Malformed URL");
}
PlayerList.Cleanup(); PlayerList.Cleanup();
EntityPool.AddPlayer(); 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; IsConnecting = false;
return; return;
} }
Task.Run(() =>
ThreadManager.CreateThread(() =>
{ {
try try
{ {
@ -93,25 +99,26 @@ namespace RageCoop.Client
// Ensure static constructor invocation // Ensure static constructor invocation
DownloadManager.Cleanup(); DownloadManager.Cleanup();
Peer = new CoopPeer(config); Peer = new CoopPeer(config,Log);
Peer.OnMessageReceived += (s, m) => Peer.OnMessageReceived += (s, m) =>
{ {
try { ProcessMessage(m); } try
{
ProcessMessage(m);
}
catch (Exception ex) catch (Exception ex)
{ {
#if DEBUG Log.Error(ex);
Main.Logger.Error(ex);
#endif
} }
}; };
Main.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); }); API.QueueAction(() => { Notification.Show("~y~Trying to connect..."); });
Menus.CoopMenu._serverConnectItem.Enabled = false; CoopMenu._serverConnectItem.Enabled = false;
Security.Regen(); Security.Regen();
if (publicKey == null) if (publicKey == null)
{ {
if (!GetServerPublicKey(ip[0], int.Parse(ip[1]))) 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"); throw new TimeoutException("Failed to retrive server's public key");
} }
} }
@ -121,60 +128,79 @@ namespace RageCoop.Client
} }
// Send handshake packet // Send handshake packet
NetOutgoingMessage outgoingMessage = Peer.CreateMessage(); var outgoingMessage = Peer.CreateMessage();
var handshake = new Packets.Handshake() var handshake = new Packets.Handshake
{ {
PedID = Main.LocalPlayerID, PedID = LocalPlayerID,
Username = username, Username = username,
ModVersion = Main.Version.ToString(), ModVersion = Main.ModVersion.ToString(),
PasswordEncrypted = Security.Encrypt(password.GetBytes()), 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); Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted, out handshake.AesIVCrypted);
handshake.Pack(outgoingMessage); handshake.Pack(outgoingMessage);
ServerConnection = Peer.Connect(ip[0], short.Parse(ip[1]), outgoingMessage); ServerConnection = Peer.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger.Error("Cannot connect to server: ", ex); Log.Error("Cannot connect to server: ", ex);
Main.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message)); API.QueueAction(() => Notification.Show("~r~Cannot connect to server: " + ex.Message));
} }
IsConnecting = false; 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 -- #region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet) private static void PlayerConnect(Packets.PlayerConnect packet)
{ {
var p = new Player var p = new Player
{ {
ID = packet.PedID, ID = packet.PedID,
Username = packet.Username, Username = packet.Username
}; };
PlayerList.SetPlayer(packet.PedID, packet.Username); PlayerList.SetPlayer(packet.PedID, packet.Username);
Main.Logger.Debug($"player connected:{p.Username}"); Log.Debug($"player connected:{p.Username}");
Main.QueueAction(() => API.QueueAction(() =>
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected.")); Notification.Show($"~h~{p.Username}~h~ connected."));
} }
private static void PlayerDisconnect(Packets.PlayerDisconnect packet) private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
{ {
var player = PlayerList.GetPlayer(packet.PedID); var player = PlayerList.GetPlayer(packet.PedID);
if (player == null) { return; } if (player == null) return;
PlayerList.RemovePlayer(packet.PedID); PlayerList.RemovePlayer(packet.PedID);
Main.QueueAction(() => API.QueueAction(() =>
{ {
EntityPool.RemoveAllFromPlayer(packet.PedID); EntityPool.RemoveAllFromPlayer(packet.PedID);
GTA.UI.Notification.Show($"~h~{player.Username}~h~ left."); Notification.Show($"~h~{player.Username}~h~ left.");
}); });
} }
#endregion // -- PLAYER -- #endregion // -- PLAYER --
#region -- GET -- #region -- GET --
private static bool GetServerPublicKey(string host, int port, int timeout = 10000) private static bool GetServerPublicKey(string host, int port, int timeout = 10000)
{ {
Security.ServerRSA = null; Security.ServerRSA = null;
@ -184,7 +210,8 @@ namespace RageCoop.Client
return _publicKeyReceived.WaitOne(timeout) && Security.ServerRSA != null; 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 received = new AutoResetEvent(false);
var id = NewRequestID(); var id = NewRequestID();
@ -202,20 +229,5 @@ namespace RageCoop.Client
} }
#endregion #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;
}
} }
} }

View File

@ -1,109 +1,75 @@
using GTA; using System;
using System.Threading;
using GTA;
using GTA.UI;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System; using RageCoop.Core.Scripting;
using System.Linq;
using System.Threading;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static partial class Networking 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 readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
private static bool _recycle;
public static void ProcessMessage(NetIncomingMessage message) public static void ProcessMessage(NetIncomingMessage message)
{ {
if (message == null) { return; } if (message == null) return;
_recycle = true; var _recycle = true;
switch (message.MessageType) switch (message.MessageType)
{ {
case NetIncomingMessageType.StatusChanged: case NetIncomingMessageType.StatusChanged:
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte(); var status = (NetConnectionStatus)message.ReadByte();
string reason = message.ReadString(); var reason = message.ReadString();
switch (status) switch (status)
{ {
case NetConnectionStatus.InitiatedConnect: case NetConnectionStatus.InitiatedConnect:
if (message.SenderConnection == ServerConnection) if (message.SenderConnection == ServerConnection) CoopMenu.InitiateConnectionMenuSetting();
{
CoopMenu.InitiateConnectionMenuSetting();
}
break; break;
case NetConnectionStatus.Connected: case NetConnectionStatus.Connected:
if (message.SenderConnection == ServerConnection) if (message.SenderConnection == ServerConnection)
{ {
var response = message.SenderConnection.RemoteHailMessage; var response = message.SenderConnection.RemoteHailMessage;
if ((PacketType)response.ReadByte() != PacketType.HandshakeSuccess) if ((PacketType)response.ReadByte() != PacketType.HandshakeSuccess)
{
throw new Exception("Invalid handshake response!"); throw new Exception("Invalid handshake response!");
}
var p = new Packets.HandshakeSuccess(); var p = new Packets.HandshakeSuccess();
p.Deserialize(response); p.Deserialize(response);
foreach (var player in p.Players) foreach (var player in p.Players) PlayerList.SetPlayer(player.ID, player.Username);
{ Connected();
PlayerList.SetPlayer(player.ID, player.Username);
}
Main.Connected();
} }
else else
{ {
// Self-initiated connection // Self-initiated connection
if (message.SenderConnection.RemoteHailMessage == null) { return; } if (message.SenderConnection.RemoteHailMessage == null) return;
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>(); var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
if (PlayerList.Players.TryGetValue(p.ID, out var player)) if (PlayerList.Players.TryGetValue(p.ID, out var player))
{ {
player.Connection = message.SenderConnection; player.Connection = message.SenderConnection;
Main.Logger.Debug($"Direct connection to {player.Username} established"); Log.Debug($"Direct connection to {player.Username} established");
} }
else 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"); message.SenderConnection.Disconnect("eat poop");
} }
} }
break; break;
case NetConnectionStatus.Disconnected: case NetConnectionStatus.Disconnected:
if (message.SenderConnection == ServerConnection) if (message.SenderConnection == ServerConnection) API.QueueAction(() => CleanUp(reason));
{
Main.Disconnected(reason);
}
break; break;
} }
break; break;
case NetIncomingMessageType.Data: case NetIncomingMessageType.Data:
{ {
if (message.LengthBytes == 0) { break; } if (message.LengthBytes == 0) break;
var packetType = PacketType.Unknown; var packetType = PacketType.Unknown;
try try
{ {
@ -113,17 +79,18 @@ namespace RageCoop.Client
{ {
case PacketType.Response: case PacketType.Response:
{ {
int id = message.ReadInt32(); var id = message.ReadInt32();
if (PendingResponses.TryGetValue(id, out var callback)) if (PendingResponses.TryGetValue(id, out var callback))
{ {
callback((PacketType)message.ReadByte(), message); callback((PacketType)message.ReadByte(), message);
PendingResponses.Remove(id); PendingResponses.Remove(id);
} }
break; break;
} }
case PacketType.Request: case PacketType.Request:
{ {
int id = message.ReadInt32(); var id = message.ReadInt32();
var realType = (PacketType)message.ReadByte(); var realType = (PacketType)message.ReadByte();
if (RequestHandlers.TryGetValue(realType, out var handler)) if (RequestHandlers.TryGetValue(realType, out var handler))
{ {
@ -131,18 +98,19 @@ namespace RageCoop.Client
response.Write((byte)PacketType.Response); response.Write((byte)PacketType.Response);
response.Write(id); response.Write(id);
handler(message).Pack(response); handler(message).Pack(response);
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered, message.SequenceChannel); Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered,
message.SequenceChannel);
Peer.FlushSendQueue(); Peer.FlushSendQueue();
} }
else 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; break;
} }
default: default:
{ {
HandlePacket(packetType, message, message.SenderConnection, ref _recycle); HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
break; break;
} }
@ -150,18 +118,16 @@ namespace RageCoop.Client
} }
catch (Exception ex) catch (Exception ex)
{ {
#if DEBUG API.QueueAction(() =>
Main.QueueAction(() =>
{ {
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}"); Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true; return true;
}); });
Main.Logger.Error($"[{packetType}] {ex.Message}"); Log.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex); Log.Error(ex);
Peer.Shutdown($"Packet Error [{packetType}]"); Peer.Shutdown($"Packet Error [{packetType}]");
#endif
_recycle = false;
} }
break; break;
} }
case NetIncomingMessageType.UnconnectedData: case NetIncomingMessageType.UnconnectedData:
@ -169,7 +135,6 @@ namespace RageCoop.Client
var packetType = (PacketType)message.ReadByte(); var packetType = (PacketType)message.ReadByte();
switch (packetType) switch (packetType)
{ {
case PacketType.HolePunch: case PacketType.HolePunch:
{ {
HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint); HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
@ -177,30 +142,29 @@ namespace RageCoop.Client
} }
case PacketType.PublicKeyResponse: 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>(); var packet = message.GetPacket<Packets.PublicKeyResponse>();
Security.SetServerPublicKey(packet.Modulus, packet.Exponent); Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
_publicKeyReceived.Set(); _publicKeyReceived.Set();
break; break;
} }
} }
break; break;
} }
case NetIncomingMessageType.DebugMessage: case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.ErrorMessage: case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage: case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.VerboseDebugMessage: case NetIncomingMessageType.VerboseDebugMessage:
Main.Logger.Trace(message.ReadString()); Log.Trace(message.ReadString());
break;
default:
break; break;
} }
if (_recycle)
{ if (_recycle) Peer.Recycle(message);
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) switch (packetType)
{ {
@ -236,25 +200,28 @@ namespace RageCoop.Client
case PacketType.ChatMessage: case PacketType.ChatMessage:
{ {
var packet = new Packets.ChatMessage(b => Security.Decrypt(b));
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
packet.Deserialize(msg); packet.Deserialize(msg);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; }); API.QueueAction(() =>
{
MainChat.AddMessage(packet.Username, packet.Message);
return true;
});
} }
break; break;
case PacketType.Voice: case PacketType.Voice:
{ {
if (Main.Settings.Voice) if (Settings.Voice)
{ {
Packets.Voice packet = new Packets.Voice(); var packet = new Packets.Voice();
packet.Deserialize(msg); packet.Deserialize(msg);
SyncedPed player = EntityPool.GetPedByID(packet.ID); var player = EntityPool.GetPedByID(packet.ID);
player.IsSpeaking = true; player.IsSpeaking = true;
player.LastSpeakingTime = Main.Ticked; player.LastSpeakingTime = Ticked;
Voice.AddVoiceData(packet.Buffer, packet.Recorded); Voice.AddVoiceData(packet.Buffer, packet.Recorded);
} }
@ -263,28 +230,28 @@ namespace RageCoop.Client
case PacketType.CustomEvent: case PacketType.CustomEvent:
{ {
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle); var packet = new Packets.CustomEvent();
packet.Deserialize(msg); if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
Scripting.API.Events.InvokeCustomEventReceived(packet); {
} recycle = false;
break; API.QueueAction(() =>
{
case PacketType.CustomEventQueued: packet.Deserialize(msg);
{ API.Events.InvokeCustomEventReceived(packet);
recycle = false; Peer.Recycle(msg);
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle); });
Main.QueueAction(() => }
else
{ {
packet.Deserialize(msg); packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet); API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg); }
});
} }
break; break;
case PacketType.FileTransferChunk: case PacketType.FileTransferChunk:
{ {
Packets.FileTransferChunk packet = new Packets.FileTransferChunk(); var packet = new Packets.FileTransferChunk();
packet.Deserialize(msg); packet.Deserialize(msg);
DownloadManager.Write(packet.ID, packet.FileChunk); DownloadManager.Write(packet.ID, packet.FileChunk);
} }
@ -295,25 +262,24 @@ namespace RageCoop.Client
{ {
recycle = false; recycle = false;
// Dispatch to script thread // Dispatch to script thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); return true; }); API.QueueAction(() =>
{
SyncEvents.HandleEvent(packetType, msg);
return true;
});
} }
break; break;
} }
} }
private static void PedSync(Packets.PedSync packet) private static void PedSync(Packets.PedSync packet)
{ {
SyncedPed c = EntityPool.GetPedByID(packet.ID); var c = EntityPool.GetPedByID(packet.ID);
if (c == null) if (c == null)
{ // Log.Debug($"Creating character for incoming sync:{packet.ID}");
if (EntityPool.PedsByID.Count(x => x.Value.OwnerID == packet.OwnerID) < Main.Settings.WorldPedSoftLimit / PlayerList.Players.Count || EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
EntityPool.VehiclesByID.Any(x => x.Value.Position.DistanceTo(packet.Position) < 2) || packet.ID == packet.OwnerID) var flags = packet.Flags;
{
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
}
else return;
}
c.ID = packet.ID; c.ID = packet.ID;
c.OwnerID = packet.OwnerID; c.OwnerID = packet.OwnerID;
c.Health = packet.Health; c.Health = packet.Health;
@ -323,7 +289,6 @@ namespace RageCoop.Client
c.Flags = packet.Flags; c.Flags = packet.Flags;
c.Heading = packet.Heading; c.Heading = packet.Heading;
c.Position = packet.Position; c.Position = packet.Position;
c.LastSyncedStopWatch.Restart();
if (c.IsRagdoll) if (c.IsRagdoll)
{ {
c.HeadPosition = packet.HeadPosition; c.HeadPosition = packet.HeadPosition;
@ -335,14 +300,14 @@ namespace RageCoop.Client
c.VehicleID = packet.VehicleID; c.VehicleID = packet.VehicleID;
c.Seat = packet.Seat; 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.Speed == 4)
} c.VehicleWeapon = packet.VehicleWeapon;
if (packet.Flags.HasPedFlag(PedDataFlags.IsFullSync)) c.CurrentWeapon = packet.CurrentWeapon;
{
c.CurrentWeaponHash = packet.CurrentWeaponHash;
c.Clothes = packet.Clothes; c.Clothes = packet.Clothes;
c.WeaponComponents = packet.WeaponComponents; c.WeaponComponents = packet.WeaponComponents;
c.WeaponTint = packet.WeaponTint; c.WeaponTint = packet.WeaponTint;
@ -350,71 +315,60 @@ namespace RageCoop.Client
c.BlipColor = packet.BlipColor; c.BlipColor = packet.BlipColor;
c.BlipSprite = packet.BlipSprite; c.BlipSprite = packet.BlipSprite;
c.BlipScale = packet.BlipScale; c.BlipScale = packet.BlipScale;
c.LastFullSynced = Main.Ticked;
} }
c.SetLastSynced(full);
} }
private static void VehicleSync(Packets.VehicleSync packet) private static void VehicleSync(Packets.VehicleSync packet)
{ {
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID); var v = EntityPool.GetVehicleByID(packet.ED.ID);
if (v == null) 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)
{ {
if (EntityPool.VehiclesByID.Count(x => x.Value.OwnerID == packet.OwnerID) < Main.Settings.WorldVehicleSoftLimit / PlayerList.Players.Count || v.VDF = packet.VDF;
EntityPool.PedsByID.Any(x => x.Value.VehicleID == packet.ID || x.Value.Position.DistanceTo(packet.Position) < 2)) v.VDV = packet.VDV;
{
// Main.Logger.Debug($"Creating vehicle for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
}
else return;
}
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.SetLastSynced(full);
} }
private static void ProjectileSync(Packets.ProjectileSync packet) private static void ProjectileSync(Packets.ProjectileSync packet)
{ {
var p = EntityPool.GetProjectileByID(packet.ID); var p = EntityPool.GetProjectileByID(packet.ID);
if (p == null) if (p == null)
{ {
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) { return; } if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) return;
// Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}"); // Log.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID)); EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
} }
p.Flags = packet.Flags; p.Flags = packet.Flags;
p.Position = packet.Position; p.Position = packet.Position;
p.Rotation = packet.Rotation; p.Rotation = packet.Rotation;
p.Velocity = packet.Velocity; p.Velocity = packet.Velocity;
p.WeaponHash = (WeaponHash)packet.WeaponHash; p.WeaponHash = (WeaponHash)packet.WeaponHash;
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle) ? p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle)
(SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID) : EntityPool.GetPedByID(packet.ShooterID); ? (SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID)
p.LastSynced = Main.Ticked; : EntityPool.GetPedByID(packet.ShooterID);
p.LastSyncedStopWatch.Restart(); 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();
} }
} }
} }

View File

@ -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
}
}

View File

@ -5,13 +5,11 @@ namespace RageCoop.Client
{ {
internal static class Statistics internal static class Statistics
{ {
public static int BytesDownPerSecond { get; private set; }
public static int BytesUpPerSecond { get; private set; }
static Statistics() static Statistics()
{ {
Task.Run(() => ThreadManager.CreateThread(() =>
{ {
while (true) while (!IsUnloading)
{ {
var bu = Networking.Peer.Statistics.SentBytes; var bu = Networking.Peer.Statistics.SentBytes;
var bd = Networking.Peer.Statistics.ReceivedBytes; var bd = Networking.Peer.Statistics.ReceivedBytes;
@ -19,7 +17,10 @@ namespace RageCoop.Client
BytesUpPerSecond = Networking.Peer.Statistics.SentBytes - bu; BytesUpPerSecond = Networking.Peer.Statistics.SentBytes - bu;
BytesDownPerSecond = Networking.Peer.Statistics.ReceivedBytes - bd; BytesDownPerSecond = Networking.Peer.Statistics.ReceivedBytes - bd;
} }
}); },"Statistics");
} }
public static int BytesDownPerSecond { get; private set; }
public static int BytesUpPerSecond { get; private set; }
} }
} }

View File

@ -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.Math;
using GTA.Native; using GTA.Native;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -15,33 +18,26 @@ namespace RageCoop.Client
private const float RIGHT_POSITION = 0.9f; private const float RIGHT_POSITION = 0.9f;
private static readonly Scaleform _mainScaleform = new Scaleform("mp_mm_card_freemode"); private static readonly Scaleform _mainScaleform = new Scaleform("mp_mm_card_freemode");
private static ulong _lastUpdate = Util.GetTickCount64(); private static ulong _lastUpdate = Util.GetTickCount64();
public static ulong Pressed { get; set; }
public static bool LeftAlign = true; 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() public static void Tick()
{ {
if (!Networking.IsOnServer) if (!Networking.IsOnServer) return;
{
return;
}
if ((Util.GetTickCount64() - _lastUpdate) >= 1000) if (Util.GetTickCount64() - _lastUpdate >= 1000) Update();
{
Update();
}
if ((Util.GetTickCount64() - Pressed) < 5000 && !Main.MainChat.Focused if (Util.GetTickCount64() - Pressed < 5000 && !MainChat.Focused
#if !NON_INTERACTIVE #if !NON_INTERACTIVE
&& !Menus.CoopMenu.MenuPool.AreAnyVisible && !CoopMenu.MenuPool.AreAnyVisible
#endif #endif
) )
{ Call(DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
Function.Call(Hash.DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle, LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f, 0.28f, 0.6f,
0.28f, 0.6f, 255, 255, 255, 255, 0);
255, 255, 255, 255, 0);
}
} }
private static void Update() private static void Update()
@ -50,20 +46,20 @@ namespace RageCoop.Client
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0); _mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
int i = 0; var i = 0;
foreach (var player in Players.Values) foreach (var player in Players.Values)
{ _mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms",
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms", player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' '); player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' ');
}
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players"); _mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players");
_mainScaleform.CallFunction("DISPLAY_VIEW"); _mainScaleform.CallFunction("DISPLAY_VIEW");
} }
public static void SetPlayer(int id, string username, float latency = 0) public static void SetPlayer(int id, string username, float latency = 0)
{ {
Main.Logger.Debug($"{id},{username},{latency}"); Log.Debug($"{id},{username},{latency}");
if (Players.TryGetValue(id, out Player p)) if (Players.TryGetValue(id, out var p))
{ {
p.Username = username; p.Username = username;
p.ID = id; p.ID = id;
@ -75,6 +71,7 @@ namespace RageCoop.Client
Players.Add(id, p); Players.Add(id, p);
} }
} }
public static void UpdatePlayer(Packets.PlayerInfoUpdate packet) public static void UpdatePlayer(Packets.PlayerInfoUpdate packet)
{ {
var p = GetPlayer(packet.PedID); var p = GetPlayer(packet.PedID);
@ -83,86 +80,94 @@ namespace RageCoop.Client
p._latencyToServer = packet.Latency; p._latencyToServer = packet.Latency;
p.Position = packet.Position; p.Position = packet.Position;
p.IsHost = packet.IsHost; p.IsHost = packet.IsHost;
Main.QueueAction(() => API.QueueAction(() =>
{ {
if (p.FakeBlip?.Exists() != true) if (p.FakeBlip?.Exists() != true) p.FakeBlip = World.CreateBlip(p.Position);
{
p.FakeBlip = World.CreateBlip(p.Position);
}
if (EntityPool.PedExists(p.ID)) if (EntityPool.PedExists(p.ID))
{ {
p.FakeBlip.DisplayType = BlipDisplayType.NoDisplay; p.FakeBlip.DisplayType = BlipDisplayType.NoDisplay;
} }
else else
{ {
p.FakeBlip.Color = Scripting.API.Config.BlipColor; p.FakeBlip.Color = API.Config.BlipColor;
p.FakeBlip.Scale = Scripting.API.Config.BlipScale; p.FakeBlip.Scale = API.Config.BlipScale;
p.FakeBlip.Sprite = Scripting.API.Config.BlipSprite; p.FakeBlip.Sprite = API.Config.BlipSprite;
p.FakeBlip.DisplayType = BlipDisplayType.Default; p.FakeBlip.DisplayType = BlipDisplayType.Default;
p.FakeBlip.Position = p.Position; p.FakeBlip.Position = p.Position;
} }
}); });
} }
} }
public static Player GetPlayer(int id) public static Player GetPlayer(int id)
{ {
Players.TryGetValue(id, out Player p); Players.TryGetValue(id, out var p);
return p; return p;
} }
public static Player GetPlayer(SyncedPed p) public static Player GetPlayer(SyncedPed p)
{ {
var player = GetPlayer(p.ID); var player = GetPlayer(p.ID);
if (player != null) if (player != null) player.Character = p;
{
player.Character = p;
}
return player; return player;
} }
public static void RemovePlayer(int id) public static void RemovePlayer(int id)
{ {
if (Players.TryGetValue(id, out var player)) if (Players.TryGetValue(id, out var player))
{ {
Players.Remove(id); Players.Remove(id);
Main.QueueAction(() => player.FakeBlip?.Delete()); API.QueueAction(() => player.FakeBlip?.Delete());
} }
} }
public static void Cleanup() public static void Cleanup()
{ {
foreach (var p in Players.Values.ToArray()) foreach (var p in Players.Values.ToArray()) p.FakeBlip?.Delete();
{ Players = new Dictionary<int, Player>();
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 byte HolePunchStatus { get; internal set; } = 1;
public bool IsHost { get; internal set; } public bool IsHost { get; internal set; }
public string Username { get; internal set; } public string Username { get; internal set; }
/// <summary> /// <summary>
/// Universal ped ID. /// Universal ped ID.
/// </summary> /// </summary>
public int ID public int ID { get; internal set; }
{
get; internal set; public int EntityHandle => Character?.MainPed?.Handle ?? 0;
}
public IPEndPoint InternalEndPoint { get; internal set; } public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { 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> /// <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> /// </summary>
public float Ping => Main.LocalPlayerID == ID ? Networking.Latency * 2 : (HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2); public float Ping => LocalPlayerID == ID ? Networking.Latency * 2 :
public float PacketTravelTime => HasDirectConnection ? Connection.AverageRoundtripTime / 2 : Networking.Latency + _latencyToServer; HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2;
internal float _latencyToServer = 0;
public float PacketTravelTime => HasDirectConnection
? Connection.AverageRoundtripTime / 2
: Networking.Latency + _latencyToServer;
public bool DisplayNameTag { get; set; } = true; public bool DisplayNameTag { get; set; } = true;
public NetConnection Connection { get; internal set; }
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected; public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
} }
} }

View File

@ -16,7 +16,7 @@ using System.Resources;
// Version information // Version information
[assembly: AssemblyVersion("1.5.4.7")] [assembly: AssemblyVersion("1.6.0.58")]
[assembly: AssemblyFileVersion("1.5.4.7")] [assembly: AssemblyFileVersion("1.6.0.58")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )] [assembly: NeutralResourcesLanguage( "en-US" )]

View File

@ -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 &quot;$(SolutionDir)Client\Data&quot; &quot;$(OutDir)..\Data&quot; /i /s /y" />
</Target>
</Project>

View File

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

View File

@ -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;
}
}
}

View File

@ -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");
}
}
}

View File

@ -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
}
}

View File

@ -1,22 +1,24 @@
using GTA; using System;
using GTA.Math;
using GTA.Native;
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
internal class BaseScript : ClientScript internal static class BaseScript
{ {
private bool _isHost = false; private static bool _isHost;
public override void OnStart()
public static void OnStart()
{ {
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); }; API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); }; API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
API.Events.OnPlayerDied += (s, m) => { API.SendCustomEvent(CustomEvents.OnPlayerDied, m); }; API.Events.OnPlayerDied += () => { API.SendCustomEvent(CustomEvents.OnPlayerDied); };
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn, SetAutoRespawn); API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn, SetAutoRespawn);
API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetDisplayNameTag); API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetDisplayNameTag);
@ -29,50 +31,50 @@ namespace RageCoop.Client.Scripting
API.RegisterCustomEventHandler(CustomEvents.DeleteServerBlip, DeleteServerBlip); API.RegisterCustomEventHandler(CustomEvents.DeleteServerBlip, DeleteServerBlip);
API.RegisterCustomEventHandler(CustomEvents.CreateVehicle, CreateVehicle); API.RegisterCustomEventHandler(CustomEvents.CreateVehicle, CreateVehicle);
API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip); API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip);
API.RegisterCustomEventHandler(CustomEvents.IsHost, (e) => { _isHost = (bool)e.Args[0]; }); API.RegisterCustomEventHandler(CustomEvents.IsHost, e => { _isHost = (bool)e.Args[0]; });
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync); API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied, (e) => { GTA.UI.Notification.Show((string)e.Args[0]); }); API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied,
Task.Run(() => e => { Notification.Show($"~h~{e.Args[0]}~h~ died."); });
{ ThreadManager.CreateThread(() =>
while (true) {
{ while (!IsUnloading)
if (_isHost) {
{ if (Networking.IsOnServer && _isHost)
API.QueueAction(() => API.QueueAction(() =>
{ {
unsafe unsafe
{ {
var time = World.CurrentTimeOfDay; var time = World.CurrentTimeOfDay;
int weather1 = default(int); var weather1 = default(int);
int weather2 = default(int); var weather2 = default(int);
float percent2 = default(float); var percent2 = default(float);
Function.Call(Hash.GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2); Call(GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2);
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes, time.Seconds, weather1, weather2, percent2); API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes,
} time.Seconds, weather1, weather2, percent2);
}); }
} });
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]); World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
Function.Call(Hash.SET_CURR_WEATHER_STATE, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]); 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]); var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; } if (p != null) p.DisplayNameTag = (bool)e.Args[1];
} }
private void UpdatePedBlip(CustomEventReceivedArgs e) private static void UpdatePedBlip(CustomEventReceivedArgs e)
{ {
var p = Entity.FromHandle((int)e.Args[0]); var p = Entity.FromHandle((int)e.Args[0]);
if (p == null) { return; } if (p == null) return;
if (p.Handle == Game.Player.Character.Handle) if (p.Handle == Game.Player.Character.Handle)
{ {
API.Config.BlipColor = (BlipColor)(byte)e.Args[1]; API.Config.BlipColor = (BlipColor)(byte)e.Args[1];
@ -82,34 +84,31 @@ namespace RageCoop.Client.Scripting
else else
{ {
var b = p.AttachedBlip; var b = p.AttachedBlip;
if (b == null) { b = p.AddBlip(); } if (b == null) b = p.AddBlip();
b.Color = (BlipColor)(byte)e.Args[1]; b.Color = (BlipColor)(byte)e.Args[1];
b.Sprite = (BlipSprite)(ushort)e.Args[2]; b.Sprite = (BlipSprite)(ushort)e.Args[2];
b.Scale = (float)e.Args[3]; b.Scale = (float)e.Args[3];
} }
} }
private void CreateVehicle(CustomEventReceivedArgs e) private static void CreateVehicle(CustomEventReceivedArgs e)
{ {
var vehicleModel = (Model)e.Args[1]; var vehicleModel = (Model)e.Args[1];
vehicleModel.Request(1000); vehicleModel.Request(1000);
Vehicle veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]); Vehicle veh;
while (veh == null) while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
{
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
Thread.Sleep(10); Thread.Sleep(10);
}
veh.CanPretendOccupants = false; veh.CanPretendOccupants = false;
var v = new SyncedVehicle() var v = new SyncedVehicle
{ {
ID = (int)e.Args[0], ID = (int)e.Args[0],
MainVehicle = veh, MainVehicle = veh,
OwnerID = Main.LocalPlayerID, OwnerID = LocalPlayerID
}; };
EntityPool.Add(v); EntityPool.Add(v);
} }
private void DeleteServerBlip(CustomEventReceivedArgs e) private static void DeleteServerBlip(CustomEventReceivedArgs e)
{ {
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip)) if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
{ {
@ -118,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 sprite = (BlipSprite)(ushort)obj.Args[1];
var color = (BlipColor)(byte)obj.Args[2]; var color = (BlipColor)(byte)obj.Args[2];
var scale = (float)obj.Args[3]; var scale = (float)obj.Args[3];
var pos = (Vector3)obj.Args[4]; var pos = (Vector3)obj.Args[4];
int rot = (int)obj.Args[5]; var rot = (int)obj.Args[5];
var name = (string)obj.Args[6]; 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)); EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos));
}
blip.Sprite = sprite; blip.Sprite = sprite;
blip.Color = color; blip.Color = color;
blip.Scale = scale; 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(); Entity.FromHandle((int)e.Args[0])?.Delete();
} }
public override void OnStop() private static void SetNameTag(CustomEventReceivedArgs e)
{
}
private void SetNameTag(CustomEventReceivedArgs e)
{ {
var p = PlayerList.GetPlayer((int)e.Args[0]); var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null) if (p != null) p.DisplayNameTag = (bool)e.Args[1];
{
p.DisplayNameTag = (bool)e.Args[1];
}
} }
private void SetAutoRespawn(CustomEventReceivedArgs args)
private static void SetAutoRespawn(CustomEventReceivedArgs args)
{ {
API.Config.EnableAutoRespawn = (bool)args.Args[0]; API.Config.EnableAutoRespawn = (bool)args.Args[0];
} }
private void DeleteServerProp(CustomEventReceivedArgs e)
private static void DeleteServerProp(CustomEventReceivedArgs e)
{ {
var id = (int)e.Args[0]; var id = (int)e.Args[0];
if (EntityPool.ServerProps.TryGetValue(id, out var prop)) if (EntityPool.ServerProps.TryGetValue(id, out var prop))
@ -168,137 +161,134 @@ namespace RageCoop.Client.Scripting
EntityPool.ServerProps.Remove(id); EntityPool.ServerProps.Remove(id);
prop?.MainProp?.Delete(); prop?.MainProp?.Delete();
} }
} }
private void ServerObjectSync(CustomEventReceivedArgs e)
private static void ServerObjectSync(CustomEventReceivedArgs e)
{ {
SyncedProp prop; SyncedProp prop;
var id = (int)e.Args[0]; var id = (int)e.Args[0];
lock (EntityPool.PropsLock) lock (EntityPool.PropsLock)
{ {
if (!EntityPool.ServerProps.TryGetValue(id, out prop)) if (!EntityPool.ServerProps.TryGetValue(id, out prop))
{
EntityPool.ServerProps.Add(id, prop = new SyncedProp(id)); EntityPool.ServerProps.Add(id, prop = new SyncedProp(id));
}
} }
prop.LastSynced = Main.Ticked + 1;
prop.LastSynced = Ticked + 1;
prop.Model = (Model)e.Args[1]; prop.Model = (Model)e.Args[1];
prop.Position = (Vector3)e.Args[2]; prop.Position = (Vector3)e.Args[2];
prop.Rotation = (Vector3)e.Args[3]; prop.Rotation = (Vector3)e.Args[3];
prop.Model.Request(1000);
prop.Update(); prop.Update();
} }
private void NativeCall(CustomEventReceivedArgs e)
private static void NativeCall(CustomEventReceivedArgs e)
{ {
List<InputArgument> arguments = new List<InputArgument>(); var arguments = new List<InputArgument>();
int i; int i;
var ty = (byte)e.Args[0]; var ty = (byte)e.Args[0];
TypeCode returnType = (TypeCode)ty; var returnType = (TypeCode)ty;
i = returnType == TypeCode.Empty ? 1 : 2; i = returnType == TypeCode.Empty ? 1 : 2;
var hash = (Hash)e.Args[i++]; var hash = (Hash)e.Args[i++];
for (; i < e.Args.Length; i++) for (; i < e.Args.Length; i++) arguments.Add(GetInputArgument(e.Args[i]));
{
arguments.Add(GetInputArgument(e.Args[i]));
}
if (returnType == TypeCode.Empty) if (returnType == TypeCode.Empty)
{ {
Function.Call(hash, arguments.ToArray()); Call(hash, arguments.ToArray());
return; return;
} }
var t = returnType;
int id = (int)e.Args[1]; var id = (int)e.Args[1];
switch (returnType) switch (returnType)
{ {
case TypeCode.Boolean: case TypeCode.Boolean:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<bool>(hash, arguments.ToArray()) }); Call<bool>(hash, arguments.ToArray()));
break; break;
case TypeCode.Byte: case TypeCode.Byte:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<byte>(hash, arguments.ToArray()) }); Call<byte>(hash, arguments.ToArray()));
break; break;
case TypeCode.Char: case TypeCode.Char:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<char>(hash, arguments.ToArray()) }); Call<char>(hash, arguments.ToArray()));
break; break;
case TypeCode.Single: case TypeCode.Single:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<float>(hash, arguments.ToArray()) }); Call<float>(hash, arguments.ToArray()));
break; break;
case TypeCode.Double: case TypeCode.Double:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<double>(hash, arguments.ToArray()) }); Call<double>(hash, arguments.ToArray()));
break; break;
case TypeCode.Int16: case TypeCode.Int16:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<short>(hash, arguments.ToArray()) }); Call<short>(hash, arguments.ToArray()));
break; break;
case TypeCode.Int32: case TypeCode.Int32:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id, Call<int>(hash, arguments.ToArray()));
new object[] { id, Function.Call<int>(hash, arguments.ToArray()) });
break; break;
case TypeCode.Int64: case TypeCode.Int64:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<long>(hash, arguments.ToArray()) }); Call<long>(hash, arguments.ToArray()));
break; break;
case TypeCode.String: case TypeCode.String:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<string>(hash, arguments.ToArray()) }); Call<string>(hash, arguments.ToArray()));
break; break;
case TypeCode.UInt16: case TypeCode.UInt16:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<ushort>(hash, arguments.ToArray()) }); Call<ushort>(hash, arguments.ToArray()));
break; break;
case TypeCode.UInt32: case TypeCode.UInt32:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<uint>(hash, arguments.ToArray()) }); Call<uint>(hash, arguments.ToArray()));
break; break;
case TypeCode.UInt64: case TypeCode.UInt64:
API.SendCustomEvent(CustomEvents.NativeResponse, API.SendCustomEvent(CustomEvents.NativeResponse, id,
new object[] { id, Function.Call<ulong>(hash, arguments.ToArray()) }); Call<ulong>(hash, arguments.ToArray()));
break; break;
} }
} }
private InputArgument GetInputArgument(object obj)
private static InputArgument GetInputArgument(object obj)
{ {
// Implicit conversion // Implicit conversion
switch (obj) switch (obj)
{ {
case byte _: case byte stuff:
return (byte)obj; return stuff;
case short _: case short stuff:
return (short)obj; return stuff;
case ushort _: case ushort stuff:
return (ushort)obj; return stuff;
case int _: case int stuff:
return (int)obj; return stuff;
case uint _: case uint stuff:
return (uint)obj; return stuff;
case long _: case long stuff:
return (long)obj; return stuff;
case ulong _: case ulong stuff:
return (ulong)obj; return stuff;
case float _: case float stuff:
return (float)obj; return stuff;
case bool _: case bool stuff:
return (bool)obj; return stuff;
case string _: case string stuff:
return (obj as string); return stuff;
default: default:
return null; return default;
} }
} }
} }
} }

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -1,33 +1,40 @@
using RageCoop.Core; using System.IO;
using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using RageCoop.Core;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal class Security internal class Security
{ {
public RSA ServerRSA { get; set; }
public Aes ClientAes { get; set; } = Aes.Create(); public Security()
private readonly Logger Logger;
public Security(Logger logger)
{ {
Logger = logger;
ClientAes.GenerateKey(); ClientAes.GenerateKey();
ClientAes.GenerateIV(); ClientAes.GenerateIV();
} }
public RSA ServerRSA { get; set; }
public Aes ClientAes { get; set; } = Aes.Create();
public void GetSymmetricKeysCrypted(out byte[] cryptedKey, out byte[] cryptedIV) public void GetSymmetricKeysCrypted(out byte[] cryptedKey, out byte[] cryptedIV)
{ {
// Logger?.Debug($"Aes.Key:{ClientAes.Key.Dump()}, Aes.IV:{ClientAes.IV.Dump()}"); // Logger?.Debug($"Aes.Key:{ClientAes.Key.Dump()}, Aes.IV:{ClientAes.IV.Dump()}");
cryptedKey = ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1); cryptedKey = ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1);
cryptedIV = ServerRSA.Encrypt(ClientAes.IV, RSAEncryptionPadding.Pkcs1); cryptedIV = ServerRSA.Encrypt(ClientAes.IV, RSAEncryptionPadding.Pkcs1);
} }
public byte[] Encrypt(byte[] data) 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) 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) public void SetServerPublicKey(byte[] modulus, byte[] exponent)
{ {
var para = new RSAParameters(); var para = new RSAParameters();
@ -35,6 +42,7 @@ namespace RageCoop.Client
para.Exponent = exponent; para.Exponent = exponent;
ServerRSA = RSA.Create(para); ServerRSA = RSA.Create(para);
} }
public void Regen() public void Regen()
{ {
ClientAes = Aes.Create(); ClientAes = Aes.Create();
@ -42,4 +50,4 @@ namespace RageCoop.Client
ClientAes.GenerateIV(); ClientAes.GenerateIV();
} }
} }
} }

50
Client/Scripts/Shared.cs Normal file
View File

@ -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");
}
}

View File

@ -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;
}
}
}

View File

@ -1,57 +1,64 @@
using GTA; using System.Collections.Generic;
using GTA;
using GTA.Math; using GTA.Math;
using LemonUI.Elements;
using RageCoop.Core; using RageCoop.Core;
using System.Collections.Generic;
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// ? /// ?
/// </summary> /// </summary>
public partial class SyncedPed : SyncedEntity 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 BlipColor BlipColor = (BlipColor)255;
internal BlipSprite BlipSprite = 0;
internal float BlipScale = 1; internal float BlipScale = 1;
internal BlipSprite BlipSprite = 0;
internal PedDataFlags Flags;
internal Blip PedBlip = null;
internal VehicleSeat Seat;
internal int VehicleID internal int VehicleID
{ {
get => CurrentVehicle?.ID ?? 0; get => CurrentVehicle?.ID ?? 0;
set set
{ {
if (CurrentVehicle == null || value != CurrentVehicle?.ID) if (CurrentVehicle == null || value != CurrentVehicle?.ID)
{
CurrentVehicle = EntityPool.GetVehicleByID(value); CurrentVehicle = EntityPool.GetVehicleByID(value);
}
} }
} }
internal SyncedVehicle CurrentVehicle { get; private set; } internal SyncedVehicle CurrentVehicle { get; private set; }
internal VehicleSeat Seat; public bool IsPlayer => OwnerID == ID && ID != 0;
public bool IsPlayer { get => OwnerID == ID && ID != 0; }
public Ped MainPed { get; internal set; } public Ped MainPed { get; internal set; }
internal int Health { get; set; } internal int Health;
internal Vector3 HeadPosition { get; set; } internal Vector3 HeadPosition;
internal Vector3 RightFootPosition { get; set; } internal Vector3 RightFootPosition;
internal Vector3 LeftFootPosition { get; set; } internal Vector3 LeftFootPosition;
internal byte WeaponTint { get; set; } internal byte WeaponTint;
private bool _lastRagdoll = false; internal byte[] Clothes;
private ulong _lastRagdollTime = 0;
private bool _lastInCover = false;
private byte[] _lastClothes = null;
internal byte[] Clothes { get; set; }
internal float Heading { get; set; } internal float Heading;
internal ulong LastSpeakingTime { get; set; } = 0; internal ulong LastSpeakingTime { get; set; } = 0;
internal bool IsSpeaking { get; set; } = false; internal bool IsSpeaking { get; set; } = false;
public byte Speed { get; set; } public byte Speed { get; set; }
private bool _lastIsJumping = false;
internal PedDataFlags Flags;
internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming); internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming);
internal bool _lastDriveBy;
internal bool IsReloading => Flags.HasPedFlag(PedDataFlags.IsReloading); internal bool IsReloading => Flags.HasPedFlag(PedDataFlags.IsReloading);
internal bool IsJumping => Flags.HasPedFlag(PedDataFlags.IsJumping); internal bool IsJumping => Flags.HasPedFlag(PedDataFlags.IsJumping);
internal bool IsRagdoll => Flags.HasPedFlag(PedDataFlags.IsRagdoll); internal bool IsRagdoll => Flags.HasPedFlag(PedDataFlags.IsRagdoll);
@ -65,17 +72,10 @@ namespace RageCoop.Client
internal bool IsInCoverFacingLeft => Flags.HasPedFlag(PedDataFlags.IsInCoverFacingLeft); internal bool IsInCoverFacingLeft => Flags.HasPedFlag(PedDataFlags.IsInCoverFacingLeft);
internal bool IsBlindFiring => Flags.HasPedFlag(PedDataFlags.IsBlindFiring); internal bool IsBlindFiring => Flags.HasPedFlag(PedDataFlags.IsBlindFiring);
internal bool IsInStealthMode => Flags.HasPedFlag(PedDataFlags.IsInStealthMode); internal bool IsInStealthMode => Flags.HasPedFlag(PedDataFlags.IsInStealthMode);
internal Prop ParachuteProp { get; set; } = null; internal Prop ParachuteProp = null;
internal uint CurrentWeaponHash { get; set; } internal WeaponHash CurrentWeapon = WeaponHash.Unarmed;
private Dictionary<uint, bool> _lastWeaponComponents = null; internal VehicleWeaponHash VehicleWeapon = VehicleWeaponHash.Invalid;
internal Dictionary<uint, bool> WeaponComponents { get; set; } = null; internal Dictionary<uint, bool> WeaponComponents = null;
private Entity _weaponObj; internal Vector3 AimCoords;
internal Vector3 AimCoords { get; set; }
private readonly string[] _currentAnimation = new string[2] { "", "" };
private bool LastMoving;
} }
} }

View File

@ -1,23 +1,24 @@
using GTA; using System;
using GTA.Math;
using GTA.Native;
using LemonUI.Elements;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; 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 namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// ? /// ?
/// </summary> /// </summary>
public partial class SyncedPed : SyncedEntity public partial class SyncedPed : SyncedEntity
{ {
/// <summary> /// <summary>
/// Create a local entity (outgoing sync) /// Create a local entity (outgoing sync)
/// </summary> /// </summary>
/// <param name="p"></param> /// <param name="p"></param>
internal SyncedPed(Ped p) internal SyncedPed(Ped p)
@ -26,57 +27,47 @@ namespace RageCoop.Client
p.CanWrithe = false; p.CanWrithe = false;
p.IsOnlyDamagedByPlayer = false; p.IsOnlyDamagedByPlayer = false;
MainPed = p; MainPed = p;
OwnerID = Main.LocalPlayerID; OwnerID = LocalPlayerID;
//Function.Call(Hash.SET_PED_IS_IGNORED_BY_AUTO_OPEN_DOORS, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true); MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
// MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableMelee, true);
} }
/// <summary> /// <summary>
/// Create an empty character with ID /// Create an empty character with ID
/// </summary> /// </summary>
internal SyncedPed(int id) internal SyncedPed(int id)
{ {
ID = id; ID = id;
LastSynced = Main.Ticked; LastSynced = Ticked;
} }
internal override void Update() internal override void Update()
{ {
if (Owner == null) { OwnerID = OwnerID; return; } if (Owner == null)
if (IsPlayer)
{ {
RenderNameTag(); OwnerID = OwnerID;
return;
} }
// Check if all data avalible if (IsPlayer) RenderNameTag();
if (!IsReady) { return; }
// Check if all data available
if (!IsReady) return;
// Skip update if no new sync message has arrived. // Skip update if no new sync message has arrived.
if (!NeedUpdate) { return; } if (!NeedUpdate) return;
if (MainPed == null || !MainPed.Exists()) if (MainPed == null || !MainPed.Exists())
{
if (!CreateCharacter()) if (!CreateCharacter())
{
return; return;
}
}
// Need to update state
if (LastFullSynced >= LastUpdated) if (LastFullSynced >= LastUpdated)
{ {
if (MainPed != null && (Model != MainPed.Model.Hash)) if (MainPed != null && Model != MainPed.Model.Hash)
{
if (!CreateCharacter()) if (!CreateCharacter())
{
return; return;
}
}
if (!Main.Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255; if (!Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255;
if ((byte)BlipColor == 255 && PedBlip != null) if ((byte)BlipColor == 255 && PedBlip != null)
{ {
PedBlip.Delete(); PedBlip.Delete();
@ -91,26 +82,15 @@ namespace RageCoop.Client
PedBlip.Sprite = BlipSprite; PedBlip.Sprite = BlipSprite;
PedBlip.Scale = BlipScale; PedBlip.Scale = BlipScale;
} }
if (PedBlip != null) if (PedBlip != null)
{ {
if (PedBlip.Color != BlipColor) if (PedBlip.Color != BlipColor) PedBlip.Color = BlipColor;
{ if (PedBlip.Sprite != BlipSprite) PedBlip.Sprite = BlipSprite;
PedBlip.Color = BlipColor; if (IsPlayer) PedBlip.Name = Owner.Username;
}
if (PedBlip.Sprite != BlipSprite)
{
PedBlip.Sprite = BlipSprite;
}
if (IsPlayer)
{
PedBlip.Name = Owner.Username;
}
} }
if (!Clothes.SequenceEqual(_lastClothes)) if (!Clothes.SequenceEqual(_lastClothes)) SetClothes();
{
SetClothes();
}
CheckCurrentWeapon(); CheckCurrentWeapon();
} }
@ -120,16 +100,12 @@ namespace RageCoop.Client
if (Health > 0) if (Health > 0)
{ {
if (IsPlayer) if (IsPlayer)
{
MainPed.Resurrect(); MainPed.Resurrect();
}
else else
{
SyncEvents.TriggerPedKilled(this); SyncEvents.TriggerPedKilled(this);
}
} }
} }
else if (IsPlayer && (MainPed.Health != Health)) else if (IsPlayer && MainPed.Health != Health)
{ {
MainPed.Health = Health; MainPed.Health = Health;
@ -141,25 +117,24 @@ namespace RageCoop.Client
} }
} }
if (!IsPlayer && Health <= 0 && !MainPed.IsDead)
{
MainPed.Kill();
return;
}
if (Speed >= 4) if (Speed >= 4)
{ {
DisplayInVehicle(); DisplayInVehicle();
} }
else else
{ {
if (MainPed.IsInVehicle()) { MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut); return; } if (MainPed.IsInVehicle())
{
MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut);
return;
}
DisplayOnFoot(); DisplayOnFoot();
} }
if (IsSpeaking) if (IsSpeaking)
{ {
if (Main.Ticked - LastSpeakingTime < 10) if (Ticked - LastSpeakingTime < 10)
{ {
DisplaySpeaking(true); DisplaySpeaking(true);
} }
@ -172,27 +147,36 @@ namespace RageCoop.Client
} }
} }
LastUpdated = Main.Ticked; LastUpdated = Ticked;
} }
private void RenderNameTag() private void RenderNameTag()
{ {
if (!Owner.DisplayNameTag || !Main.Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible || !MainPed.IsInRange(Main.PlayerPosition, 40f)) if (!Owner.DisplayNameTag || !Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible)
{
return; 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; Point toDraw = default;
if (Util.WorldToScreen(targetPos, ref toDraw)) if (Util.WorldToScreen(headPos, ref toDraw))
{ {
toDraw.Y -= 100; _nameTag ??= new ScaledText(toDraw, Owner.Username, fontSize, Font.ChaletLondon)
new ScaledText(toDraw, Owner.Username, 0.4f, GTA.UI.Font.ChaletLondon)
{ {
Outline = true, Alignment = Alignment.Center,
Alignment = GTA.UI.Alignment.Center, Outline = true
Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White, };
}.Draw(); _nameTag.Position = toDraw;
_nameTag.Scale = fontSize;
_nameTag.Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White;
_nameTag.Draw();
} }
} }
@ -202,7 +186,7 @@ namespace RageCoop.Client
{ {
if (MainPed.Exists()) if (MainPed.Exists())
{ {
// Main.Logger.Debug($"Removing ped {ID}. Reason:CreateCharacter"); // Log.Debug($"Removing ped {ID}. Reason:CreateCharacter");
MainPed.Kill(); MainPed.Kill();
MainPed.MarkAsNoLongerNeeded(); MainPed.MarkAsNoLongerNeeded();
MainPed.Delete(); MainPed.Delete();
@ -211,21 +195,19 @@ namespace RageCoop.Client
MainPed = null; MainPed = null;
} }
if (PedBlip != null) if (PedBlip != null && PedBlip.Exists())
{ {
PedBlip.Delete(); PedBlip.Delete();
PedBlip = null; PedBlip = null;
} }
if (!Model.IsLoaded) if (!Model.IsLoaded)
{ {
Model.Request(); Model.Request();
return false; return false;
} }
if ((MainPed = Util.CreatePed(Model, Position)) == null) if ((MainPed = Util.CreatePed(Model, Position)) == null) return false;
{
return false;
}
Model.MarkAsNoLongerNeeded(); Model.MarkAsNoLongerNeeded();
@ -233,17 +215,17 @@ namespace RageCoop.Client
MainPed.CanWrithe = false; MainPed.CanWrithe = false;
MainPed.CanBeDraggedOutOfVehicle = true; MainPed.CanBeDraggedOutOfVehicle = true;
MainPed.IsOnlyDamagedByPlayer = false; MainPed.IsOnlyDamagedByPlayer = false;
MainPed.RelationshipGroup = Main.SyncedPedsGroup; MainPed.RelationshipGroup = SyncedPedsGroup;
MainPed.IsFireProof = false; MainPed.IsFireProof = false;
MainPed.IsExplosionProof = false; MainPed.IsExplosionProof = false;
Function.Call(Hash.SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false); Call(SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true); Call(SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true); Call(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); Call(SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true); Call(SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
Function.Call(Hash.SET_PED_IS_IGNORED_BY_AUTO_OPEN_DOORS, false); // Call(_SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, 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_DrownsInWater, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true); MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
@ -258,8 +240,8 @@ namespace RageCoop.Client
SetClothes(); SetClothes();
if (IsPlayer) { MainPed.IsInvincible = true; } if (IsPlayer) MainPed.IsInvincible = true;
if (IsInvincible) { MainPed.IsInvincible = true; } if (IsInvincible) MainPed.IsInvincible = true;
lock (EntityPool.PedsLock) lock (EntityPool.PedsLock)
{ {
@ -273,25 +255,27 @@ namespace RageCoop.Client
private void SetClothes() private void SetClothes()
{ {
for (byte i = 0; i < 12; i++) for (byte i = 0; i < 12; i++)
{ Call(SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i],
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i], (int)Clothes[i + 12], (int)Clothes[i + 24]); (int)Clothes[i + 12], (int)Clothes[i + 24]);
}
_lastClothes = Clothes; _lastClothes = Clothes;
} }
private void DisplayOnFoot() private void DisplayOnFoot()
{ {
if (IsInParachuteFreeFall) if (IsInParachuteFreeFall)
{ {
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f); 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; return;
} }
@ -303,33 +287,36 @@ namespace RageCoop.Client
model.Request(1000); model.Request(1000);
if (model != null) 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(); model.MarkAsNoLongerNeeded();
ParachuteProp.IsPositionFrozen = true; ParachuteProp.IsPositionFrozen = true;
ParachuteProp.IsCollisionEnabled = false; 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.ClearAllImmediately();
MainPed.Task.ClearSecondary(); MainPed.Task.ClearSecondary();
} }
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f); MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Quaternion = Rotation.ToQuaternion(); MainPed.Rotation = Rotation;
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person",
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3)) "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; return;
} }
if (ParachuteProp != null) if (ParachuteProp != null)
{ {
if (ParachuteProp.Exists()) if (ParachuteProp.Exists()) ParachuteProp.Delete();
{
ParachuteProp.Delete();
}
ParachuteProp = null; ParachuteProp = null;
} }
@ -337,17 +324,15 @@ namespace RageCoop.Client
{ {
if (Velocity.Z < 0) 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) if (_currentAnimation[1] != anim)
{ {
MainPed.Task.ClearAllImmediately(); MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = anim; _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); MainPed.Task.PlayAnimation("laddersbase", anim, 8f, -1, AnimationFlags.Loop);
}
} }
else else
{ {
@ -359,10 +344,9 @@ namespace RageCoop.Client
_currentAnimation[1] = "base_left_hand_up"; _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); MainPed.Task.PlayAnimation("laddersbase", "base_left_hand_up", 8f, -1, AnimationFlags.Loop);
}
} }
else else
{ {
@ -372,17 +356,16 @@ namespace RageCoop.Client
_currentAnimation[1] = "climb_up"; _currentAnimation[1] = "climb_up";
} }
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up", 3)) if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up",
{ 3)) MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
}
} }
} }
SmoothTransition(); SmoothTransition();
return; return;
} }
else if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
{ {
MainPed.Task.ClearAllImmediately(); MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = ""; _currentAnimation[1] = "";
@ -390,28 +373,18 @@ namespace RageCoop.Client
if (IsVaulting) if (IsVaulting)
{ {
if (!MainPed.IsVaulting) if (!MainPed.IsVaulting) MainPed.Task.Climb();
{
MainPed.Task.Climb();
}
SmoothTransition(); SmoothTransition();
return; return;
} }
if (!IsVaulting && MainPed.IsVaulting)
{ if (!IsVaulting && MainPed.IsVaulting) MainPed.Task.ClearAllImmediately();
MainPed.Task.ClearAllImmediately();
}
if (IsOnFire && !MainPed.IsOnFire) if (IsOnFire && !MainPed.IsOnFire)
{ Call(START_ENTITY_FIRE, MainPed);
Function.Call(Hash.START_ENTITY_FIRE, MainPed); else if (!IsOnFire && MainPed.IsOnFire) Call(STOP_ENTITY_FIRE, MainPed);
}
else if (!IsOnFire && MainPed.IsOnFire)
{
Function.Call(Hash.STOP_ENTITY_FIRE, MainPed);
}
if (IsJumping) if (IsJumping)
{ {
@ -424,42 +397,35 @@ namespace RageCoop.Client
SmoothTransition(); SmoothTransition();
return; return;
} }
_lastIsJumping = false; _lastIsJumping = false;
if (IsRagdoll || (IsPlayer && Health == 0)) if (IsRagdoll || Health == 0)
{ {
if (!MainPed.IsRagdoll) if (!MainPed.IsRagdoll) MainPed.Ragdoll();
{
MainPed.Ragdoll();
}
SmoothTransition(); SmoothTransition();
if (!_lastRagdoll) if (!_lastRagdoll)
{ {
_lastRagdoll = true; _lastRagdoll = true;
_lastRagdollTime = Main.Ticked; _lastRagdollTime = Ticked;
} }
return; return;
} }
if (MainPed.IsRagdoll) if (MainPed.IsRagdoll)
{ {
if (Speed == 0) if (Speed == 0)
{
MainPed.CancelRagdoll(); MainPed.CancelRagdoll();
}
else else
{
MainPed.Task.ClearAllImmediately(); MainPed.Task.ClearAllImmediately();
}
_lastRagdoll = false; _lastRagdoll = false;
return; return;
} }
if (IsReloading) if (IsReloading)
{ {
if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun)) if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun)) MainPed.Task.ReloadWeapon();
{
MainPed.Task.ReloadWeapon();
}
/* /*
if (!_isPlayingAnimation) if (!_isPlayingAnimation)
{ {
@ -476,10 +442,7 @@ namespace RageCoop.Client
} }
else if (IsInCover) else if (IsInCover)
{ {
if (!_lastInCover) if (!_lastInCover) Call(TASK_STAY_IN_COVER, MainPed.Handle);
{
Function.Call(Hash.TASK_STAY_IN_COVER, MainPed.Handle);
}
_lastInCover = true; _lastInCover = true;
if (IsAiming) if (IsAiming)
@ -513,101 +476,110 @@ namespace RageCoop.Client
private void CheckCurrentWeapon() 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(); if (_lastWeaponHash == WeaponHash.Unarmed && WeaponObj?.Exists() == true)
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 (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) Call(GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, WeaponObj.Handle, comp.Key);
{
Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, _weaponObj, comp.Key);
}
} }
} }
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _weaponObj, MainPed.Handle); _lastWeaponComponents = WeaponComponents;
} }
_lastWeaponComponents = WeaponComponents; Call(GIVE_WEAPON_OBJECT_TO_PED, WeaponObj.Handle, MainPed.Handle);
} _lastWeaponHash = CurrentWeapon;
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);
} }
if (Call<int>(GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon) != WeaponTint)
Call<int>(SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon, WeaponTint);
} }
private void DisplayAiming() private void DisplayAiming()
{ {
if (Velocity == default) if (Velocity == default)
{
MainPed.Task.AimAt(AimCoords, 1000); MainPed.Task.AimAt(AimCoords, 1000);
}
else else
{ Call(TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
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,
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);
AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 0x3F000000, 0x40800000, false, 512, false, 0);
}
SmoothTransition(); SmoothTransition();
} }
private void WalkTo() private void WalkTo()
{ {
MainPed.Task.ClearAll(); Call(SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0); var predictPosition = Predict(Position) + Velocity;
Vector3 predictPosition = Predict(Position) + Velocity; var range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
float range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
switch (Speed) switch (Speed)
{ {
case 1: case 1:
if (!MainPed.IsWalking || range > 0.25f) if (!MainPed.IsWalking || range > 0.25f)
{ {
float nrange = range * 2; var nrange = range * 2;
if (nrange > 1.0f) if (nrange > 1.0f) nrange = 1.0f;
{
nrange = 1.0f;
}
MainPed.Task.GoStraightTo(predictPosition); 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; break;
case 2: case 2:
if (!MainPed.IsRunning || range > 0.50f) if (!MainPed.IsRunning || range > 0.50f)
{ {
MainPed.Task.RunTo(predictPosition, true); 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; break;
case 3: case 3:
if (!MainPed.IsSprinting || range > 0.75f) 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); Call(TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X,
Function.Call(Hash.SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f); predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.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; break;
default: default:
if (LastMoving) if (_lastMoving)
{ {
MainPed.Task.StandStill(2000); MainPed.Task.StandStill(2000);
LastMoving = false; _lastMoving = false;
} }
if (MainPed.IsTaskActive(TaskType.CTaskDiveToGround)) MainPed.Task.ClearAll(); if (MainPed.IsTaskActive(TaskType.CTaskDiveToGround)) MainPed.Task.ClearAll();
break; break;
} }
SmoothTransition(); SmoothTransition();
} }
@ -621,26 +593,28 @@ namespace RageCoop.Client
MainPed.PositionNoOffset = predicted; MainPed.PositionNoOffset = predicted;
return; return;
} }
if (!(localRagdoll || MainPed.IsDead)) if (!(localRagdoll || MainPed.IsDead))
{ {
if (!IsAiming && !MainPed.IsGettingUp) if (!IsAiming && !MainPed.IsGettingUp)
{ {
var cur = MainPed.Heading; var cur = MainPed.Heading;
var diff = Heading - cur; var diff = Heading - cur;
if (diff > 180) { diff -= 360; } if (diff > 180)
else if (diff < -180) { diff += 360; } diff -= 360;
else if (diff < -180) diff += 360;
MainPed.Heading = cur + diff / 2; MainPed.Heading = cur + diff / 2;
} }
MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition()); MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
} }
else if (Main.Ticked - _lastRagdollTime < 10) else if (Ticked - _lastRagdollTime < 10)
{ {
return;
} }
else if (IsRagdoll) else if (IsRagdoll)
{ {
var helper = new GTA.NaturalMotion.ApplyImpulseHelper(MainPed); var helper = new ApplyImpulseHelper(MainPed);
var head = MainPed.Bones[Bone.SkelHead]; var head = MainPed.Bones[Bone.SkelHead];
var rightFoot = MainPed.Bones[Bone.SkelRightFoot]; var rightFoot = MainPed.Bones[Bone.SkelRightFoot];
var leftFoot = MainPed.Bones[Bone.SkelLeftFoot]; var leftFoot = MainPed.Bones[Bone.SkelLeftFoot];
@ -648,7 +622,7 @@ namespace RageCoop.Client
// 20:head, 3:left foot, 6:right foot, 17:right hand, // 20:head, 3:left foot, 6:right foot, 17:right hand,
amount = 20 * (Predict(HeadPosition) - head.Position); 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.EqualizeAmount = 1;
helper.PartIndex = 20; helper.PartIndex = 20;
helper.Impulse = amount; helper.Impulse = amount;
@ -656,7 +630,7 @@ namespace RageCoop.Client
helper.Stop(); helper.Stop();
amount = 20 * (Predict(RightFootPosition) - rightFoot.Position); 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.EqualizeAmount = 1;
helper.PartIndex = 6; helper.PartIndex = 6;
helper.Impulse = amount; helper.Impulse = amount;
@ -664,7 +638,7 @@ namespace RageCoop.Client
helper.Stop(); helper.Stop();
amount = 20 * (Predict(LeftFootPosition) - leftFoot.Position); 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.EqualizeAmount = 1;
helper.PartIndex = 3; helper.PartIndex = 3;
helper.Impulse = amount; helper.Impulse = amount;
@ -675,36 +649,37 @@ namespace RageCoop.Client
{ {
// localRagdoll // localRagdoll
var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition()); var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
if (force.Length() > 20) { force = force.Normalized * 20; } if (force.Length() > 20) force = force.Normalized * 20;
MainPed.ApplyForce(force); MainPed.ApplyWorldForceCenterOfMass(force, ForceType.InternalImpulse, true);
} }
} }
private void DisplayInVehicle() private void DisplayInVehicle()
{ {
if (CurrentVehicle?.MainVehicle == null) { return; } if (CurrentVehicle?.MainVehicle == null) return;
switch (Speed) switch (Speed)
{ {
// In vehicle
case 4: 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); MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
}
if (MainPed.IsOnTurretSeat()) 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) 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) if (!_lastDriveBy)
{ {
_lastDriveBy = true; _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)) else if (_lastDriveBy || MainPed.IsTaskActive(TaskType.CTaskAimGunVehicleDriveBy))
@ -712,33 +687,25 @@ namespace RageCoop.Client
MainPed.Task.ClearAll(); MainPed.Task.ClearAll();
_lastDriveBy = false; _lastDriveBy = false;
} }
}
break;
} // Entering vehicle
else if (MainPed.VehicleWeapon != (VehicleWeaponHash)CurrentWeaponHash)
{
MainPed.VehicleWeapon = (VehicleWeaponHash)CurrentWeaponHash;
}
break;
case 5: case 5:
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle || MainPed.GetSeatTryingToEnter() != Seat) if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle ||
{ MainPed.GetSeatTryingToEnter() != Seat)
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5, EnterVehicleFlags.JackAnyone); MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5,
} EnterVehicleFlags.JackAnyone);
break; break;
// Leaving vehicle
case 6: case 6:
if (!MainPed.IsTaskActive(TaskType.CTaskExitVehicle)) if (!MainPed.IsTaskActive(TaskType.CTaskExitVehicle))
{ MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.LengthSquared() > 5 * 5
MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.Length() > 5f ? LeaveVehicleFlags.BailOut : LeaveVehicleFlags.None); ? LeaveVehicleFlags.BailOut
} : LeaveVehicleFlags.None);
break; break;
} }
/*
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, P,true, 0);
return Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, P);
*/
} }
} }
} }

View File

@ -1,116 +1,131 @@
using GTA; using System.Diagnostics;
using GTA;
using GTA.Math; using GTA.Math;
using System.Diagnostics;
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
///
/// </summary> /// </summary>
public abstract class SyncedEntity public abstract class SyncedEntity
{ {
private float _accumulatedOff;
/// <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 int _ownerID; 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>
///
/// </summary> /// </summary>
public int OwnerID public int OwnerID
{ {
get => _ownerID; get => _ownerID;
internal set internal set
{ {
if (value == _ownerID && Owner != null) { return; } if (value == _ownerID && Owner != null) return;
_ownerID = value; _ownerID = value;
Owner = PlayerList.GetPlayer(value); Owner = PlayerList.GetPlayer(value);
if (this is SyncedPed && Owner != null) if (this is SyncedPed && Owner != null) Owner.Character = (SyncedPed)this;
{
Owner.Character = ((SyncedPed)this);
}
} }
} }
internal virtual Player Owner { get; private set; } internal virtual Player Owner { get; private set; }
/// <summary> /// <summary>
///
/// </summary> /// </summary>
public bool IsOutOfSync public bool IsOutOfSync => Ticked - LastSynced > 200 && ID != 0;
{
get => Main.Ticked - LastSynced > 200 && ID != 0; internal bool IsReady => LastSynced > 0 || LastFullSynced == 0;
}
internal bool IsReady
{
get => LastSynced > 0 || LastFullSynced == 0;
}
internal bool IsInvincible { get; set; } = false; 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 bool NeedUpdate => LastSynced >= LastUpdated;
internal Stopwatch LastSentStopWatch { get; set; } = Stopwatch.StartNew();
#endregion
public bool SendNextFrame { get; set; } = false; public bool SendNextFrame { get; set; } = false;
public bool SendFullNextFrame { get; set; } = false; public bool SendFullNextFrame { get; set; } = false;
/// <summary>
///
/// </summary>
protected internal bool _lastFrozen = false;
internal Model Model { get; set; } internal Model Model { get; set; }
internal Vector3 Position { get; set; } internal Vector3 Position { get; set; }
internal Vector3 Rotation { get; set; } internal Vector3 Rotation { get; set; }
internal Quaternion Quaternion { get; set; } internal Quaternion Quaternion { get; set; }
internal Vector3 Velocity { get; set; } internal Vector3 Velocity { get; set; }
public Stopwatch LastSyncedStopWatch = new Stopwatch();
internal abstract void Update(); internal abstract void Update();
internal void PauseUpdate(ulong frames) internal void PauseUpdate(ulong frames)
{ {
LastUpdated = Main.Ticked + frames; LastUpdated = Ticked + frames;
} }
protected Vector3 Predict(Vector3 input) 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) protected bool IsOff(float thisOff, float tolerance = 3, float limit = 30)
{ {
_accumulatedOff += thisOff - tolerance; _accumulatedOff += thisOff - tolerance;
if (_accumulatedOff < 0) { _accumulatedOff = 0; } if (_accumulatedOff < 0)
{
_accumulatedOff = 0;
}
else if (_accumulatedOff >= limit) else if (_accumulatedOff >= limit)
{ {
_accumulatedOff = 0; _accumulatedOff = 0;
return true; return true;
} }
return false; 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
} }
} }

View File

@ -7,57 +7,18 @@ namespace RageCoop.Client
{ {
internal class SyncedProjectile : SyncedEntity internal class SyncedProjectile : SyncedEntity
{ {
public ProjectileDataFlags Flags { private get; set; } = ProjectileDataFlags.None;
public readonly Vector3 Origin; 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) public SyncedProjectile(Projectile p)
{ {
var owner = p.OwnerEntity; var owner = p.OwnerEntity;
if (owner == null) { IsValid = false; return; } if (owner == null)
{
IsValid = false;
return;
}
ID = EntityPool.RequestNewID(); ID = EntityPool.RequestNewID();
MainProjectile = p; MainProjectile = p;
Origin = p.Position; Origin = p.Position;
@ -65,12 +26,13 @@ namespace RageCoop.Client
{ {
if (shooter.MainPed != null if (shooter.MainPed != null
&& (p.AttachedEntity == shooter.MainPed.Weapons.CurrentWeaponObject && (p.AttachedEntity == shooter.MainPed.Weapons.CurrentWeaponObject
|| p.AttachedEntity == shooter.MainPed)) || p.AttachedEntity == shooter.MainPed))
{ {
// Reloading // Reloading
IsValid = false; IsValid = false;
return; return;
} }
Shooter = shooter; Shooter = shooter;
IsLocal = shooter.IsLocal; IsLocal = shooter.IsLocal;
} }
@ -84,37 +46,87 @@ namespace RageCoop.Client
IsValid = false; IsValid = false;
} }
} }
public SyncedProjectile(int id) public SyncedProjectile(int id)
{ {
ID = id; ID = id;
IsLocal = false; 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() internal override void Update()
{ {
// Skip update if no new sync message has arrived. // Skip update if no new sync message has arrived.
if (!NeedUpdate) { return; } if (!NeedUpdate) return;
if (MainProjectile == null || !MainProjectile.Exists()) if (MainProjectile == null || !MainProjectile.Exists())
{ {
CreateProjectile(); CreateProjectile();
return; return;
} }
MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position); MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position);
MainProjectile.Rotation = Rotation; MainProjectile.Rotation = Rotation;
LastUpdated = Main.Ticked; LastUpdated = Ticked;
} }
private void CreateProjectile() private void CreateProjectile()
{ {
Asset = new WeaponAsset(WeaponHash); Asset = new WeaponAsset(WeaponHash);
if (!Asset.IsLoaded) { Asset.Request(); return; } if (!Asset.IsLoaded)
if (Shooter == null) { return; } {
Asset.Request();
return;
}
if (Shooter == null) return;
Entity owner; Entity owner;
owner = (Shooter as SyncedPed)?.MainPed ?? (Entity)(Shooter as SyncedVehicle)?.MainVehicle; 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; 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(); var ps = World.GetAllProjectiles();
MainProjectile = ps[ps.Length - 1]; MainProjectile = ps[ps.Length - 1];
MainProjectile.Position = Position; MainProjectile.Position = Position;
@ -123,4 +135,4 @@ namespace RageCoop.Client
EntityPool.Add(this); EntityPool.Add(this);
} }
} }
} }

View File

@ -3,7 +3,7 @@
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// Synchronized prop, mostly owned by server /// Synchronized prop, mostly owned by server
/// </summary> /// </summary>
public class SyncedProp : SyncedEntity public class SyncedProp : SyncedEntity
{ {
@ -11,31 +11,35 @@ namespace RageCoop.Client
{ {
ID = id; ID = id;
} }
/// <summary> /// <summary>
/// The real entity /// The real entity
/// </summary> /// </summary>
public Prop MainProp { get; set; } public Prop MainProp { get; set; }
internal new int OwnerID
{ internal new int OwnerID =>
get // alwayse owned by server
{ 0;
// alwayse owned by server
return 0;
}
}
internal override void Update() internal override void Update()
{ {
if (!NeedUpdate) return;
if (!Model.IsLoaded)
{
Model.Request();
return;
}
if (!NeedUpdate) { return; }
if (MainProp == null || !MainProp.Exists()) if (MainProp == null || !MainProp.Exists())
{ {
MainProp = World.CreateProp(Model, Position, Rotation, false, false); MainProp = World.CreateProp(Model, Position, Rotation, false, false);
MainProp.IsInvincible = true; MainProp.IsInvincible = true;
} }
MainProp.Position = Position; MainProp.Position = Position;
MainProp.Rotation = Rotation; MainProp.Rotation = Rotation;
MainProp.SetFrozen(true); MainProp.SetFrozen(true);
LastUpdated = Main.Ticked; LastUpdated = Ticked;
} }
} }
} }

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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 GTA.Native;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Scripting; using RageCoop.Client.Scripting;
using System; using SHVDN;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal class EntityPool internal static unsafe class EntityPool
{ {
public static object PedsLock = new object(); public static object PedsLock = new object();
#if BENCHMARK
private static Stopwatch PerfCounter=new Stopwatch();
private static Stopwatch PerfCounter2=Stopwatch.StartNew();
#endif
#region ACTIVE INSTANCES #region ACTIVE INSTANCES
public static Dictionary<int, SyncedPed> PedsByID = new Dictionary<int, SyncedPed>(); public static Dictionary<int, SyncedPed> PedsByID = new Dictionary<int, SyncedPed>();
@ -41,68 +40,70 @@ namespace RageCoop.Client
public static object BlipsLock = new object(); public static object BlipsLock = new object();
#endregion #endregion
public static void Cleanup(bool keepPlayer = true, bool keepMine = true) public static void Cleanup(bool keepPlayer = true, bool keepMine = true)
{ {
foreach (var ped in PedsByID.Values.ToArray()) foreach (var ped in PedsByID.Values.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); RemovePed(ped.ID);
} }
PedsByID.Clear(); PedsByID.Clear();
PedsByHandle.Clear(); PedsByHandle.Clear();
foreach (int id in VehiclesByID.Keys.ToArray()) foreach (var id in VehiclesByID.Keys.ToArray())
{ {
if (keepMine && (VehiclesByID[id].OwnerID == Main.LocalPlayerID)) { continue; } if (keepMine && VehiclesByID[id].OwnerID == LocalPlayerID) continue;
RemoveVehicle(id); RemoveVehicle(id);
} }
VehiclesByID.Clear(); VehiclesByID.Clear();
VehiclesByHandle.Clear(); VehiclesByHandle.Clear();
foreach (var p in ProjectilesByID.Values) foreach (var p in ProjectilesByID.Values.ToArray())
{ if (p.Shooter.ID != LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
if (p.Shooter.ID != Main.LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
{
p.MainProjectile.Delete(); p.MainProjectile.Delete();
}
}
ProjectilesByID.Clear(); ProjectilesByID.Clear();
ProjectilesByHandle.Clear(); ProjectilesByHandle.Clear();
foreach (var p in ServerProps.Values) foreach (var p in ServerProps.Values) p?.MainProp?.Delete();
{
p?.MainProp?.Delete();
}
ServerProps.Clear(); ServerProps.Clear();
foreach (var b in ServerBlips.Values) foreach (var b in ServerBlips.Values)
{
if (b.Exists()) if (b.Exists())
{
b.Delete(); b.Delete();
}
}
ServerBlips.Clear(); ServerBlips.Clear();
} }
#region PEDS #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 SyncedPed GetPedByID(int id)
public static List<int> GetPedIDs() => new List<int>(PedsByID.Keys); => 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() 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@"; // var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
// Function.Call(Hash.SET_PED_MOVEMENT_CLIPSET,p,clipset,1f); // Call(SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
SyncedPed player = GetPedByID(Main.LocalPlayerID); var player = GetPedByID(LocalPlayerID);
if (player == null) if (player == null)
{ {
Main.Logger.Debug($"Creating SyncEntity for player, handle:{p.Handle}"); Log.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
SyncedPed c = new SyncedPed(p); var c = new SyncedPed(p);
Main.LocalPlayerID = c.OwnerID = c.ID; LocalPlayerID = c.OwnerID = c.ID;
Add(c); Add(c);
Main.Logger.Debug($"Local player ID is:{c.ID}"); Log.Debug($"Local player ID is:{c.ID}");
PlayerList.SetPlayer(c.ID, Main.Settings.Username); PlayerList.SetPlayer(c.ID, Settings.Username);
return true; return true;
} }
@ -119,281 +120,253 @@ namespace RageCoop.Client
// Re-add // Re-add
PedsByHandle.Remove(pair.Key); PedsByHandle.Remove(pair.Key);
if (PedsByHandle.ContainsKey(p.Handle)) if (PedsByHandle.ContainsKey(p.Handle)) RemovePed(PedsByHandle[p.Handle].ID);
{
RemovePed(PedsByHandle[p.Handle].ID);
}
PedsByHandle.Add(p.Handle, player); PedsByHandle.Add(p.Handle, player);
} }
} }
return false; return false;
} }
public static void Add(SyncedPed c) public static void Add(SyncedPed c)
{ {
if (PedsByID.ContainsKey(c.ID)) if (PedsByID.ContainsKey(c.ID))
{
PedsByID[c.ID] = c; PedsByID[c.ID] = c;
}
else else
{
PedsByID.Add(c.ID, c); PedsByID.Add(c.ID, c);
} if (c.MainPed == null) return;
if (c.MainPed == null) { return; }
if (PedsByHandle.ContainsKey(c.MainPed.Handle)) if (PedsByHandle.ContainsKey(c.MainPed.Handle))
{
PedsByHandle[c.MainPed.Handle] = c; PedsByHandle[c.MainPed.Handle] = c;
}
else else
{
PedsByHandle.Add(c.MainPed.Handle, c); 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") public static void RemovePed(int id, string reason = "Cleanup")
{ {
if (PedsByID.ContainsKey(id)) if (PedsByID.ContainsKey(id))
{ {
SyncedPed c = PedsByID[id]; var c = PedsByID[id];
var p = c.MainPed; var p = c.MainPed;
if (p != null) if (p != null)
{ {
if (PedsByHandle.ContainsKey(p.Handle)) if (PedsByHandle.ContainsKey(p.Handle)) PedsByHandle.Remove(p.Handle);
{ // Log.Debug($"Removing ped {c.ID}. Reason:{reason}");
PedsByHandle.Remove(p.Handle);
}
// Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}");
p.AttachedBlip?.Delete(); p.AttachedBlip?.Delete();
p.Kill(); p.Kill();
p.Model.MarkAsNoLongerNeeded(); p.Model.MarkAsNoLongerNeeded();
p.MarkAsNoLongerNeeded(); p.MarkAsNoLongerNeeded();
p.Delete(); p.Delete();
} }
c.PedBlip?.Delete(); c.PedBlip?.Delete();
c.ParachuteProp?.Delete(); c.ParachuteProp?.Delete();
PedsByID.Remove(id); PedsByID.Remove(id);
if (c.IsLocal) if (c.IsLocal) API.Events.InvokePedDeleted(c);
{
API.Events.InvokePedDeleted(c);
}
} }
} }
#endregion #endregion
#region VEHICLES #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 SyncedVehicle GetVehicleByID(int id)
public static List<int> GetVehicleIDs() => new List<int>(VehiclesByID.Keys); => 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) public static void Add(SyncedVehicle v)
{ {
if (VehiclesByID.ContainsKey(v.ID)) if (VehiclesByID.ContainsKey(v.ID))
{
VehiclesByID[v.ID] = v; VehiclesByID[v.ID] = v;
}
else else
{
VehiclesByID.Add(v.ID, v); VehiclesByID.Add(v.ID, v);
} if (v.MainVehicle == null) return;
if (v.MainVehicle == null) { return; }
if (VehiclesByHandle.ContainsKey(v.MainVehicle.Handle)) if (VehiclesByHandle.ContainsKey(v.MainVehicle.Handle))
{
VehiclesByHandle[v.MainVehicle.Handle] = v; VehiclesByHandle[v.MainVehicle.Handle] = v;
}
else else
{
VehiclesByHandle.Add(v.MainVehicle.Handle, v); 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") public static void RemoveVehicle(int id, string reason = "Cleanup")
{ {
if (VehiclesByID.ContainsKey(id)) if (VehiclesByID.ContainsKey(id))
{ {
SyncedVehicle v = VehiclesByID[id]; var v = VehiclesByID[id];
var veh = v.MainVehicle; var veh = v.MainVehicle;
if (veh != null) if (veh != null)
{ {
if (VehiclesByHandle.ContainsKey(veh.Handle)) if (VehiclesByHandle.ContainsKey(veh.Handle)) VehiclesByHandle.Remove(veh.Handle);
{ // Log.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
VehiclesByHandle.Remove(veh.Handle);
}
// Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
veh.AttachedBlip?.Delete(); veh.AttachedBlip?.Delete();
veh.Model.MarkAsNoLongerNeeded(); veh.Model.MarkAsNoLongerNeeded();
veh.MarkAsNoLongerNeeded(); veh.MarkAsNoLongerNeeded();
veh.Delete(); veh.Delete();
} }
VehiclesByID.Remove(id); VehiclesByID.Remove(id);
if (v.IsLocal) { API.Events.InvokeVehicleDeleted(v); } if (v.IsLocal) API.Events.InvokeVehicleDeleted(v);
} }
} }
#endregion #endregion
#region PROJECTILES #region PROJECTILES
public static SyncedProjectile GetProjectileByID(int id) public static SyncedProjectile GetProjectileByID(int id)
{ => ProjectilesByID.TryGetValue(id, out var p) ? p : null;
return ProjectilesByID.TryGetValue(id, out var p) ? p : null;
}
public static void Add(SyncedProjectile p) public static void Add(SyncedProjectile p)
{ {
if (!p.IsValid) { return; } if (!p.IsValid) return;
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank) 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)) if (ProjectilesByID.ContainsKey(p.ID))
{
ProjectilesByID[p.ID] = p; ProjectilesByID[p.ID] = p;
}
else else
{
ProjectilesByID.Add(p.ID, p); ProjectilesByID.Add(p.ID, p);
} if (p.MainProjectile == null) return;
if (p.MainProjectile == null) { return; }
if (ProjectilesByHandle.ContainsKey(p.MainProjectile.Handle)) if (ProjectilesByHandle.ContainsKey(p.MainProjectile.Handle))
{
ProjectilesByHandle[p.MainProjectile.Handle] = p; ProjectilesByHandle[p.MainProjectile.Handle] = p;
}
else else
{
ProjectilesByHandle.Add(p.MainProjectile.Handle, p); ProjectilesByHandle.Add(p.MainProjectile.Handle, p);
}
} }
public static void RemoveProjectile(int id, string reason) public static void RemoveProjectile(int id, string reason)
{ {
if (ProjectilesByID.ContainsKey(id)) if (ProjectilesByID.ContainsKey(id))
{ {
SyncedProjectile sp = ProjectilesByID[id]; var sp = ProjectilesByID[id];
var p = sp.MainProjectile; var p = sp.MainProjectile;
if (p != null) if (p != null)
{ {
if (ProjectilesByHandle.ContainsKey(p.Handle)) if (ProjectilesByHandle.ContainsKey(p.Handle)) ProjectilesByHandle.Remove(p.Handle);
{ Log.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
ProjectilesByHandle.Remove(p.Handle); p.Explode();
}
//Main.Logger.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
if (sp.Exploded) p.Explode();
} }
ProjectilesByID.Remove(id); ProjectilesByID.Remove(id);
} }
} }
public static bool PedExists(int id) => PedsByID.ContainsKey(id); public static bool PedExists(int id)
public static bool VehicleExists(int id) => VehiclesByID.ContainsKey(id); {
public static bool ProjectileExists(int id) => ProjectilesByID.ContainsKey(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 #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 vehStateIndex;
private static int pedStateIndex; private static int pedStateIndex;
private static int vehStatesPerFrame; private static int vehStatesPerFrame;
private static int pedStatesPerFrame; private static int pedStatesPerFrame;
private static int i; 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() public static void DoSync()
{ {
UpdateTargets(); 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; var allPeds = NativeMemory.GetPedHandles();
#endif var allVehicles = NativeMemory.GetVehicleHandles();
var allProjectiles = NativeMemory.GetProjectileHandles();
vehStatesPerFrame = allVehicles.Length * 2 / (int)FPS + 1;
pedStatesPerFrame = allPeds.Length * 2 / (int)FPS + 1;
lock (ProjectilesLock) lock (ProjectilesLock)
{ {
foreach (var p in allProjectiles)
if (!ProjectilesByHandle.ContainsKey(p))
Add(new SyncedProjectile(Projectile.FromHandle(p)));
foreach (Projectile p in allProjectiles) foreach (var p in ProjectilesByID.Values.ToArray())
{
if (!ProjectilesByHandle.ContainsKey(p.Handle))
{
Add(new SyncedProjectile(p));
}
}
foreach (SyncedProjectile p in ProjectilesByID.Values.ToArray())
{
// Outgoing sync // Outgoing sync
if (p.IsLocal) if (p.IsLocal)
{ {
if (p.MainProjectile.AttachedEntity == null) if (p.MainProjectile.AttachedEntity == null)
{ {
// Prevent projectiles from exploding next to vehicle // 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)) if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank ||
{ (p.MainProjectile.OwnerEntity?.EntityType == EntityType.Vehicle &&
continue; p.MainProjectile.Position.DistanceTo(p.Origin) < 2)) continue;
}
Networking.SendProjectile(p); Networking.SendProjectile(p);
} }
} }
else // Incoming sync else // Incoming sync
{ {
if (p.Exploded || p.IsOutOfSync) if (p.Exploded || p.IsOutOfSync)
{
RemoveProjectile(p.ID, "OutOfSync | Exploded"); RemoveProjectile(p.ID, "OutOfSync | Exploded");
}
else else
{
p.Update(); p.Update();
}
} }
}
} }
i = -1; i = -1;
lock (PedsLock) lock (PedsLock)
{ {
AddPlayer(); AddPlayer();
var mainCharacters = new List<PedHash> { PedHash.Michael, PedHash.Franklin, PedHash.Franklin02, PedHash.Trevor };
foreach (Ped p in allPeds) foreach (var p in allPeds)
{ {
if (!PedsByHandle.ContainsKey(p.Handle) && p != Game.Player.Character && !mainCharacters.Contains((PedHash)p.Model.Hash)) var c = GetPedByHandle(p);
if (c == null && p != Game.Player.Character.Handle)
{ {
if (PedsByID.Count(x => x.Value.IsLocal) > Main.Settings.WorldPedSoftLimit) var type = Util.GetPopulationType(p);
if (allPeds.Length > Settings.WorldPedSoftLimit
&& type == EntityPopulationType.RandomAmbient
&& !Call<bool>(IS_PED_IN_ANY_VEHICLE, p, 0))
{ {
if (p.PopulationType == EntityPopulationType.RandomAmbient && !p.IsInVehicle()) Util.DeleteEntity(p);
{ continue;
p.Delete();
continue;
}
if (p.PopulationType == EntityPopulationType.RandomScenario) continue;
} }
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
Add(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(); var ps = PedsByID.Values.ToArray();
pedStateIndex += pedStatesPerFrame; pedStateIndex += pedStatesPerFrame;
if (pedStateIndex >= ps.Length) if (pedStateIndex >= ps.Length) pedStateIndex = 0;
{
pedStateIndex = 0;
}
foreach (SyncedPed c in ps) foreach (var c in ps)
{ {
i++; i++;
if ((c.MainPed != null) && (!c.MainPed.Exists())) if (c.MainPed != null && !c.MainPed.Exists())
{ {
RemovePed(c.ID, "non-existent"); RemovePed(c.ID, "non-existent");
continue; continue;
@ -402,110 +375,81 @@ namespace RageCoop.Client
// Outgoing sync // Outgoing sync
if (c.IsLocal) if (c.IsLocal)
{ {
#if BENCHMARK
var start = PerfCounter2.ElapsedTicks;
#endif
// event check // event check
SyncEvents.Check(c); SyncEvents.Check(c);
Networking.SendPed(c, (i - pedStateIndex) < pedStatesPerFrame); Networking.SendPed(c, i - pedStateIndex < pedStatesPerFrame);
#if BENCHMARK
Debug.TimeStamps[TimeStamp.SendPed]=PerfCounter2.ElapsedTicks-start;
#endif
} }
else // Incoming sync else // Incoming sync
{ {
#if BENCHMARK
var start = PerfCounter2.ElapsedTicks;
#endif
c.Update(); c.Update();
if (c.IsOutOfSync) if (c.IsOutOfSync) RemovePed(c.ID, "OutOfSync");
{
RemovePed(c.ID, "OutOfSync");
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.UpdatePed]=PerfCounter2.ElapsedTicks-start;
#endif
} }
} }
#if BENCHMARK
Debug.TimeStamps[TimeStamp.PedTotal]=PerfCounter.ElapsedTicks;
#endif
} }
var check = Main.Ticked % 100 == 0;
var check = Ticked % 100 == 0;
i = -1; i = -1;
lock (VehiclesLock) lock (VehiclesLock)
{ {
foreach (Vehicle veh in allVehicles) foreach (var veh in allVehicles)
{ if (!VehiclesByHandle.ContainsKey(veh))
if (!VehiclesByHandle.ContainsKey(veh.Handle))
{ {
if (VehiclesByID.Count(x => x.Value.IsLocal) > Main.Settings.WorldVehicleSoftLimit) var cveh = (Vehicle)Entity.FromHandle(veh);
if (cveh == null)
continue;
if (allVehicles.Length > Settings.WorldVehicleSoftLimit)
{ {
if (veh.PopulationType == EntityPopulationType.RandomAmbient || veh.PopulationType == 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(); p.Delete();
var c = GetPedByHandle(p.Handle); var c = GetPedByHandle(p.Handle);
if (c != null) if (c != null) RemovePed(c.ID, "ThrottleTraffic");
{
RemovePed(c.ID, "ThrottleTraffic");
}
} }
veh.Delete();
cveh.Delete();
continue; 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(); var vs = VehiclesByID.Values.ToArray();
vehStateIndex += vehStatesPerFrame; vehStateIndex += vehStatesPerFrame;
if (vehStateIndex >= vs.Length) if (vehStateIndex >= vs.Length) vehStateIndex = 0;
{
vehStateIndex = 0;
}
foreach (SyncedVehicle v in vs) foreach (var v in vs)
{ {
i++; i++;
if ((v.MainVehicle != null) && (!v.MainVehicle.Exists())) if (v.MainVehicle != null && !v.MainVehicle.Exists())
{ {
RemoveVehicle(v.ID, "non-existent"); RemoveVehicle(v.ID, "non-existent");
continue; continue;
} }
if (check)
{ if (check) v.SetUpFixedData();
v.SetUpFixedData();
}
// Outgoing sync // Outgoing sync
if (v.IsLocal) if (v.IsLocal)
{ {
if (!v.MainVehicle.IsVisible) { continue; } if (!v.MainVehicle.IsVisible) continue;
SyncEvents.Check(v); SyncEvents.Check(v);
Networking.SendVehicle(v, (i - vehStateIndex) < vehStatesPerFrame); Networking.SendVehicle(v, i - vehStateIndex < vehStatesPerFrame);
} }
else // Incoming sync else // Incoming sync
{ {
v.Update(); v.Update();
if (v.IsOutOfSync) if (v.IsOutOfSync) RemoveVehicle(v.ID, "OutOfSync");
{
RemoveVehicle(v.ID, "OutOfSync");
}
} }
} }
#if BENCHMARK
Debug.TimeStamps[TimeStamp.VehicleTotal]=PerfCounter.ElapsedTicks;
#endif
} }
Networking.Peer.FlushSendQueue(); Networking.Peer.FlushSendQueue();
} }
@ -513,52 +457,38 @@ namespace RageCoop.Client
{ {
Networking.Targets = new List<NetConnection>(PlayerList.Players.Count) { Networking.ServerConnection }; Networking.Targets = new List<NetConnection>(PlayerList.Players.Count) { Networking.ServerConnection };
foreach (var p in PlayerList.Players.Values.ToArray()) foreach (var p in PlayerList.Players.Values.ToArray())
{ if (p.HasDirectConnection && p.Position.DistanceTo(PlayerPosition) < 500)
if (p.HasDirectConnection && p.Position.DistanceTo(Main.PlayerPosition) < 500)
{
Networking.Targets.Add(p.Connection); Networking.Targets.Add(p.Connection);
}
}
} }
public static void RemoveAllFromPlayer(int playerPedId) public static void RemoveAllFromPlayer(int playerPedId)
{ {
foreach (SyncedPed p in PedsByID.Values.ToArray()) foreach (var p in PedsByID.Values.ToArray())
{
if (p.OwnerID == playerPedId) if (p.OwnerID == playerPedId)
{
RemovePed(p.ID); RemovePed(p.ID);
}
}
foreach (SyncedVehicle v in VehiclesByID.Values.ToArray()) foreach (var v in VehiclesByID.Values.ToArray())
{
if (v.OwnerID == playerPedId) if (v.OwnerID == playerPedId)
{
RemoveVehicle(v.ID); RemoveVehicle(v.ID);
}
}
} }
public static int RequestNewID() public static int RequestNewID()
{ {
int ID = 0; var ID = 0;
while ((ID == 0) || PedsByID.ContainsKey(ID) || VehiclesByID.ContainsKey(ID) || ProjectilesByID.ContainsKey(ID)) 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); RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer // Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0); ID = BitConverter.ToInt32(rngBytes, 0);
} }
return ID; 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() public static string DumpDebug()
{ {
return $"\nID_Peds: {PedsByID.Count}" + return $"\nID_Peds: {PedsByID.Count}" +
@ -570,6 +500,7 @@ namespace RageCoop.Client
$"\npedStatesPerFrame: {pedStatesPerFrame}" + $"\npedStatesPerFrame: {pedStatesPerFrame}" +
$"\nvehStatesPerFrame: {vehStatesPerFrame}"; $"\nvehStatesPerFrame: {vehStatesPerFrame}";
} }
public static class ThreadSafe public static class ThreadSafe
{ {
public static void Add(SyncedVehicle v) public static void Add(SyncedVehicle v)
@ -579,6 +510,7 @@ namespace RageCoop.Client
EntityPool.Add(v); EntityPool.Add(v);
} }
} }
public static void Add(SyncedPed p) public static void Add(SyncedPed p)
{ {
lock (PedsLock) lock (PedsLock)
@ -586,6 +518,7 @@ namespace RageCoop.Client
EntityPool.Add(p); EntityPool.Add(p);
} }
} }
public static void Add(SyncedProjectile sp) public static void Add(SyncedProjectile sp)
{ {
lock (ProjectilesLock) lock (ProjectilesLock)
@ -595,4 +528,4 @@ namespace RageCoop.Client
} }
} }
} }
} }

View File

@ -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
}
}

View File

@ -1,17 +1,28 @@
using NAudio.Wave; using System.Threading;
using System.Threading; using NAudio.Wave;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static class Voice internal static class Voice
{ {
private static bool _stopping = false;
private static WaveInEvent _waveIn; 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; private static Thread _thread;
public static bool WasInitialized() => _thread != null; public static bool WasInitialized()
public static bool IsRecording() => _waveIn != null; {
return _thread != null;
}
public static bool IsRecording()
{
return _waveIn != null;
}
public static void ClearAll() public static void ClearAll()
{ {
_waveProvider.ClearBuffer(); _waveProvider.ClearBuffer();
@ -20,7 +31,8 @@ namespace RageCoop.Client
if (_thread != null && _thread.IsAlive) if (_thread != null && _thread.IsAlive)
{ {
_thread.Abort(); _stopping = true;
_thread.Join();
_thread = null; _thread = null;
} }
} }
@ -41,23 +53,17 @@ namespace RageCoop.Client
return; return;
// I tried without thread but the game will lag without // 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()) using (var wo = new WaveOutEvent())
{ {
wo.Init(_waveProvider); wo.Init(_waveProvider);
wo.Play(); wo.Play();
while (wo.PlaybackState == PlaybackState.Playing) while (wo.PlaybackState == PlaybackState.Playing) Thread.Sleep(100);
{
Thread.Sleep(100);
}
} }
} },"Voice");
}));
_thread.Start();
} }
public static void StartRecording() public static void StartRecording()
@ -90,4 +96,4 @@ namespace RageCoop.Client
Networking.SendVoiceMessage(e.Buffer, e.BytesRecorded); Networking.SendVoiceMessage(e.Buffer, e.BytesRecorded);
} }
} }
} }

View File

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

View File

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

View File

@ -1,19 +1,19 @@
 using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using GTA; using GTA;
using GTA.Math; using GTA.Math;
using RageCoop.Core; using RageCoop.Core;
using SHVDN; using NativeMemory = SHVDN.NativeMemory;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal unsafe class MemPatch internal unsafe class MemPatch
{ {
private readonly IntPtr _address;
private readonly byte[] _data; private readonly byte[] _data;
private readonly byte[] _orginal; private readonly byte[] _orginal;
private readonly IntPtr _address;
public MemPatch(byte* address, byte[] data) public MemPatch(byte* address, byte[] data)
{ {
_data = data; _data = data;
@ -21,10 +21,12 @@ namespace RageCoop.Client
_address = (IntPtr)address; _address = (IntPtr)address;
Marshal.Copy((IntPtr)address, _orginal, 0, data.Length); Marshal.Copy((IntPtr)address, _orginal, 0, data.Length);
} }
public void Install() public void Install()
{ {
Marshal.Copy(_data, 0, _address, _data.Length); Marshal.Copy(_data, 0, _address, _data.Length);
} }
public void Uninstall() public void Uninstall()
{ {
Marshal.Copy(_orginal, 0, _address, _orginal.Length); Marshal.Copy(_orginal, 0, _address, _orginal.Length);
@ -36,68 +38,95 @@ namespace RageCoop.Client
public static MemPatch VignettingPatch; public static MemPatch VignettingPatch;
public static MemPatch VignettingCallPatch; public static MemPatch VignettingCallPatch;
public static MemPatch TimeScalePatch; public static MemPatch TimeScalePatch;
static Memory() static Memory()
{ {
// Weapon/radio wheel slow-mo patch // 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"); 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; var address = result + 26;
address = address + *(int*)address + 4u; address = address + *(int*)address + 4u;
VignettingPatch = new MemPatch(address, new byte[] { RET, 0x90, 0x90, 0x90, 0x90 }); VignettingPatch = new MemPatch(address, new byte[] { RET, 0x90, 0x90, 0x90, 0x90 });
VignettingCallPatch = new MemPatch(result + 8, new byte[] { 0x90, 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 }); TimeScalePatch = new MemPatch(result + 34, new byte[] { XOR_32_64, 0xD2 });
} }
public static void ApplyPatches() public static void ApplyPatches()
{ {
VignettingPatch.Install(); VignettingPatch.Install();
VignettingCallPatch.Install(); VignettingCallPatch.Install();
TimeScalePatch.Install(); TimeScalePatch.Install();
} }
public static void RestorePatches() public static void RestorePatches()
{ {
VignettingPatch.Uninstall(); VignettingPatch.Uninstall();
VignettingCallPatch.Uninstall(); VignettingCallPatch.Uninstall();
TimeScalePatch.Uninstall(); TimeScalePatch.Uninstall();
} }
#region PATCHES
#endregion public static Vector3 ReadPosition(this Entity e)
#region OFFSET-CONST {
public const int PositionOffset = 144; return ReadVector3(e.MemoryAddress + PositionOffset);
public const int VelocityOffset = 800; }
public const int MatrixOffset = 96;
#endregion public static Quaternion ReadQuaternion(this Entity e)
#region OPCODE {
private const byte XOR_32_64 = 0x31; return Quaternion.RotationMatrix(e.Matrix);
private const byte RET = 0xC3; }
#endregion
public static Vector3 ReadPosition(this Entity e) => ReadVector3(e.MemoryAddress + PositionOffset); public static Vector3 ReadRotation(this Entity e)
public static Quaternion ReadQuaternion(this Entity e) => Quaternion.RotationMatrix(e.Matrix); {
public static Vector3 ReadRotation(this Entity e) => e.ReadQuaternion().ToEulerDegrees(); return e.ReadQuaternion().ToEulerDegrees();
public static Vector3 ReadVelocity(this Ped e) => ReadVector3(e.MemoryAddress + VelocityOffset); }
public static Vector3 ReadVelocity(this Ped e)
{
return ReadVector3(e.MemoryAddress + VelocityOffset);
}
public static Vector3 ReadVector3(IntPtr address) public static Vector3 ReadVector3(IntPtr address)
{ {
float* ptr = (float*)address.ToPointer(); var ptr = (float*)address.ToPointer();
return new Vector3() return new Vector3
{ {
X = *ptr, X = *ptr,
Y = ptr[1], Y = ptr[1],
Z = ptr[2] Z = ptr[2]
}; };
} }
public static List<int> FindOffset(float toSearch, IntPtr start, int range = 1000, float tolerance = 0.01f) public static List<int> FindOffset(float toSearch, IntPtr start, int range = 1000, float tolerance = 0.01f)
{ {
var foundOffsets = new List<int>(100); 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); var val = NativeMemory.ReadFloat(start + i);
if (Math.Abs(val - toSearch) < tolerance) if (Math.Abs(val - toSearch) < tolerance) foundOffsets.Add(i);
{
foundOffsets.Add(i);
}
} }
return foundOffsets; 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
} }
} }

View File

@ -1,62 +1,52 @@
using GTA.Math; using System;
using GTA.Native;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using GTA.Math;
using GTA.Native;
namespace RageCoop.Client namespace RageCoop.Client
{ {
[StructLayout(LayoutKind.Explicit, Size = 80)] [StructLayout(LayoutKind.Explicit, Size = 80)]
public struct HeadBlendData public struct HeadBlendData
{ {
[FieldOffset(0)] [FieldOffset(0)] public int ShapeFirst;
public int ShapeFirst;
[FieldOffset(8)] [FieldOffset(8)] public int ShapeSecond;
public int ShapeSecond;
[FieldOffset(16)] [FieldOffset(16)] public int ShapeThird;
public int ShapeThird;
[FieldOffset(24)] [FieldOffset(24)] public int SkinFirst;
public int SkinFirst;
[FieldOffset(32)] [FieldOffset(32)] public int SkinSecond;
public int SkinSecond;
[FieldOffset(40)] [FieldOffset(40)] public int SkinThird;
public int SkinThird;
[FieldOffset(48)] [FieldOffset(48)] public float ShapeMix;
public float ShapeMix;
[FieldOffset(56)] [FieldOffset(56)] public float SkinMix;
public float SkinMix;
[FieldOffset(64)] [FieldOffset(64)] public float ThirdMix;
public float ThirdMix;
} }
[StructLayout(LayoutKind.Explicit, Size = 24)] [StructLayout(LayoutKind.Explicit, Size = 24)]
public struct NativeVector3 public struct NativeVector3
{ {
[FieldOffset(0)] [FieldOffset(0)] public float X;
public float X;
[FieldOffset(8)] [FieldOffset(8)] public float Y;
public float Y;
[FieldOffset(16)] [FieldOffset(16)] public float Z;
public float Z;
public static implicit operator Vector3(NativeVector3 vec) 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) 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 public static class NativeCaller
{ {
// These are borrowed from ScriptHookVDotNet's // These are borrowed from ScriptHookVDotNet's
@ -80,8 +70,9 @@ namespace RageCoop.Client
public static unsafe R Invoke<R>(ulong hash) where R : unmanaged public static unsafe R Invoke<R>(ulong hash) where R : unmanaged
{ {
NativeInit(hash); NativeInit(hash);
return *(R*)(NativeCall()); return *(R*)NativeCall();
} }
public static unsafe R Invoke<R>(Hash hash, params object[] args) public static unsafe R Invoke<R>(Hash hash, params object[] args)
where R : unmanaged where R : unmanaged
{ {
@ -90,45 +81,50 @@ namespace RageCoop.Client
foreach (var arg in arguments) foreach (var arg in arguments)
NativePush(arg); NativePush(arg);
return *(R*)(NativeCall()); return *(R*)NativeCall();
} }
/// <summary> /// <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> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns></returns> /// <returns></returns>
private static unsafe ulong[] ConvertPrimitiveArguments(object[] args) private static unsafe ulong[] ConvertPrimitiveArguments(object[] args)
{ {
var result = new ulong[args.Length]; 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) if (args[i] is bool valueBool)
{ {
result[i] = valueBool ? 1ul : 0ul; result[i] = valueBool ? 1ul : 0ul;
continue; continue;
} }
if (args[i] is byte valueByte) if (args[i] is byte valueByte)
{ {
result[i] = valueByte; result[i] = valueByte;
continue; continue;
} }
if (args[i] is int valueInt32) if (args[i] is int valueInt32)
{ {
result[i] = (ulong)valueInt32; result[i] = (ulong)valueInt32;
continue; continue;
} }
if (args[i] is ulong valueUInt64) if (args[i] is ulong valueUInt64)
{ {
result[i] = valueUInt64; result[i] = valueUInt64;
continue; continue;
} }
if (args[i] is float valueFloat) if (args[i] is float valueFloat)
{ {
result[i] = *(ulong*)&valueFloat; result[i] = *(ulong*)&valueFloat;
continue; continue;
} }
if (args[i] is IntPtr valueIntPtr) if (args[i] is IntPtr valueIntPtr)
{ {
result[i] = (ulong)valueIntPtr.ToInt64(); result[i] = (ulong)valueIntPtr.ToInt64();
@ -141,5 +137,4 @@ namespace RageCoop.Client
return result; return result;
} }
} }
}
}

View File

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

View File

@ -460,6 +460,6 @@
_0x1D19C622 = 454, _0x1D19C622 = 454,
_0xB68D3EAB = 455, _0xB68D3EAB = 455,
CPED_CONFIG_FLAG_CanBeIncapacitated = 456, CPED_CONFIG_FLAG_CanBeIncapacitated = 456,
_0x4BD5EBAD = 457, _0x4BD5EBAD = 457
} }
} }

View File

@ -1,15 +1,14 @@
using GTA; using System;
using System.Collections.Generic;
using GTA;
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using RageCoop.Core; using RageCoop.Core;
using System;
using System.Collections.Generic;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static partial class PedExtensions internal static class PedExtensions
{ {
public static bool IsBetween<T>(this T item, T start, T end) 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; 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) public static bool Compare<T, Y>(this Dictionary<T, Y> item, Dictionary<T, Y> item2)
{ {
if (item == null || item2 == null || item.Count != item2.Count) if (item == null || item2 == null || item.Count != item2.Count) return false;
{
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)) if (item2.TryGetValue(pair.Key, out var value) && Equals(value, pair.Value)) continue;
{
continue;
}
// TryGetValue() or Equals failed // TryGetValue() or Equals failed
return false; 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 #region PED
public static byte GetPedSpeed(this Ped ped) public static byte GetPedSpeed(this Ped ped)
{ {
if (ped.IsSittingInVehicle()) return 4;
if (ped.IsSittingInVehicle()) if (ped.IsTaskActive(TaskType.CTaskEnterVehicle)) return 5;
{ if (ped.IsTaskActive(TaskType.CTaskExitVehicle)) return 6;
return 4; if (ped.IsWalking) return 1;
} if (ped.IsRunning) return 2;
if (ped.IsTaskActive(TaskType.CTaskEnterVehicle)) if (ped.IsSprinting) return 3;
{
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; return 0;
@ -80,91 +177,50 @@ namespace RageCoop.Client
var result = new byte[36]; var result = new byte[36];
for (byte i = 0; i < 12; i++) for (byte i = 0; i < 12; i++)
{ {
result[i] = (byte)Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i); result[i] = (byte)Call<short>(GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i + 12] = (byte)Function.Call<short>(Hash.GET_PED_TEXTURE_VARIATION, ped.Handle, i); result[i + 12] = (byte)Call<short>(GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i + 24] = (byte)Function.Call<short>(Hash.GET_PED_PALETTE_VARIATION, ped.Handle, i); result[i + 24] = (byte)Call<short>(GET_PED_PALETTE_VARIATION, ped.Handle, i);
} }
return result; return result;
} }
public static PedDataFlags GetPedFlags(this Ped ped) public static PedDataFlags GetPedFlags(this Ped ped)
{ {
PedDataFlags flags = PedDataFlags.None; var flags = PedDataFlags.None;
if (ped.IsAiming || ped.IsOnTurretSeat()) if (ped.IsAiming || ped.IsOnTurretSeat()) flags |= PedDataFlags.IsAiming;
{
flags |= PedDataFlags.IsAiming;
}
if (ped.IsReloading) if (ped.IsReloading) flags |= PedDataFlags.IsReloading;
{
flags |= PedDataFlags.IsReloading;
}
if (ped.IsJumping) if (ped.IsJumping) flags |= PedDataFlags.IsJumping;
{
flags |= PedDataFlags.IsJumping;
}
// Fake death // Fake death
if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer)) if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer)) flags |= PedDataFlags.IsRagdoll;
{
flags |= PedDataFlags.IsRagdoll;
}
if (ped.IsOnFire) if (ped.IsOnFire) flags |= PedDataFlags.IsOnFire;
{
flags |= PedDataFlags.IsOnFire;
}
if (ped.IsInParachuteFreeFall) if (ped.IsInParachuteFreeFall) flags |= PedDataFlags.IsInParachuteFreeFall;
{
flags |= PedDataFlags.IsInParachuteFreeFall;
}
if (ped.ParachuteState == ParachuteState.Gliding) if (ped.ParachuteState == ParachuteState.Gliding) flags |= PedDataFlags.IsParachuteOpen;
{
flags |= PedDataFlags.IsParachuteOpen;
}
bool climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder); var climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
if (climbingLadder) if (climbingLadder) flags |= PedDataFlags.IsOnLadder;
{
flags |= PedDataFlags.IsOnLadder;
}
if (ped.IsVaulting && !climbingLadder) if (ped.IsVaulting && !climbingLadder) flags |= PedDataFlags.IsVaulting;
{
flags |= PedDataFlags.IsVaulting;
}
if (ped.IsInCover || ped.IsGoingIntoCover) if (ped.IsInCover || ped.IsGoingIntoCover)
{ {
flags |= PedDataFlags.IsInCover; flags |= PedDataFlags.IsInCover;
if (ped.IsInCoverFacingLeft) if (ped.IsInCoverFacingLeft) flags |= PedDataFlags.IsInCover;
{ if (!Call<bool>(IS_PED_IN_HIGH_COVER, ped)) flags |= PedDataFlags.IsInLowCover;
flags |= PedDataFlags.IsInCover; if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire)) flags |= PedDataFlags.IsBlindFiring;
}
if (!Function.Call<bool>(Hash.IS_PED_IN_HIGH_COVER, ped))
{
flags |= PedDataFlags.IsInLowCover;
}
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire))
{
flags |= PedDataFlags.IsBlindFiring;
}
} }
if (ped.IsInvincible) if (ped.IsInvincible) flags |= PedDataFlags.IsInvincible;
{
flags |= PedDataFlags.IsInvincible;
}
if (Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped)) if (Call<bool>(GET_PED_STEALTH_MOVEMENT, ped)) flags |= PedDataFlags.IsInStealthMode;
{
flags |= PedDataFlags.IsInStealthMode;
}
return flags; return flags;
@ -253,259 +309,110 @@ namespace RageCoop.Client
case WeaponHash.MG: case WeaponHash.MG:
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" }; return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
default: 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; 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) public static VehicleSeat GetNearestSeat(this Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
{ {
float num = 99f; var num = 99f;
int result = -2; var result = -2;
Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (var text in _vehicleDoors.Keys)
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)
{ {
bool flag = veh.Bones[text].Position != Vector3.Zero; var flag = veh.Bones[text].Position != Vector3.Zero;
if (flag) if (flag)
{ {
float num2 = ped.Position.DistanceTo(Function.Call<Vector3>(Hash.GET_WORLD_POSITION_OF_ENTITY_BONE, new InputArgument[] var num2 = ped.Position.DistanceTo(Call<Vector3>(GET_WORLD_POSITION_OF_ENTITY_BONE, veh, veh.Bones[text].Index));
{ var flag2 = num2 < distanceToignoreDoors && num2 < num &&
veh, IsSeatUsableByPed(ped, veh, _vehicleDoors[text]);
veh.Bones[text].Index
}));
bool flag2 = (num2 < distanceToignoreDoors) && (num2 < num) && IsSeatUsableByPed(ped, veh, dictionary[text]);
if (flag2) if (flag2)
{ {
num = num2; num = num2;
result = dictionary[text]; result = _vehicleDoors[text];
} }
} }
} }
return (VehicleSeat)result; return (VehicleSeat)result;
} }
public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat) public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat)
{ {
VehicleSeat seat = (VehicleSeat)_seat; var seat = (VehicleSeat)_seat;
bool result = false; var result = false;
bool flag = veh.IsSeatFree(seat); var flag = veh.IsSeatFree(seat);
if (flag) if (flag)
{ {
result = true; result = true;
} }
else if (veh.GetPedOnSeat(seat) != null) else
{ {
var isDead = veh.GetPedOnSeat(seat).IsDead;
bool isDead = veh.GetPedOnSeat(seat).IsDead;
if (isDead) if (isDead)
{ {
result = true; result = true;
} }
else else
{ {
int num = Function.Call<int>(Hash.GET_RELATIONSHIP_BETWEEN_PEDS, new InputArgument[] var num = Call<int>(GET_RELATIONSHIP_BETWEEN_PEDS, ped, veh.GetPedOnSeat(seat));
{ var flag2 = num > 2;
ped, if (flag2) result = true;
veh.GetPedOnSeat(seat)
});
bool flag2 = num > 2;
if (flag2)
{
result = true;
}
} }
} }
return result; return result;
} }
public static bool IsTaskActive(this Ped p, TaskType task) 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) public static Vector3 GetAimCoord(this Ped p)
{ {
var weapon = p.Weapons.CurrentWeaponObject; Prop weapon;
var v = p.CurrentVehicle; EntityBone b;
// Rhino if (p.IsOnTurretSeat())
if (v != null && v.Model.Hash == 782665360)
{ {
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 // Not very accurate, but doesn't matter
Vector3 dir = weapon.RightVector; var dir = weapon.RightVector;
return weapon.Position + dir * 20; return weapon.Position + dir * 20;
} }
return GetLookingCoord(p); return GetLookingCoord(p);
} }
public static Vector3 GetLookingCoord(this Ped 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); return RaycastEverything(default);
}
EntityBone b = p.Bones[Bone.FacialForehead]; EntityBone b = p.Bones[Bone.FacialForehead];
Vector3 v = b.UpVector.Normalized; var v = b.UpVector.Normalized;
return b.Position + 200 * v; return b.Position + 200 * v;
} }
public static VehicleSeat GetSeatTryingToEnter(this Ped p) 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 #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);
}
} }
} }

View File

@ -1,6 +1,7 @@
// WARNING: values can change after a game update // WARNING: values can change after a game update
// if R* adds in the middle! // if R* adds in the middle!
// This is up-to-date for b2372 // This is up-to-date for b2372
internal enum TaskType internal enum TaskType
{ {
CTaskHandsUp = 0, CTaskHandsUp = 0,
@ -435,4 +436,4 @@ internal enum TaskType
CTaskVehiclePullAlongside = 528, CTaskVehiclePullAlongside = 528,
CTaskVehicleTransformToSubmarine = 529, CTaskVehicleTransformToSubmarine = 529,
CTaskAnimatedFallback = 530 CTaskAnimatedFallback = 530
}; }

275
Client/Scripts/Util/Util.cs Normal file
View File

@ -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
}
}

View File

@ -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
}
}

View File

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

View File

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

20
Client/Scripts/app.config Normal file
View File

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

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