118 Commits

Author SHA1 Message Date
4e6bab1b53 blah 2022-10-15 18:14:35 +08:00
64692bc161 Update 2022-10-15 17:16:47 +08:00
8d27c072ca Change package format to iso9660 2022-10-15 17:06:19 +08:00
d7c0abdfc2 Bump version 2022-10-15 15:54:46 +08:00
d0b6bbaa3a Update installer 2022-10-15 15:30:58 +08:00
411b199a98 Unload fix and small tweaks 2022-10-15 13:52:49 +08:00
b48b15b652 Tidy up 2022-10-15 12:15:19 +08:00
e83480b7f9 Update action 2022-10-15 11:57:26 +08:00
d1b4f23992 Restructure solution 2022-10-15 11:51:18 +08:00
42c0ef2159 Load in client in seperate domain
Rewrote logger for more flexible options
Complete client resource loading
2022-10-15 11:09:17 +08:00
8701ac703e blah 2022-10-10 16:45:58 +08:00
fe53e01a4a ClientScript loading logic 2022-10-09 23:35:30 +08:00
617dbc9812 Change settings and data directory 2022-10-09 22:07:52 +08:00
1606d25fed Queue action in WorldThread 2022-10-09 11:15:09 +08:00
5585876005 API remoting 2022-10-08 23:49:48 +08:00
54c7f4ce92 ResourceDomain 2022-10-08 12:43:24 +08:00
a062322dda Fix queued events 2022-10-05 18:10:56 +08:00
4599c558e4 Add back Hash method for compatabilit 2022-10-05 16:09:43 +08:00
71f7f4b257 Use implicit operator for CustomEventHash, add CustomEventFlags
Resource rebuild will be required, but no code change is needed
2022-10-05 15:53:57 +08:00
3f3b5fd2d0 Closes #40 2022-09-30 23:40:14 +08:00
9173e9a99e Fix non-ambient peds get deleted 2022-09-30 23:27:37 +08:00
6e64c458df Merge branch 'dev-nightly' of https://github.com/RAGECOOP/RAGECOOP-V into dev-nightly 2022-09-09 11:24:02 +08:00
76f959abe9 Stuff 2022-09-09 11:23:46 +08:00
f2e85d66ab Update build-test.yaml 2022-09-09 11:15:27 +08:00
e30ef1f4bd Update actions 2022-09-08 20:09:52 -07:00
6e8f6e78f6 Add build test action 2022-09-08 20:05:26 -07:00
f28c83ccbd Merge pull request #38 from RAGECOOP/dev-nightly
Bump to 1.5.4
2022-09-07 22:44:48 -07:00
a83821b3d2 Version bump 2022-09-08 13:19:09 -07:00
3b5436064e Don't announce if already present in master server 2022-09-08 13:15:34 -07:00
c4b321324e Filter out white space for reload key parsing 2022-09-08 12:59:14 -07:00
ba8d525ddf Merge pull request #37 from RAGECOOP/dev-nightly
Rewrite packet system
2022-09-07 21:45:14 -07:00
884e2f39f0 Yet another code cleanup 2022-09-08 12:41:56 -07:00
76c529f1d1 Rewrote packet system to reduce heap allocation 2022-09-08 12:37:06 -07:00
df0064bd38 Update nightly-build.yaml 2022-09-07 21:01:22 -07:00
f1fc96bbd7 Fix weird projectile shooting 2022-09-07 11:08:25 +08:00
23e9326f5f Fix stuff, add listening address display and projectile shoot prediction 2022-09-07 09:52:40 +08:00
4621fb4987 Some code cleanup and formatting 2022-09-06 21:46:35 +08:00
6c82895fa7 code cleaned up 2022-09-05 13:02:09 +02:00
84b040766f Simplify GetBytesFromObject 2022-08-27 14:23:28 +08:00
f44558cd3b Add some client API 2022-08-27 14:17:10 +08:00
accdbbcbc6 blah 2022-08-27 13:53:03 +08:00
2d4107f35e Fix traffic 2022-08-27 12:40:47 +08:00
bac53fd769 Load scripts from main assembly only 2022-08-26 22:58:03 +08:00
8f63fee5b5 Delete temp directory 2022-08-25 22:34:54 +08:00
8d0ad0b600 Show runtime information 2022-08-25 22:32:01 +08:00
dc08c0c1f6 Added support for loading runtime-specific libraries 2022-08-25 21:51:09 +08:00
faaa856aa5 Instructional buttons for popup 2022-08-25 00:58:21 +08:00
eab64f9254 Check fixed data every 100 frames 2022-08-24 22:38:50 +08:00
6c936cb8f9 Show help in popup 2022-08-24 19:26:40 +08:00
83a37f8556 blah 2022-08-24 18:48:21 +08:00
bb4eacce26 Don't disable traffic if not on server 2022-08-24 18:47:59 +08:00
d5b71db5d4 💩 2022-08-23 23:21:35 +08:00
89ebd0305c Fix ped running not displayed 2022-08-23 18:10:52 +08:00
b209202292 Add option to show entity owner 2022-08-23 17:43:24 +08:00
f199241ed8 Time for a release 2022-08-23 16:29:43 +08:00
50229116a7 blah 2022-08-23 14:04:18 +08:00
f192caeae1 Add sdk 2022-08-23 14:03:33 +08:00
6005b2ba11 Add slipstreaming 2022-08-23 13:51:38 +08:00
6e213611cc Fix deluxo wing 2022-08-23 13:45:45 +08:00
347d65af62 Expose chat meesage API to client script 2022-08-23 13:34:12 +08:00
636ee3a33f Fix memory stream close on send 2022-08-23 13:22:14 +08:00
8ff08e0804 Add resource package support 2022-08-23 12:21:17 +08:00
6d7fe58719 Ped sync tweaks 2022-08-23 10:55:54 +08:00
c7e14ef191 Fix disable traffic not always work 2022-08-22 21:59:01 +08:00
39eb5ef80a Hmm... 2022-08-22 19:38:04 +08:00
d25c2539e0 Tweaks 2022-08-22 19:27:25 +08:00
eb31a33104 Fix vibration on stopped vehicle 2022-08-22 19:25:03 +08:00
8b3f8ffd9b Check ped weapon on full sync only 2022-08-22 19:13:52 +08:00
cca09c4178 Fixed ped weapon display after exiting vehicle 2022-08-22 19:08:09 +08:00
871bb7e64e ver 2022-08-22 17:57:43 +08:00
02da3ce8af Fix voice on P2P mode 2022-08-22 17:55:58 +08:00
711ba62fc2 Update installer 2022-08-22 17:30:15 +08:00
8f7d3135db Revert "Fix output path overlap"
This reverts commit 2af9482da9.
2022-08-22 17:27:35 +08:00
cb563a1bf8 Revert "Update nightly-build.yaml"
This reverts commit 6720636d48.
2022-08-22 17:27:29 +08:00
a84ab42a42 blah 2022-08-22 17:01:08 +08:00
b3e285f92f Wait ten minutes before updating 2022-08-22 16:59:00 +08:00
01433f0de6 Restore damage model for aircraft 2022-08-22 11:36:52 +08:00
c034cd8aa9 Fix traffic 2022-08-22 10:01:09 +08:00
363c2ccb00 Don't disable vehicle enter/exit key 2022-08-22 09:40:02 +08:00
7380c162be Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-22 09:34:52 +08:00
f1c7192029 Change kicking options 2022-08-22 09:33:54 +08:00
6720636d48 Update nightly-build.yaml 2022-08-22 00:46:46 +08:00
2af9482da9 Fix output path overlap 2022-08-22 00:36:43 +08:00
bf22c17bba Add AntiAsshole and host display 2022-08-22 00:23:46 +08:00
01492a0c55 Auto server update should work now 2022-08-21 19:56:59 +08:00
c17b234057 Update AssemblyInfo.cs 2022-08-21 19:41:25 +08:00
739577bbf1 Hmm... 2022-08-21 19:30:27 +08:00
2d2da624e4 Add automatic server update (need testing) 2022-08-21 19:13:12 +08:00
9e66762061 Add ZeroTier check to installer 2022-08-21 15:13:36 +08:00
84d578366a Fix installer 2022-08-21 14:28:37 +08:00
03bc3a7742 blah 2022-08-21 14:22:41 +08:00
5bfc7f836e Fix vehicle seat sync 2022-08-21 14:15:01 +08:00
54977c79f1 Fix unintended carjacking 2022-08-21 14:12:44 +08:00
0352bfa328 Add menyoo and directory check for installer 2022-08-21 13:42:28 +08:00
6fbc386b45 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-21 12:37:47 +08:00
203e3b2528 Add Client.EntitiesCount 2022-08-21 12:37:37 +08:00
c9aa995224 Update README.md 2022-08-21 12:17:20 +08:00
c201b94897 Better vehicle rotation display 2022-08-20 22:36:48 +08:00
87f873d1a5 blah 2022-08-20 22:22:06 +08:00
6c355b109d Fix player blip (again) 2022-08-20 22:15:01 +08:00
2ef3012672 Fix installer not overwriting files 2022-08-20 17:27:05 +08:00
7229574ff4 Hmm 2022-08-20 17:17:49 +08:00
eff9fd50b1 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-20 15:31:03 +08:00
e2cbf26360 Fix installer output 2022-08-20 15:30:54 +08:00
b0f448fd5f Fix insyaller output 2022-08-20 15:29:44 +08:00
721c4d3dee Add client installer 2022-08-20 15:20:25 +08:00
87dfc18d91 Smoother ped heading calibration 2022-08-20 12:33:55 +08:00
fa4fe2c10f Fix player blip 2022-08-20 11:32:35 +08:00
3a088ddbfc Fix blip duplication and name issue 2022-08-20 11:08:51 +08:00
52cc9b647c Vehicle duplication prevention 2022-08-19 22:31:13 +08:00
1aa23901d5 Update lidgren again 2022-08-19 22:10:01 +08:00
52584d5774 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-19 21:43:34 +08:00
62bf15dede default LogLevel to 0 2022-08-19 21:43:23 +08:00
722ca16f57 Update README.md 2022-08-19 21:39:40 +08:00
3e61498366 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-19 21:35:08 +08:00
1ff2fa5691 Update lidgren 2022-08-19 21:35:05 +08:00
5088973474 Update README.md 2022-08-19 21:29:24 +08:00
b63378f4d0 Update README.md 2022-08-19 21:28:44 +08:00
168 changed files with 48121 additions and 6219 deletions

48
.github/workflows/build-test.yaml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Build test
on:
push:
branches:
- '*' # matches every branch that doesn't contain a '/'
- '*/*' # matches every branch containing a single '/'
- '**' # matches every branch
- '!main' # excludes main
- '!dev-nightly' # excludes nightly
pull_request:
branches: [ "main", "dev-nightly" ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: ['6.0.x']
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Restore dependencies
run: dotnet restore
- name: Restore nuget packages
run: nuget restore
- name: Build client and installer
run: dotnet build Client/Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client
- name: Build server win-x64
run: dotnet build Server/RageCoop.Server.csproj -o bin/Release/Server
- name: Upload server
uses: actions/upload-artifact@v3
with:
name: RageCoop.Server
path: bin/Release/Server
- name: Upload Client
uses: actions/upload-artifact@v3
with:
name: RageCoop.Client
path: bin/Release/Client
- uses: actions/checkout@v2

View File

@ -2,9 +2,7 @@ name: Nightly-build
on: on:
push: push:
branches: [ "main" ] branches: [ "dev-nightly" ]
pull_request:
branches: [ "main" ]
jobs: jobs:
build: build:
@ -24,15 +22,14 @@ jobs:
run: dotnet restore run: dotnet restore
- name: Restore nuget packages - name: Restore nuget packages
run: nuget restore run: nuget restore
- name: Build client - name: Build client and installer
run: dotnet build RageCoop.Client/RageCoop.Client.csproj --configuration Release -o bin/Release/Client/RageCoop run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
- name: Build server win-x64 - name: Build server win-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release
- name: Build server linux-x64 - name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release
- name: Build server linux-arm - name: Build server linux-arm
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o bin/Release/Server/linux-arm -c Release
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: bin/Release/Client files: bin/Release/Client
@ -40,17 +37,17 @@ jobs:
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: RageCoop.Server/bin/win-x64 files: bin/Release/Server/win-x64
dest: RageCoop.Server-win-x64.zip dest: RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: RageCoop.Server/bin/linux-x64 files: bin/Release/Server/linux-x64
dest: RageCoop.Server-linux-x64.zip dest: RageCoop.Server-linux-x64.zip
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
with: with:
files: RageCoop.Server/bin/linux-arm files: bin/Release/Server/linux-arm
dest: RageCoop.Server-linux-arm.zip dest: RageCoop.Server-linux-arm.zip
- uses: WebFreak001/deploy-nightly@v1.1.0 - uses: WebFreak001/deploy-nightly@v1.1.0

View File

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

View File

@ -0,0 +1,11 @@
using System.Windows;
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

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

View File

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

View File

@ -0,0 +1,192 @@
using RageCoop.Core;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using Path = System.IO.Path;
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Choose();
}
private void Choose()
{
var od = new OpenFileDialog()
{
Filter = "GTA 5 executable |GTA5.exe;PlayGTAV.exe",
Title = "Select you GTAV executable"
};
if (od.ShowDialog() ?? false == true)
{
Task.Run(() =>
{
try
{
Install(Directory.GetParent(od.FileName).FullName);
}
catch (Exception ex)
{
MessageBox.Show("Installation failed: " + ex.ToString());
Environment.Exit(1);
}
});
}
else
{
Environment.Exit(0);
}
}
private void Install(string root)
{
UpdateStatus("Checking requirements");
var shvPath = Path.Combine(root, "ScriptHookV.dll");
var scriptsPath = Path.Combine(root, "Scripts");
var installPath = Path.Combine(root, "RageCoop", "Scripts");
var legacyPath = Path.Combine(scriptsPath, "RageCoop");
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName.StartsWith(installPath))
{
throw new InvalidOperationException("The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
}
if (!File.Exists(shvPath))
{
MessageBox.Show("Please install ScriptHookV first!");
Environment.Exit(1);
}
Directory.CreateDirectory(installPath);
File.Copy("ScriptHookVDotNet.dll", Path.Combine(root, "ScriptHookVDotNet.asi"), true);
File.Copy("ScriptHookVDotNet3.dll", Path.Combine(root, "ScriptHookVDotNet3.dll"), true);
UpdateStatus("Removing old versions");
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
{
File.Delete(f);
}
// 1.5 installation check
if (Directory.Exists(legacyPath))
{
Directory.Delete(legacyPath, true);
}
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories))
{
File.Delete(f);
}
if (File.Exists("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll") && File.Exists("RageCoop.Client.Loader.dll"))
{
UpdateStatus("Installing...");
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath));
File.Copy("RageCoop.Client.Loader.dll", Path.Combine(scriptsPath, "RageCoop.Client.Loader.dll"), true);
Finish();
}
else
{
throw new Exception("Required files are missing, please re-download the installer from official website");
}
void Finish()
{
checkKeys:
UpdateStatus("Checking conflicts");
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
var settingsPath = Path.Combine(root, Util.SettingsPath);
Settings settings = null;
try
{
settings = Util.ReadSettings(settingsPath);
}
catch
{
settings = new Settings();
}
if (File.Exists(menyooConfig))
{
var lines = File.ReadAllLines(menyooConfig).Where(x => !x.StartsWith(";") && x.EndsWith(" = " + (int)settings.MenuKey));
if (lines.Any())
{
if (MessageBox.Show("Following menyoo config value will conflict with RAGECOOP menu key\n" +
string.Join("\n", lines)
+ "\nDo you wish to change the Menu Key?", "Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var ae = new AutoResetEvent(false);
UpdateStatus("Press the key you wish to change to");
Dispatcher.BeginInvoke(new Action(() =>
KeyDown += (s, e) =>
{
settings.MenuKey = (Keys)KeyInterop.VirtualKeyFromKey(e.Key);
ae.Set();
}));
ae.WaitOne();
if (!Util.SaveSettings(settingsPath, settings))
{
MessageBox.Show("Error occurred when saving settings");
Environment.Exit(1);
}
MessageBox.Show("Menu key changed to " + settings.MenuKey);
goto checkKeys;
}
}
}
UpdateStatus("Checking ZeroTier");
try
{
ZeroTierHelper.Check();
}
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)
{
var url = "https://download.zerotier.com/dist/ZeroTier%20One.msi";
UpdateStatus("Downloading ZeroTier from " + url);
try
{
HttpHelper.DownloadFile(url, "ZeroTier.msi", (p) => UpdateStatus("Downloading ZeroTier " + p + "%"));
UpdateStatus("Installing ZeroTier");
Process.Start("ZeroTier.msi").WaitForExit();
}
catch
{
MessageBox.Show("Failed to download ZeroTier, please download it from officail website");
Process.Start(url);
}
}
}
UpdateStatus("Completed!");
MessageBox.Show("Installation sucessful!");
Environment.Exit(0);
}
}
private void UpdateStatus(string status)
{
Dispatcher.BeginInvoke(new Action(() => Status.Content = status));
}
}
}

View File

@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="bg.png" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\..\bin\Debug\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>DEBUG</DefineConstants>
<WarningLevel>4</WarningLevel>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\..\bin\Release\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
<ProjectReference Include="..\Loader\RageCoop.Client.Loader.csproj" />
<ProjectReference Include="..\Scripts\RageCoop.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="bg.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
</Project>

View File

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

View File

@ -0,0 +1,72 @@
<Project>
<PropertyGroup>
<AssemblyName>RageCoop.Client.Installer</AssemblyName>
<IntermediateOutputPath>obj\Debug\</IntermediateOutputPath>
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
<MSBuildProjectExtensionsPath>M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\</MSBuildProjectExtensionsPath>
<_TargetAssemblyProjectName>RageCoop.Client.Installer</_TargetAssemblyProjectName>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="bg.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RageCoop.Client\RageCoop.Client.csproj" />
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
</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>
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\sharpziplib\1.3.3\lib\net45\ICSharpCode.SharpZipLib.dll" />
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\microsoft.extensions.objectpool\6.0.8\lib\net461\Microsoft.Extensions.ObjectPool.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\mscorlib.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationCore.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationFramework.dll" />
<ReferencePath Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Client\RageCoop.Client.dll" />
<ReferencePath Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Core\RageCoop.Core.dll" />
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\system.buffers\4.5.1\ref\net45\System.Buffers.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Core.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Data.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Drawing.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.IO.Compression.FileSystem.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Numerics.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Runtime.Serialization.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Controls.Ribbon.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Forms.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xaml.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.Linq.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClient.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClientsideProviders.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationProvider.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationTypes.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsBase.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsFormsIntegration.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\Facades\netstandard.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\Debug\net48\MainWindow.g.cs" />
<Compile Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\Debug\net48\App.g.cs" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

63
Client/Installer/Resource.Designer.cs generated Normal file
View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace RageCoop.Client.Installer {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RageCoop.Client.Installer.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</root>

BIN
Client/Installer/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -0,0 +1,205 @@
using SHVDN;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using Console = GTA.Console;
namespace RageCoop.Client.Loader
{
public class LoaderContext : MarshalByRefObject, IDisposable
{
#region PRIMARY-LOADING-LOGIC
public static ConcurrentDictionary<string, LoaderContext> LoadedDomains => new ConcurrentDictionary<string, LoaderContext>(_loadedDomains);
private static readonly ConcurrentDictionary<string, LoaderContext> _loadedDomains = new ConcurrentDictionary<string, LoaderContext>();
public bool UnloadRequested;
public string BaseDirectory => AppDomain.CurrentDomain.BaseDirectory;
private ScriptDomain CurrentDomain => ScriptDomain.CurrentDomain;
public static void CheckForUnloadRequest()
{
lock (_loadedDomains)
{
foreach (var p in _loadedDomains.Values)
{
if (p.UnloadRequested)
{
Unload(p);
}
}
}
}
public static bool IsLoaded(string dir)
{
return _loadedDomains.ContainsKey(Path.GetFullPath(dir).ToLower());
}
public static LoaderContext Load(string dir)
{
lock (_loadedDomains)
{
dir = Path.GetFullPath(dir).ToLower();
if (IsLoaded(dir))
{
throw new Exception("Already loaded");
}
ScriptDomain newDomain = null;
try
{
dir = Path.GetFullPath(dir).ToLower();
Directory.CreateDirectory(dir);
Exception e = null;
// Load domain in main thread
Main.QueueToMainThread(() =>
{
try
{
/*
var assemblies = new List<string>();
assemblies.Add(typeof(DomainLoader).Assembly.Location);
assemblies.AddRange(typeof(DomainLoader).Assembly.GetReferencedAssemblies()
.Select(x => Assembly.Load(x.FullName).Location)
.Where(x => !string.IsNullOrEmpty(x)));
*/
// Delete API assemblies
Directory.GetFiles(dir, "ScriptHookVDotNet*", SearchOption.AllDirectories).ToList().ForEach(x => File.Delete(x));
var ctxAsm = Path.Combine(dir, "RageCoop.Client.Loader.dll");
if (File.Exists(ctxAsm)) { File.Delete(ctxAsm); }
newDomain = ScriptDomain.Load(
Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName, dir);
newDomain.AppDomain.SetData("Primary", ScriptDomain.CurrentDomain);
newDomain.AppDomain.SetData("Console", ScriptDomain.CurrentDomain.AppDomain.GetData("Console"));
var context = (LoaderContext)newDomain.AppDomain.CreateInstanceFromAndUnwrap(
typeof(LoaderContext).Assembly.Location,
typeof(LoaderContext).FullName, ignoreCase: false,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { }
, null, null);
newDomain.AppDomain.SetData("RageCoop.Client.LoaderContext", context);
newDomain.Start();
_loadedDomains.TryAdd(dir, context);
}
catch (Exception ex)
{
e = ex;
}
});
// Wait till next tick
GTA.Script.Yield();
if (e != null) { throw e; }
return _loadedDomains[dir];
}
catch (Exception ex)
{
GTA.UI.Notification.Show(ex.ToString());
Console.Error(ex);
if (newDomain != null)
{
ScriptDomain.Unload(newDomain);
}
throw;
}
}
}
public static void Unload(LoaderContext domain)
{
lock (_loadedDomains)
{
Exception ex = null;
var name = domain.CurrentDomain.Name;
Console.Info("Unloading domain: " + name);
Main.QueueToMainThread(() =>
{
try
{
if (!_loadedDomains.TryRemove(domain.BaseDirectory.ToLower(), out _))
{
throw new Exception("Failed to remove domain from list");
}
domain.Dispose();
ScriptDomain.Unload(domain.CurrentDomain);
}
catch (Exception e)
{
ex = e;
GTA.UI.Notification.Show(ex.ToString());
}
});
GTA.Script.Yield();
if (ex != null)
{
throw ex;
}
Console.Info("Unloaded domain: " + name);
}
}
public static void Unload(string dir)
{
Unload(_loadedDomains[Path.GetFullPath(dir).ToLower()]);
}
public static void UnloadAll()
{
lock (_loadedDomains)
{
foreach (var d in _loadedDomains.Values.ToArray())
{
Unload(d);
}
}
}
#endregion
#region LOAD-CONTEXT
private LoaderContext()
{
AppDomain.CurrentDomain.DomainUnload += (s, e) => Dispose();
PrimaryDomain.Tick += Tick;
PrimaryDomain.KeyEvent += KeyEvent;
Console.Info($"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
}
public static ScriptDomain PrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain;
public static LoaderContext CurrentContext => AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext") as LoaderContext;
/// <summary>
/// Request the current domain to be unloaded
/// </summary>
public static void RequestUnload()
{
if (PrimaryDomain == null)
{
throw new NotSupportedException("Current domain not loaded by the loader therfore cannot be unloaded automatically");
}
CurrentContext.UnloadRequested = true;
}
private void Tick()
{
CurrentDomain.DoTick();
}
private void KeyEvent(Keys keys, bool status)
{
CurrentDomain.DoKeyEvent(keys, status);
}
public void Dispose()
{
lock (this)
{
if (PrimaryDomain == null) { return; }
PrimaryDomain.Tick -= Tick;
PrimaryDomain.KeyEvent -= KeyEvent;
AppDomain.CurrentDomain.SetData("Primary", null);
}
}
#endregion
}
}

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

@ -0,0 +1,69 @@
using GTA;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using Console = GTA.Console;
namespace RageCoop.Client.Loader
{
public class Main : GTA.Script
{
private static readonly string GameDir = Directory.GetParent(typeof(SHVDN.ScriptDomain).Assembly.Location).FullName;
private static readonly string ScriptsLocation = Path.Combine(GameDir, "RageCoop", "Scripts");
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
private static int MainThreadID;
public Main()
{
if (LoaderContext.PrimaryDomain != null) { throw new InvalidOperationException("Improperly placed loader assembly, please re-install to fix this issue"); }
Tick += OnTick;
SHVDN.ScriptDomain.CurrentDomain.Tick += DomainTick;
Aborted += (s, e) => LoaderContext.UnloadAll();
}
private void OnTick(object sender, EventArgs e)
{
while (Game.IsLoading)
{
GTA.Script.Yield();
}
LoaderContext.CheckForUnloadRequest();
if (!LoaderContext.IsLoaded(ScriptsLocation))
{
if (!File.Exists(Path.Combine(ScriptsLocation, "RageCoop.Client.dll")))
{
GTA.UI.Notification.Show("~r~Main assembly is missing, please re-install the client");
Abort();
}
LoaderContext.Load(ScriptsLocation);
}
}
internal static void QueueToMainThread(Action task)
{
if (Thread.CurrentThread.ManagedThreadId != MainThreadID)
{
TaskQueue.Enqueue(task);
}
else
{
task();
}
}
private static void DomainTick()
{
if (MainThreadID == default) { MainThreadID = Thread.CurrentThread.ManagedThreadId; }
while (TaskQueue.TryDequeue(out var task))
{
try
{
task.Invoke();
}
catch (Exception ex)
{
Console.Error(ex.ToString());
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -2,25 +2,28 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using SHVDN;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Drawing; using System.Drawing;
using System.Linq; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using System.Threading.Tasks; using Console = GTA.Console;
using System.Threading; using Script = GTA.Script;
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class Main : Script internal class Main : Script
{ {
private bool _gameLoaded = false; private static bool _gameLoaded = false;
internal static Version Version = typeof(Main).Assembly.GetName().Version; internal static Version Version = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0; internal static int LocalPlayerID = 0;
@ -28,51 +31,79 @@ namespace RageCoop.Client
internal static RelationshipGroup SyncedPedsGroup; internal static RelationshipGroup SyncedPedsGroup;
internal static new Settings Settings = null; internal static new Settings Settings = null;
internal static Scripting.BaseScript BaseScript = new Scripting.BaseScript();
#if !NON_INTERACTIVE #if !NON_INTERACTIVE
#endif #endif
internal static Chat MainChat = null; internal static Chat MainChat = null;
internal static Stopwatch Counter = new Stopwatch(); internal static Stopwatch Counter = new Stopwatch();
internal static Logger Logger = null; internal static Logger Logger = null;
internal static ulong Ticked = 0; internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition; internal static Vector3 PlayerPosition;
internal static Scripting.Resources Resources = null; internal static Resources Resources = null;
private static List<Func<bool>> QueuedActions = new List<Func<bool>>(); private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
public static Worker Worker; public static string LogPath => $"{Settings.DataDirectory}\\RageCoop.Client.log";
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
public Main() public Main()
{ {
#if DEBUG_HIGH_PING Util.StartUpCheck();
Networking.SimulatedLatency=0.3f; Console.Info($"Starting {typeof(Main).FullName}, domain: {AppDomain.CurrentDomain.Id} {AppDomain.CurrentDomain.FriendlyName}");
#endif
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
try try
{ {
Settings = Util.ReadSettings(); Settings = Util.ReadSettings();
if (Settings.DataDirectory.StartsWith("Scripts"))
{
var defaultDir = new Settings().DataDirectory;
Console.Warning("Data directory must be outside scripts folder, migrating to default direcoty: " + defaultDir);
if (Directory.Exists(Settings.DataDirectory))
{
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Settings.DataDirectory), new DirectoryInfo(defaultDir));
Directory.Delete(Settings.DataDirectory, true);
}
Settings.DataDirectory = defaultDir;
Util.SaveSettings();
}
} }
catch catch
{ {
GTA.UI.Notification.Show("Malformed configuration, overwriting with default values..."); // GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
Settings = new Settings(); Settings = new Settings();
Util.SaveSettings(); Util.SaveSettings();
} }
Directory.CreateDirectory(Settings.DataDirectory); Directory.CreateDirectory(Settings.DataDirectory);
Logger = new Logger() Logger = new Logger()
{ {
LogPath=$"{Settings.DataDirectory}\\RageCoop.Client.log", Writers = new List<StreamWriter> { CoreUtils.OpenWriter(LogPath) },
UseConsole=false,
#if DEBUG #if DEBUG
LogLevel = 0, LogLevel = 0,
#else #else
LogLevel = Settings.LogLevel, LogLevel = Settings.LogLevel,
#endif #endif
}; };
Resources = new Scripting.Resources(); Logger.OnFlush += (line, formatted) =>
{
switch (line.LogLevel)
{
#if DEBUG
// case LogLevel.Trace:
case LogLevel.Debug:
Console.Info(line.Message);
break;
#endif
case LogLevel.Info:
Console.Info(line.Message);
break;
case LogLevel.Warning:
Console.Warning(line.Message);
break;
case LogLevel.Error:
Console.Error(line.Message);
break;
}
};
ScriptDomain.CurrentDomain.Tick += DomainTick;
Resources = new Resources();
if (Game.Version < GameVersion.v1_0_1290_1_Steam) if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{ {
Tick += (object sender, EventArgs e) => Tick += (object sender, EventArgs e) =>
@ -95,41 +126,78 @@ namespace RageCoop.Client
#if !NON_INTERACTIVE #if !NON_INTERACTIVE
#endif #endif
MainChat = new Chat(); MainChat = new Chat();
Aborted += OnAborted;
Tick += OnTick; Tick += OnTick;
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
KeyDown += OnKeyDown; KeyDown += OnKeyDown;
KeyDown+=(s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
KeyUp+=(s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
Aborted += (object sender, EventArgs e) => CleanUp();
Util.NativeMemory(); Util.NativeMemory();
Counter.Restart(); Counter.Restart();
}
private static void OnAborted(object sender, EventArgs e)
{
try
{
WorldThread.Instance?.Abort();
DevTool.Instance?.Abort();
ScriptDomain.CurrentDomain.Tick -= DomainTick;
Disconnected("Abort");
WorldThread.DoQueuedActions();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
private static void DomainTick()
{
while (TaskQueue.TryDequeue(out var task))
{
try
{
task.Invoke();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
if (Networking.IsOnServer)
{
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
}
/// <summary>
/// Queue an action to main thread and wait for execution to complete, must be called from script thread.
/// </summary>
/// <param name="task"></param>
internal static void QueueToMainThreadAndWait(Action task)
{
Exception e = null;
TaskQueue.Enqueue(() => { try { task(); } catch (Exception ex) { e = ex; } });
Yield();
if (e != null) { throw e; }
} }
public static Ped P; public static Ped P;
public static float FPS; public static float FPS;
private bool _lastDead; private static bool _lastDead;
private void OnTick(object sender, EventArgs e) private static void OnTick(object sender, EventArgs e)
{ {
/*
unsafe
{
var stationName = Function.Call<string>(Hash.GET_RADIO_STATION_NAME, Game.RadioStation);
//_GET_CURRENT_RADIO_TRACK_NAME
var currentTrack = Function.Call<int>((Hash)0x34D66BC058019CE0, stationName);
Function.Call(Hash.SET_RADIO_TRACK, "RADIO_03_HIPHOP_NEW", "ARM1_RADIO_STARTS");
return currentTrack;
var h1 = Function.Call<int>(Hash._GET_CURRENT_RADIO_STATION_HASH);
return $"{h1},{h2},{s},{s1}";
}
*/
P = Game.Player.Character; P = Game.Player.Character;
PlayerPosition = P.ReadPosition(); PlayerPosition = P.ReadPosition();
FPS = Game.FPS; FPS = Game.FPS;
// World.DrawMarker(MarkerType.DebugSphere, PedExtensions.RaycastEverything(default), default, default, new Vector3(0.2f, 0.2f, 0.2f), Color.AliceBlue);
if (Game.IsLoading) if (Game.IsLoading)
{ {
return; return;
@ -146,7 +214,6 @@ namespace RageCoop.Client
#endif #endif
DoQueuedActions();
if (!Networking.IsOnServer) if (!Networking.IsOnServer)
{ {
return; return;
@ -155,14 +222,6 @@ namespace RageCoop.Client
{ {
Game.TimeScale = 1; Game.TimeScale = 1;
} }
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
if (Networking.ShowNetworkInfo) if (Networking.ShowNetworkInfo)
{ {
@ -173,7 +232,7 @@ namespace RageCoop.Client
MainChat.Tick(); MainChat.Tick();
PlayerList.Tick(); PlayerList.Tick();
if (!Scripting.API.Config.EnableAutoRespawn) if (!API.Config.EnableAutoRespawn)
{ {
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true); Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
Function.Call(Hash.IGNORE_NEXT_RESTART, true); Function.Call(Hash.IGNORE_NEXT_RESTART, true);
@ -187,8 +246,8 @@ namespace RageCoop.Client
{ {
P.Health = 1; P.Health = 1;
Game.Player.WantedLevel = 0; Game.Player.WantedLevel = 0;
Main.Logger.Debug("Player died."); Logger.Debug("Player died.");
Scripting.API.Events.InvokePlayerDied(); API.Events.InvokePlayerDied();
} }
GTA.UI.Screen.StopEffects(); GTA.UI.Screen.StopEffects();
} }
@ -199,13 +258,13 @@ namespace RageCoop.Client
} }
else if (P.IsDead && !_lastDead) else if (P.IsDead && !_lastDead)
{ {
Scripting.API.Events.InvokePlayerDied(); API.Events.InvokePlayerDied();
} }
_lastDead = P.IsDead; _lastDead = P.IsDead;
Ticked++; Ticked++;
} }
private void OnKeyDown(object sender, KeyEventArgs e) private static void OnKeyDown(object sender, KeyEventArgs e)
{ {
if (MainChat.Focused) if (MainChat.Focused)
{ {
@ -273,25 +332,6 @@ namespace RageCoop.Client
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp; PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp;
} }
} }
else if (Game.IsControlJustPressed(GTA.Control.VehicleExit))
{
if (P.IsInVehicle())
{
P.Task.LeaveVehicle();
}
else if (P.IsTaskActive(TaskType.CTaskMoveGoToVehicleDoor))
{
P.Task.ClearAll();
}
else
{
var v = World.GetClosestVehicle(P.Position, 10);
if (v!=null)
{
P.Task.EnterVehicle(v, VehicleSeat.Driver, -1, 5, EnterVehicleFlags.AllowJacking);
}
}
}
else if (e.KeyCode == Settings.PassengerKey) else if (e.KeyCode == Settings.PassengerKey)
{ {
var P = Game.Player.Character; var P = Game.Player.Character;
@ -309,76 +349,69 @@ namespace RageCoop.Client
if (V != null) if (V != null)
{ {
var seat = P.GetNearestSeat(V); var seat = P.GetNearestSeat(V);
P.Task.EnterVehicle(V, seat); var p = V.GetPedOnSeat(seat);
} if (p != null && !p.IsDead)
}
}
}
}
public static void CleanUp()
{ {
MainChat.Clear(); for (int i = -1; i < V.PassengerCapacity; i++)
Voice.ClearAll();
EntityPool.Cleanup();
PlayerList.Cleanup();
LocalPlayerID=default;
WorldThread.Traffic(true);
}
private static void DoQueuedActions()
{ {
lock (QueuedActions) seat = (VehicleSeat)i;
p = V.GetPedOnSeat(seat);
if (p == null || p.IsDead)
{ {
foreach (var action in QueuedActions.ToArray()) break;
{
try
{
if (action())
{
QueuedActions.Remove(action);
} }
} }
catch (Exception ex) }
{ P.Task.EnterVehicle(V, seat, -1, 5, EnterVehicleFlags.None);
Logger.Error(ex);
QueuedActions.Remove(action);
} }
} }
} }
} }
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
internal static void QueueAction(Func<bool> a)
{
lock (QueuedActions)
{
QueuedActions.Add(a);
} }
internal static void Connected()
{
Memory.ApplyPatches();
if (Settings.Voice && !Voice.WasInitialized())
{
Voice.Init();
} }
internal static void QueueAction(Action a) API.QueueAction(() =>
{ {
lock (QueuedActions) WorldThread.Traffic(!Settings.DisableTraffic);
{ Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
QueuedActions.Add(() => { a(); return true; }); CoopMenu.ConnectedMenuSetting();
} MainChat.Init();
} GTA.UI.Notification.Show("~g~Connected!");
/// <summary>
/// Clears all queued actions
/// </summary>
internal static void ClearQueuedActions()
{
lock (QueuedActions) { QueuedActions.Clear(); }
}
public static void Delay(Action a, int time)
{
Task.Run(() =>
{
Thread.Sleep(time);
QueueAction(a);
}); });
}
Logger.Info(">> Connected <<");
}
public static void Disconnected(string reason)
{
Logger.Info($">> Disconnected << reason: {reason}");
API.QueueAction(() =>
{
if (MainChat.Focused)
{
MainChat.Focused = false;
}
PlayerList.Cleanup();
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
GTA.UI.Notification.Show("~r~Disconnected: " + reason);
LocalPlayerID = default;
Resources.Unload();
});
Memory.RestorePatches();
DownloadManager.Cleanup();
Voice.ClearAll();
}
} }
} }

View File

@ -1,4 +1,5 @@
using GTA; using GTA;
using GTA.Native;
using LemonUI; using LemonUI;
using LemonUI.Menus; using LemonUI.Menus;
using LemonUI.Scaleform; using LemonUI.Scaleform;
@ -69,7 +70,6 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(DebugMenu.Menu); Menu.AddSubMenu(DebugMenu.Menu);
Menu.AddSubMenu(UpdateMenu.Menu); Menu.AddSubMenu(UpdateMenu.Menu);
MenuPool.Add(Menu); MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu); MenuPool.Add(SettingsMenu.Menu);
MenuPool.Add(DevToolMenu.Menu); MenuPool.Add(DevToolMenu.Menu);
@ -96,6 +96,16 @@ namespace RageCoop.Client.Menus
{ {
Game.DisableAllControlsThisFrame(); Game.DisableAllControlsThisFrame();
MenuPool.Process(); MenuPool.Process();
var scaleform = new Scaleform("instructional_buttons");
scaleform.CallFunction("CLEAR_ALL");
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
scaleform.CallFunction("CREATE_CONTAINER");
scaleform.CallFunction("SET_DATA_SLOT", 0, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
scaleform.CallFunction("SET_DATA_SLOT", 1, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
scaleform.Render2D();
if (Game.IsControlJustPressed(Control.FrontendAccept)) if (Game.IsControlJustPressed(Control.FrontendAccept))
{ {
PopUp.Visible = false; PopUp.Visible = false;
@ -107,6 +117,8 @@ namespace RageCoop.Client.Menus
return false; return false;
} }
Script.Yield(); Script.Yield();
Game.DisableAllControlsThisFrame();
} }
} }
public static void UsernameActivated(object a, System.EventArgs b) public static void UsernameActivated(object a, System.EventArgs b)

View File

@ -1,7 +1,7 @@
using GTA; using GTA;
using LemonUI.Menus; using LemonUI.Menus;
using System.Drawing;
using System; using System;
using System.Drawing;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -17,7 +17,9 @@ namespace RageCoop.Client
UseMouse = false, UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
}; };
public static NativeItem ReloadItem = new NativeItem("Reload", "Reload RAGECOOP and associated scripts");
public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0"); public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0");
public static NativeCheckboxItem ShowOwnerItem = new NativeCheckboxItem("Show entity owner", "Show the owner name of the entity you're aiming at", false);
private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo); private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
static DebugMenu() static DebugMenu()
@ -44,12 +46,19 @@ namespace RageCoop.Client
catch (Exception ex) { Main.Logger.Error(ex); } catch (Exception ex) { Main.Logger.Error(ex); }
}; };
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; }; ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
ReloadItem.Activated += ReloadDomain;
Menu.Add(SimulatedLatencyItem); Menu.Add(SimulatedLatencyItem);
Menu.Add(ShowNetworkInfoItem); Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.Add(ReloadItem);
Menu.AddSubMenu(DiagnosticMenu); Menu.AddSubMenu(DiagnosticMenu);
} }
private static void ReloadDomain(object sender, EventArgs e)
{
Loader.LoaderContext.RequestUnload();
}
} }
} }

View File

@ -12,9 +12,9 @@ namespace RageCoop.Client
UseMouse = false, UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
}; };
private static NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable"); private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable");
private static NativeCheckboxItem enableSecondaryItem = new NativeCheckboxItem("Secondary", "Enable if this vehicle have two muzzles"); private static readonly NativeCheckboxItem enableSecondaryItem = new NativeCheckboxItem("Secondary", "Enable if this vehicle have two muzzles");
public static NativeItem boneIndexItem = new NativeItem("Current bone index"); public static NativeItem boneIndexItem = new NativeItem("Current bone index");
public static NativeItem secondaryBoneIndexItem = new NativeItem("Secondary bone index"); public static NativeItem secondaryBoneIndexItem = new NativeItem("Secondary bone index");
public static NativeItem clipboardItem = new NativeItem("Copy to clipboard"); public static NativeItem clipboardItem = new NativeItem("Copy to clipboard");
@ -73,10 +73,12 @@ namespace RageCoop.Client
{ {
if (enableItem.Checked) if (enableItem.Checked)
{ {
DevTool.Instance.Resume();
DevTool.ToMark = Game.Player.Character.CurrentVehicle; DevTool.ToMark = Game.Player.Character.CurrentVehicle;
} }
else else
{ {
DevTool.Instance.Pause();
DevTool.ToMark = null; DevTool.ToMark = null;
} }
} }

View File

@ -1,12 +1,13 @@
using LemonUI.Menus; using GTA.UI;
using LemonUI.Menus;
using Newtonsoft.Json; using Newtonsoft.Json;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using RageCoop.Core;
using GTA.UI;
namespace RageCoop.Client.Menus namespace RageCoop.Client.Menus
{ {
@ -60,7 +61,7 @@ namespace RageCoop.Client.Menus
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl)); serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
// Need to be processed in main thread // Need to be processed in main thread
Main.QueueAction(() => API.QueueAction(() =>
{ {
if (serverList == null) if (serverList == null)
{ {
@ -85,7 +86,7 @@ namespace RageCoop.Client.Menus
if (server.useZT) if (server.useZT)
{ {
address = $"{server.ztAddress}:{server.port}"; address = $"{server.ztAddress}:{server.port}";
Main.QueueAction(() => { Notification.Show($"~y~Joining ZeroTier network... {server.ztID}"); }); Notification.Show($"~y~Joining ZeroTier network... {server.ztID}");
if (ZeroTierHelper.Join(server.ztID) == null) if (ZeroTierHelper.Join(server.ztID) == null)
{ {
throw new Exception("Failed to obtain ZeroTier network IP"); throw new Exception("Failed to obtain ZeroTier network IP");
@ -127,7 +128,7 @@ namespace RageCoop.Client.Menus
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.QueueAction(() => API.QueueAction(() =>
{ {
ResultItem.Title = "Download failed!"; ResultItem.Title = "Download failed!";
ResultItem.Description = ex.Message; ResultItem.Description = ex.Message;

View File

@ -16,12 +16,12 @@ namespace RageCoop.Client.Menus
private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic); private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu); private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableTraffic); private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableAlternatePause);
private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable voice", "Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends", Main.Settings.Voice); private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable voice", "Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends", Main.Settings.Voice);
private static NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString()); private static readonly NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString());
private static NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString()); private static readonly NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
private static NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldVehicleSoftLimit.ToString()); private static readonly NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldVehicleSoftLimit.ToString());
static SettingsMenu() static SettingsMenu()
{ {
@ -53,7 +53,9 @@ namespace RageCoop.Client.Menus
{ {
Voice.Init(); Voice.Init();
} }
} else { }
else
{
Voice.ClearAll(); Voice.ClearAll();
} }
@ -108,6 +110,7 @@ namespace RageCoop.Client.Menus
public static void DisableTrafficCheckboxChanged(object a, System.EventArgs b) public static void DisableTrafficCheckboxChanged(object a, System.EventArgs b)
{ {
WorldThread.Traffic(!_disableTrafficItem.Checked);
Main.Settings.DisableTraffic = _disableTrafficItem.Checked; Main.Settings.DisableTraffic = _disableTrafficItem.Checked;
Util.SaveSettings(); Util.SaveSettings();
} }

View File

@ -1,5 +1,7 @@
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using LemonUI.Menus; using LemonUI.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
@ -11,10 +13,10 @@ namespace RageCoop.Client.Menus
internal class UpdateMenu internal class UpdateMenu
{ {
public static bool IsUpdating { get; private set; } = false; public static bool IsUpdating { get; private set; } = false;
private static NativeItem _updatingItem = new NativeItem("Updating..."); private static readonly NativeItem _updatingItem = new NativeItem("Updating...");
private static NativeItem _downloadItem = new NativeItem("Download", "Download and update to latest nightly"); private static readonly NativeItem _downloadItem = new NativeItem("Download", "Download and update to latest nightly");
private static string _downloadPath = Path.Combine(Main.Settings.DataDirectory, "RageCoop.Client.zip"); private static readonly string _downloadPath = Path.Combine(Main.Settings.DataDirectory, "RageCoop.Client.zip");
public static NativeMenu Menu = new NativeMenu("Update", "Update", "Download and install latest nightly build from GitHub") public static NativeMenu Menu = new NativeMenu("Update", "Update", "Download and install latest nightly build from GitHub")
{ {
UseMouse = false, UseMouse = false,
@ -30,6 +32,11 @@ namespace RageCoop.Client.Menus
private static void StartUpdate(object sender, EventArgs e) private static void StartUpdate(object sender, EventArgs e)
{ {
if (CoreUtils.GetLatestVersion() < Main.Version)
{
GTA.UI.Notification.Show("Local version is newer than remote version, update can't continue");
return;
}
IsUpdating = true; IsUpdating = true;
Menu.Clear(); Menu.Clear();
Menu.Add(_updatingItem); Menu.Add(_updatingItem);
@ -45,7 +52,7 @@ 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; };
client.DownloadProgressChanged += (s, e1) => { Main.QueueAction(() => { _updatingItem.AltTitle=$"{e1.ProgressPercentage}%"; }); }; client.DownloadProgressChanged += (s, e1) => { API.QueueAction(() => { _updatingItem.AltTitle = $"{e1.ProgressPercentage}%"; }); };
client.DownloadFileCompleted += (s, e2) => { Install(); }; client.DownloadFileCompleted += (s, e2) => { Install(); };
client.DownloadFileAsync(new Uri("https://github.com/RAGECOOP/RAGECOOP-V/releases/download/nightly/RageCoop.Client.zip"), _downloadPath); client.DownloadFileAsync(new Uri("https://github.com/RAGECOOP/RAGECOOP-V/releases/download/nightly/RageCoop.Client.zip"), _downloadPath);
} }
@ -60,22 +67,22 @@ namespace RageCoop.Client.Menus
{ {
try try
{ {
Main.QueueAction(() => API.QueueAction(() =>
{ {
_updatingItem.AltTitle = "Installing..."; _updatingItem.AltTitle = "Installing...";
}); });
Directory.CreateDirectory(@"Scripts\RageCoop"); var insatllPath = @"RageCoop\Scripts";
foreach(var f in Directory.GetFiles(@"Scripts\RageCoop", "*.dll", SearchOption.AllDirectories)) Directory.CreateDirectory(insatllPath);
foreach (var f in Directory.GetFiles(insatllPath, "*.dll", SearchOption.AllDirectories))
{ {
try { File.Delete(f); } try { File.Delete(f); }
catch { } catch { }
} }
new FastZip().ExtractZip(_downloadPath, "Scripts", FastZip.Overwrite.Always, null, null, null, true); new FastZip().ExtractZip(_downloadPath, insatllPath, FastZip.Overwrite.Always, null, null, null, true);
Main.QueueAction(() => try { File.Delete(_downloadPath); } catch { }
{ try { File.Delete(Path.Combine(insatllPath, "RageCoop.Client.Installer.exe")); } catch { }
Util.Reload(); Loader.LoaderContext.RequestUnload();
IsUpdating = false; IsUpdating = false;
});
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -16,7 +16,7 @@ namespace RageCoop.Client
private bool CurrentFocused { get; set; } private bool CurrentFocused { get; set; }
public bool Focused public bool Focused
{ {
get { return CurrentFocused; } get => CurrentFocused;
set set
{ {
if (value && Hidden) if (value && Hidden)
@ -35,7 +35,7 @@ namespace RageCoop.Client
private bool CurrentHidden { get; set; } private bool CurrentHidden { get; set; }
private bool Hidden private bool Hidden
{ {
get { return CurrentHidden; } get => CurrentHidden;
set set
{ {
if (value) if (value)

View File

@ -49,7 +49,7 @@ namespace RageCoop.Client
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger.Error("Error occurred when loading server resource:"); Main.Logger.Error("Error occurred when loading server resource");
Main.Logger.Error(ex); Main.Logger.Error(ex);
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed }; return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
} }
@ -122,8 +122,7 @@ namespace RageCoop.Client
{ {
lock (InProgressDownloads) lock (InProgressDownloads)
{ {
DownloadFile file; if (InProgressDownloads.TryGetValue(id, out DownloadFile file))
if (InProgressDownloads.TryGetValue(id, out file))
{ {
file.Stream.Write(chunk, 0, chunk.Length); file.Stream.Write(chunk, 0, chunk.Length);
@ -137,9 +136,8 @@ namespace RageCoop.Client
public static void Complete(int id) public static void Complete(int id)
{ {
DownloadFile f;
if (InProgressDownloads.TryGetValue(id, out f)) if (InProgressDownloads.TryGetValue(id, out DownloadFile f))
{ {
InProgressDownloads.Remove(id); InProgressDownloads.Remove(id);
f.Dispose(); f.Dispose();

View File

@ -1,12 +1,10 @@
using System; using Lidgren.Network;
using RageCoop.Core;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using RageCoop.Core;
using Lidgren.Network;
using System.Net; using System.Net;
using System.Timers;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -29,7 +27,7 @@ namespace RageCoop.Client
{ {
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null || p.Connection.Status == NetConnectionStatus.Disconnected)) if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null || p.Connection.Status == NetConnectionStatus.Disconnected))
{ {
Main.Logger.Trace($"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.PedID}"); Main.Logger.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
{ {
@ -69,7 +67,7 @@ namespace RageCoop.Client
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.PedID); Main.Logger.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); Main.Logger.Debug("Connecting to peer: " + from);

View File

@ -1,12 +1,13 @@
using Lidgren.Network; using GTA.UI;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
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 System.Net;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -17,14 +18,15 @@ namespace RageCoop.Client
public static bool ShowNetworkInfo = false; public static bool ShowNetworkInfo = false;
public static Security Security; public static Security Security;
public static NetConnection ServerConnection; public static NetConnection ServerConnection;
private static readonly Dictionary<int, Action<PacketType, byte[]>> PendingResponses = new Dictionary<int, Action<PacketType, byte[]>>(); private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new Dictionary<int, Action<PacketType, NetIncomingMessage>>();
internal static readonly Dictionary<PacketType, Func<byte[], Packet>> RequestHandlers = new Dictionary<PacketType, Func<byte[], Packet>>(); internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new Dictionary<PacketType, Func<NetIncomingMessage, Packet>>();
internal static float SimulatedLatency = 0; internal static float SimulatedLatency = 0;
public static bool IsConnecting { get; private set; } public static bool IsConnecting { get; private set; }
public static IPEndPoint _targetServerEP; public static IPEndPoint _targetServerEP;
static Networking() static Networking()
{ {
Security = new Security(Main.Logger); Security = new Security(Main.Logger);
Packets.CustomEvent.ResolveHandle = _resolveHandle;
} }
public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null) public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null)
@ -35,7 +37,8 @@ namespace RageCoop.Client
{ {
// ? // ?
} }
else if (IsConnecting) { else if (IsConnecting)
{
_publicKeyReceived.Set(); _publicKeyReceived.Set();
IsConnecting = false; IsConnecting = false;
Notification.Show("Connection has been canceled"); Notification.Show("Connection has been canceled");
@ -88,6 +91,8 @@ namespace RageCoop.Client
try try
{ {
_targetServerEP = CoreUtils.StringToEndPoint(address); _targetServerEP = CoreUtils.StringToEndPoint(address);
// Ensure static constructor invocation
DownloadManager.Cleanup(); DownloadManager.Cleanup();
Peer = new CoopPeer(config); Peer = new CoopPeer(config);
Peer.OnMessageReceived += (s, m) => Peer.OnMessageReceived += (s, m) =>
@ -95,17 +100,19 @@ namespace RageCoop.Client
try { ProcessMessage(m); } try { ProcessMessage(m); }
catch (Exception ex) { Main.Logger.Error(ex); } catch (Exception ex) { Main.Logger.Error(ex); }
}; };
Main.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); }); API.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
Menus.CoopMenu._serverConnectItem.Enabled = false; Menus.CoopMenu._serverConnectItem.Enabled = false;
Security.Regen(); Security.Regen();
if(publicKey==null){ if (publicKey == null)
{
if (!GetServerPublicKey(ip[0], int.Parse(ip[1]))) if (!GetServerPublicKey(ip[0], int.Parse(ip[1])))
{ {
Menus.CoopMenu._serverConnectItem.Enabled = true; Menus.CoopMenu._serverConnectItem.Enabled = true;
throw new TimeoutException("Failed to retrive server's public key"); throw new TimeoutException("Failed to retrive server's public key");
} }
} }
else{ else
{
Security.SetServerPublicKey(publicKey.Modulus, publicKey.Exponent); Security.SetServerPublicKey(publicKey.Modulus, publicKey.Exponent);
} }
@ -128,26 +135,26 @@ namespace RageCoop.Client
catch (Exception ex) catch (Exception ex)
{ {
Main.Logger.Error("Cannot connect to server: ", ex); Main.Logger.Error("Cannot connect to server: ", ex);
Main.QueueAction(() => Notification.Show("Cannot connect to server: "+ex.Message)); API.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
} }
IsConnecting = false; IsConnecting = false;
}); });
} }
} }
public static bool IsOnServer { get => ServerConnection?.Status == NetConnectionStatus.Connected; } public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
#region -- PLAYER -- #region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet) private static void PlayerConnect(Packets.PlayerConnect packet)
{ {
var p = new Player var p = new Player
{ {
PedID = 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}"); Main.Logger.Debug($"player connected:{p.Username}");
Main.QueueAction(() => API.QueueAction(() =>
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected.")); GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected."));
} }
private static void PlayerDisconnect(Packets.PlayerDisconnect packet) private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
@ -155,7 +162,8 @@ namespace RageCoop.Client
var player = PlayerList.GetPlayer(packet.PedID); var player = PlayerList.GetPlayer(packet.PedID);
if (player == null) { return; } if (player == null) { return; }
PlayerList.RemovePlayer(packet.PedID); PlayerList.RemovePlayer(packet.PedID);
Main.QueueAction(() => { API.QueueAction(() =>
{
EntityPool.RemoveAllFromPlayer(packet.PedID); EntityPool.RemoveAllFromPlayer(packet.PedID);
GTA.UI.Notification.Show($"~h~{player.Username}~h~ left."); GTA.UI.Notification.Show($"~h~{player.Username}~h~ left.");
}); });

View File

@ -1,9 +1,10 @@
using GTA; using GTA;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Menus; using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting;
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace RageCoop.Client namespace RageCoop.Client
@ -14,7 +15,7 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// Reduce GC pressure by reusing frequently used packets /// Reduce GC pressure by reusing frequently used packets
/// </summary> /// </summary>
static class ReceivedPackets private static class ReceivedPackets
{ {
public static Packets.PedSync PedPacket = new Packets.PedSync(); public static Packets.PedSync PedPacket = new Packets.PedSync();
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync(); public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
@ -24,7 +25,7 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent"/> /// Used to reslove entity handle in a <see cref="Packets.CustomEvent"/>
/// </summary> /// </summary>
private static readonly Func<byte, BitReader, object> _resolveHandle = (t, reader) => private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
{ {
switch (t) switch (t)
{ {
@ -41,10 +42,11 @@ namespace RageCoop.Client
} }
}; };
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;
switch (message.MessageType) switch (message.MessageType)
{ {
case NetIncomingMessageType.StatusChanged: case NetIncomingMessageType.StatusChanged:
@ -61,30 +63,18 @@ namespace RageCoop.Client
case NetConnectionStatus.Connected: case NetConnectionStatus.Connected:
if (message.SenderConnection == ServerConnection) if (message.SenderConnection == ServerConnection)
{ {
Memory.ApplyPatches();
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.ReadBytes(response.ReadInt32())); p.Deserialize(response);
foreach (var player in p.Players) foreach (var player in p.Players)
{ {
PlayerList.SetPlayer(player.ID, player.Username); PlayerList.SetPlayer(player.ID, player.Username);
} }
Main.QueueAction(() => Main.Connected();
{
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
if (Main.Settings.Voice && !Voice.WasInitialized())
{
Voice.Init();
}
GTA.UI.Notification.Show("~g~Connected!");
});
Main.Logger.Info(">> Connected <<");
} }
else else
{ {
@ -107,19 +97,7 @@ namespace RageCoop.Client
case NetConnectionStatus.Disconnected: case NetConnectionStatus.Disconnected:
if (message.SenderConnection == ServerConnection) if (message.SenderConnection == ServerConnection)
{ {
Memory.RestorePatches(); Main.Disconnected(reason);
DownloadManager.Cleanup();
if (Main.MainChat.Focused)
{
Main.MainChat.Focused = false;
}
Main.QueueAction(() => Main.CleanUp());
CoopMenu.DisconnectedMenuSetting();
Main.Logger.Info($">> Disconnected << reason: {reason}");
Main.QueueAction(() => GTA.UI.Notification.Show("~r~Disconnected: " + reason));
Main.Resources.Unload();
} }
break; break;
} }
@ -139,7 +117,7 @@ namespace RageCoop.Client
int id = message.ReadInt32(); int id = message.ReadInt32();
if (PendingResponses.TryGetValue(id, out var callback)) if (PendingResponses.TryGetValue(id, out var callback))
{ {
callback((PacketType)message.ReadByte(), message.ReadBytes(message.ReadInt32())); callback((PacketType)message.ReadByte(), message);
PendingResponses.Remove(id); PendingResponses.Remove(id);
} }
break; break;
@ -148,32 +126,34 @@ namespace RageCoop.Client
{ {
int id = message.ReadInt32(); int id = message.ReadInt32();
var realType = (PacketType)message.ReadByte(); var realType = (PacketType)message.ReadByte();
int len = message.ReadInt32();
if (RequestHandlers.TryGetValue(realType, out var handler)) if (RequestHandlers.TryGetValue(realType, out var handler))
{ {
var response = Peer.CreateMessage(); var response = Peer.CreateMessage();
response.Write((byte)PacketType.Response); response.Write((byte)PacketType.Response);
response.Write(id); response.Write(id);
handler(message.ReadBytes(len)).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
{
Main.Logger.Debug("Did not find a request handler of type: " + realType);
}
break; break;
} }
default: default:
{ {
byte[] data = message.ReadBytes(message.ReadInt32());
HandlePacket(packetType, data,message.SenderConnection); HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
break; break;
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Main.QueueAction(() => API.QueueAction(() =>
{ {
GTA.UI.Notification.Show("~r~~h~Packet Error"); GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true; return true;
}); });
Main.Logger.Error($"[{packetType}] {ex.Message}"); Main.Logger.Error($"[{packetType}] {ex.Message}");
@ -185,20 +165,18 @@ namespace RageCoop.Client
case NetIncomingMessageType.UnconnectedData: case NetIncomingMessageType.UnconnectedData:
{ {
var packetType = (PacketType)message.ReadByte(); var packetType = (PacketType)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
switch (packetType) switch (packetType)
{ {
case PacketType.HolePunch: case PacketType.HolePunch:
{ {
HolePunch.Punched(data.GetPacket<Packets.HolePunch>(), message.SenderEndPoint); HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
break; break;
} }
case PacketType.PublicKeyResponse: case PacketType.PublicKeyResponse:
{ {
if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) { break; } if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) { break; }
var packet = data.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;
@ -215,41 +193,42 @@ namespace RageCoop.Client
default: default:
break; break;
} }
if (_recycle)
{
Peer.Recycle(message); Peer.Recycle(message);
} }
private static void HandlePacket(PacketType packetType, byte[] data, NetConnection senderConnection) }
private static void HandlePacket(PacketType packetType, NetIncomingMessage msg, NetConnection senderConnection, ref bool recycle)
{ {
switch (packetType) switch (packetType)
{ {
case PacketType.HolePunchInit: case PacketType.HolePunchInit:
HolePunch.Add(data.GetPacket<Packets.HolePunchInit>()); HolePunch.Add(msg.GetPacket<Packets.HolePunchInit>());
break; break;
case PacketType.PlayerConnect: case PacketType.PlayerConnect:
PlayerConnect(data.GetPacket<Packets.PlayerConnect>()); PlayerConnect(msg.GetPacket<Packets.PlayerConnect>());
break; break;
case PacketType.PlayerDisconnect: case PacketType.PlayerDisconnect:
PlayerDisconnect(data.GetPacket<Packets.PlayerDisconnect>()); PlayerDisconnect(msg.GetPacket<Packets.PlayerDisconnect>());
break; break;
case PacketType.PlayerInfoUpdate: case PacketType.PlayerInfoUpdate:
PlayerList.UpdatePlayer(data.GetPacket<Packets.PlayerInfoUpdate>()); PlayerList.UpdatePlayer(msg.GetPacket<Packets.PlayerInfoUpdate>());
break; break;
case PacketType.VehicleSync: case PacketType.VehicleSync:
ReceivedPackets.VehicelPacket.Deserialize(data); ReceivedPackets.VehicelPacket.Deserialize(msg);
VehicleSync(ReceivedPackets.VehicelPacket); VehicleSync(ReceivedPackets.VehicelPacket);
break; break;
case PacketType.PedSync: case PacketType.PedSync:
ReceivedPackets.PedPacket.Deserialize(data); ReceivedPackets.PedPacket.Deserialize(msg);
PedSync(ReceivedPackets.PedPacket); PedSync(ReceivedPackets.PedPacket);
break; break;
case PacketType.ProjectileSync: case PacketType.ProjectileSync:
ReceivedPackets.ProjectilePacket.Deserialize(data); ReceivedPackets.ProjectilePacket.Deserialize(msg);
ProjectileSync(ReceivedPackets.ProjectilePacket); ProjectileSync(ReceivedPackets.ProjectilePacket);
break; break;
@ -257,9 +236,9 @@ namespace RageCoop.Client
{ {
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b)); Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
packet.Deserialize(data); packet.Deserialize(msg);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; }); API.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
} }
break; break;
@ -268,7 +247,7 @@ namespace RageCoop.Client
if (Main.Settings.Voice) if (Main.Settings.Voice)
{ {
Packets.Voice packet = new Packets.Voice(); Packets.Voice packet = new Packets.Voice();
packet.Deserialize(data); packet.Deserialize(msg);
SyncedPed player = EntityPool.GetPedByID(packet.ID); SyncedPed player = EntityPool.GetPedByID(packet.ID);
@ -282,27 +261,29 @@ namespace RageCoop.Client
case PacketType.CustomEvent: case PacketType.CustomEvent:
{ {
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle); Packets.CustomEvent packet = new Packets.CustomEvent();
packet.Deserialize(data); if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
{
recycle = false;
API.QueueAction(() =>
{
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg);
});
}
else
{
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet); Scripting.API.Events.InvokeCustomEventReceived(packet);
} }
break;
case PacketType.CustomEventQueued:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
Main.QueueAction(() =>
{
packet.Deserialize(data);
Scripting.API.Events.InvokeCustomEventReceived(packet);
});
} }
break; break;
case PacketType.FileTransferChunk: case PacketType.FileTransferChunk:
{ {
Packets.FileTransferChunk packet = new Packets.FileTransferChunk(); Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Deserialize(data); packet.Deserialize(msg);
DownloadManager.Write(packet.ID, packet.FileChunk); DownloadManager.Write(packet.ID, packet.FileChunk);
} }
break; break;
@ -310,8 +291,9 @@ namespace RageCoop.Client
default: default:
if (packetType.IsSyncEvent()) if (packetType.IsSyncEvent())
{ {
recycle = false;
// Dispatch to script thread // Dispatch to script thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; }); API.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
} }
break; break;
} }
@ -335,6 +317,7 @@ 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;
@ -420,6 +403,7 @@ namespace RageCoop.Client
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) : EntityPool.GetPedByID(packet.ShooterID);
p.LastSynced = Main.Ticked; p.LastSynced = Main.Ticked;
p.LastSyncedStopWatch.Restart();
} }
} }
} }

View File

@ -2,6 +2,7 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Lidgren.Network; using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -13,7 +14,7 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// Reduce GC pressure by reusing frequently used packets /// Reduce GC pressure by reusing frequently used packets
/// </summary> /// </summary>
static class SendPackets private static class SendPackets
{ {
public static Packets.PedSync PedPacket = new Packets.PedSync(); public static Packets.PedSync PedPacket = new Packets.PedSync();
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync(); public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
@ -90,9 +91,9 @@ namespace RageCoop.Client
Blip b; Blip b;
if (sp.IsPlayer) if (sp.IsPlayer)
{ {
p.BlipColor=Scripting.API.Config.BlipColor; p.BlipColor = API.Config.BlipColor;
p.BlipSprite=Scripting.API.Config.BlipSprite; p.BlipSprite = API.Config.BlipSprite;
p.BlipScale=Scripting.API.Config.BlipScale; p.BlipScale = API.Config.BlipScale;
} }
else if ((b = ped.AttachedBlip) != null) else if ((b = ped.AttachedBlip) != null)
{ {

View File

@ -1,11 +1,12 @@
using GTA; using GTA;
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core; using RageCoop.Core;
using System.Collections.Generic; using System.Collections.Generic;
using Lidgren.Network;
using System.Net;
using System.Linq; using System.Linq;
using System.Net;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -54,7 +55,7 @@ namespace RageCoop.Client
foreach (var player in Players.Values) foreach (var player in Players.Values)
{ {
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms", player.Username, 116, 0, i - 1, "", "", 2, "", "", ' '); _mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms", player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' ');
} }
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players"); _mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players");
@ -63,16 +64,15 @@ namespace RageCoop.Client
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}"); Main.Logger.Debug($"{id},{username},{latency}");
Player p; if (Players.TryGetValue(id, out Player p))
if (Players.TryGetValue(id, out p))
{ {
p.Username = username; p.Username = username;
p.PedID=id; p.ID = id;
p._latencyToServer = latency; p._latencyToServer = latency;
} }
else else
{ {
p = new Player { PedID=id, Username=username, _latencyToServer=latency }; p = new Player { ID = id, Username = username, _latencyToServer = latency };
Players.Add(id, p); Players.Add(id, p);
} }
} }
@ -83,24 +83,24 @@ namespace RageCoop.Client
{ {
p._latencyToServer = packet.Latency; p._latencyToServer = packet.Latency;
p.Position = packet.Position; p.Position = packet.Position;
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 (p.Position.DistanceTo(Main.PlayerPosition)>500) if (EntityPool.PedExists(p.ID))
{ {
p.FakeBlip.Color=Scripting.API.Config.BlipColor; p.FakeBlip.DisplayType = BlipDisplayType.NoDisplay;
p.FakeBlip.Scale=Scripting.API.Config.BlipScale;
p.FakeBlip.Sprite=Scripting.API.Config.BlipSprite;
p.FakeBlip.DisplayType=BlipDisplayType.Default;
p.FakeBlip.Position=p.Position;
} }
else else
{ {
p.FakeBlip.DisplayType=BlipDisplayType.NoDisplay; p.FakeBlip.Color = API.Config.BlipColor;
p.FakeBlip.Scale = API.Config.BlipScale;
p.FakeBlip.Sprite = API.Config.BlipSprite;
p.FakeBlip.DisplayType = BlipDisplayType.Default;
p.FakeBlip.Position = p.Position;
} }
}); });
@ -108,8 +108,7 @@ namespace RageCoop.Client
} }
public static Player GetPlayer(int id) public static Player GetPlayer(int id)
{ {
Player p; Players.TryGetValue(id, out Player p);
Players.TryGetValue(id, out p);
return p; return p;
} }
public static Player GetPlayer(SyncedPed p) public static Player GetPlayer(SyncedPed p)
@ -126,7 +125,7 @@ namespace RageCoop.Client
if (Players.TryGetValue(id, out var player)) if (Players.TryGetValue(id, out var player))
{ {
Players.Remove(id); Players.Remove(id);
Main.QueueAction(() => player.FakeBlip?.Delete()); API.QueueAction(() => player.FakeBlip?.Delete());
} }
} }
public static void Cleanup() public static void Cleanup()
@ -139,31 +138,32 @@ namespace RageCoop.Client
} }
} }
internal class Player public class Player
{ {
public byte HolePunchStatus { get; set; } = 1; public byte HolePunchStatus { get; internal set; } = 1;
public bool IsHost { get; internal set; }
public string Username { get; internal set; } public string Username { get; internal set; }
/// <summary> /// <summary>
/// Universal character ID. /// Universal ped ID.
/// </summary> /// </summary>
public int PedID public int ID
{ {
get; internal set; get; internal set;
} }
public IPEndPoint InternalEndPoint { get; set; } public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; set; } public IPEndPoint ExternalEndPoint { get; internal set; }
public bool ConnectWhenPunched { get; set; } internal bool ConnectWhenPunched { get; set; }
public Blip FakeBlip { get; set; } public Blip FakeBlip { get; internal set; }
public Vector3 Position { get; set; } public Vector3 Position { get; internal set; }
public SyncedPed Character { get; set; } public SyncedPed Character { get; internal set; }
/// <summary> /// <summary>
/// Player round-trip time in seconds, will be the latency 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==PedID ? Networking.Latency*2 : (HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer*2); public float Ping => Main.LocalPlayerID == ID ? Networking.Latency * 2 : (HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2);
public float PacketTravelTime => HasDirectConnection ? Connection.AverageRoundtripTime / 2 : Networking.Latency + _latencyToServer; public float PacketTravelTime => HasDirectConnection ? Connection.AverageRoundtripTime / 2 : Networking.Latency + _latencyToServer;
public float _latencyToServer = 0; internal float _latencyToServer = 0;
public bool DisplayNameTag { get; set; } = true; public bool DisplayNameTag { get; set; } = true;
public NetConnection Connection { get; set; } 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 informationr( // Version informationr(
[assembly: AssemblyVersion("1.5.1.13")] [assembly: AssemblyVersion("1.5.6.1")]
[assembly: AssemblyFileVersion("1.5.1.13")] [assembly: AssemblyFileVersion("1.5.6.1")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )] [assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RageCoop.Client</RootNamespace>
<AssemblyName>RageCoop.Client</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutPutPath>..\..\bin\Debug\Client</OutPutPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>DEBUG</DefineConstants>
<WarningLevel>4</WarningLevel>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutPutPath>..\..\bin\Release\Client</OutPutPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="Debug.cs" />
<Compile Include="DevTools\DevTool.cs" />
<Compile Include="Main.cs" />
<Compile Include="Menus\CoopMenu.cs" />
<Compile Include="Menus\Sub\DebugMenu.cs" />
<Compile Include="Menus\Sub\DevToolMenu.cs" />
<Compile Include="Menus\Sub\ServersMenu.cs" />
<Compile Include="Menus\Sub\SettingsMenu.cs" />
<Compile Include="Menus\Sub\UpdateMenu.cs" />
<Compile Include="Networking\Chat.cs" />
<Compile Include="Networking\DownloadManager.cs" />
<Compile Include="Networking\HolePunch.cs" />
<Compile Include="Networking\Networking.cs" />
<Compile Include="Networking\Receive.cs" />
<Compile Include="Networking\Send.cs" />
<Compile Include="Networking\Statistics.cs" />
<Compile Include="PlayerList.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AssemblyInfo.tt</DependentUpon>
</Compile>
<Compile Include="Scripting\API.cs" />
<Compile Include="Scripting\BaseScript.cs" />
<Compile Include="Scripting\ClientScript.cs" />
<Compile Include="Scripting\Resources.cs" />
<Compile Include="Security.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Sync\Entities\Ped\SyncedPed.Members.cs" />
<Compile Include="Sync\Entities\Ped\SyncedPed.Animations.cs" />
<Compile Include="Sync\Entities\SyncedEntity.cs" />
<Compile Include="Sync\Entities\Ped\SyncedPed.cs" />
<Compile Include="Sync\Entities\SyncedProjectile.cs" />
<Compile Include="Sync\Entities\SyncedProp.cs" />
<Compile Include="Sync\Entities\Vehicle\SyncedVehicle.cs" />
<Compile Include="Sync\Entities\Vehicle\SyncedVehicle.Members.cs" />
<Compile Include="Sync\EntityPool.cs" />
<Compile Include="Sync\SyncEvents.cs" />
<Compile Include="Sync\Voice.cs" />
<Compile Include="Util\AddOnDataProvider.cs" />
<Compile Include="Util\Memory.cs" />
<Compile Include="Util\NativeCaller.cs" />
<Compile Include="Util\PedConfigFlags.cs" />
<Compile Include="Util\PedExtensions.cs" />
<Compile Include="Util\TaskType.cs" />
<Compile Include="Util\Util.cs" />
<Compile Include="Util\VehicleExtensions.cs" />
<Compile Include="Util\WeaponUtil.cs" />
<Compile Include="WorldThread.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.4.0.12, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\..\packages\SharpZipLib.1.4.0\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="LemonUI.SHVDN3">
<HintPath>..\..\libs\LemonUI.SHVDN3.dll</HintPath>
</Reference>
<Reference Include="Lidgren.Network">
<HintPath>..\..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Extensions.ObjectPool, Version=6.0.8.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.ObjectPool.6.0.8\lib\net461\Microsoft.Extensions.ObjectPool.dll</HintPath>
</Reference>
<Reference Include="NAudio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.2.1.0\lib\net472\NAudio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Asio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Asio.2.1.0\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Core.2.1.0\lib\netstandard2.0\NAudio.Core.dll</HintPath>
</Reference>
<Reference Include="NAudio.Midi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Midi.2.1.0\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
</Reference>
<Reference Include="NAudio.Wasapi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Wasapi.2.1.0\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinForms, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.WinForms.2.1.0\lib\net472\NAudio.WinForms.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinMM, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.WinMM.2.1.0\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
</Content>
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj">
<Project>{cc2e8102-e568-4524-aa1f-f8e0f1cfe58a}</Project>
<Name>RageCoop.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Loader\RageCoop.Client.Loader.csproj">
<Project>{fc8cbdbb-6dc3-43af-b34d-092e476410a5}</Project>
<Name>RageCoop.Client.Loader</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup Condition=" '$(DevEnvDir)' != '*Undefined*'">
<PostBuildEvent>"$(DevEnvDir)TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt"</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -1,8 +1,12 @@
#undef DEBUG #undef DEBUG
using GTA; using GTA;
using Newtonsoft.Json;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
@ -24,7 +28,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Provides vital functionality to interact with RAGECOOP /// Provides vital functionality to interact with RAGECOOP
/// </summary> /// </summary>
public class API public static class API
{ {
#region INTERNAL #region INTERNAL
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>(); internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
@ -39,7 +43,7 @@ namespace RageCoop.Client.Scripting
/// </summary> /// </summary>
public static string Username public static string Username
{ {
get { return Main.Settings.Username; } get => Main.Settings.Username;
set set
{ {
if (Networking.IsOnServer || string.IsNullOrEmpty(value)) if (Networking.IsOnServer || string.IsNullOrEmpty(value))
@ -115,16 +119,22 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// This is equivalent of <see cref="GTA.Script.Tick"/>. /// This is equivalent of <see cref="GTA.Script.Tick"/>.
/// </summary> /// </summary>
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.Tick"/> instead.</remarks>
[Obsolete]
public static event EmptyEvent OnTick; public static event EmptyEvent OnTick;
/// <summary> /// <summary>
/// This is equivalent of <see cref="Script.KeyDown"/> /// This is equivalent of <see cref="Script.KeyDown"/>
/// </summary> /// </summary>
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyDown"/> instead.</remarks>
[Obsolete]
public static KeyEventHandler OnKeyDown; public static KeyEventHandler OnKeyDown;
/// <summary> /// <summary>
/// This is equivalent of <see cref="Script.KeyUp"/> /// This is equivalent of <see cref="Script.KeyUp"/>
/// </summary> /// </summary>
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyUp"/> instead.</remarks>
[Obsolete]
public static KeyEventHandler OnKeyUp; public static KeyEventHandler OnKeyUp;
#region INVOKE #region INVOKE
@ -145,8 +155,7 @@ namespace RageCoop.Client.Scripting
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType()); // Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
List<Action<CustomEventReceivedArgs>> handlers; if (CustomEventHandlers.TryGetValue(p.Hash, out List<Action<CustomEventReceivedArgs>> handlers))
if (CustomEventHandlers.TryGetValue(p.Hash, out handlers))
{ {
handlers.ForEach((x) => { x.Invoke(args); }); handlers.ForEach((x) => { x.Invoke(args); });
} }
@ -160,65 +169,48 @@ namespace RageCoop.Client.Scripting
/// Get the local player's ID /// Get the local player's ID
/// </summary> /// </summary>
/// <returns>PlayerID</returns> /// <returns>PlayerID</returns>
public static int LocalPlayerID public static int LocalPlayerID => Main.LocalPlayerID;
{
get { return Main.LocalPlayerID; }
}
/// <summary> /// <summary>
/// Check if player is connected to a server /// Check if player is connected to a server
/// </summary> /// </summary>
public static bool IsOnServer { get { return Networking.IsOnServer; } } public static bool IsOnServer => Networking.IsOnServer;
/// <summary> /// <summary>
/// Get an <see cref="System.Net.IPEndPoint"/> that the player is currently connected to, or null if not connected to the server /// Get an <see cref="System.Net.IPEndPoint"/> that the player is currently connected to, or null if not connected to the server
/// </summary> /// </summary>
public static System.Net.IPEndPoint ServerEndPoint { get { return Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null; } } public static System.Net.IPEndPoint ServerEndPoint => Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
/// <summary> /// <summary>
/// Check if a RAGECOOP menu is visible /// Check if a RAGECOOP menu is visible
/// </summary> /// </summary>
public static bool IsMenuVisible public static bool IsMenuVisible => Menus.CoopMenu.MenuPool.AreAnyVisible;
{
get { return Menus.CoopMenu.MenuPool.AreAnyVisible; }
}
/// <summary> /// <summary>
/// Check if the RAGECOOP chat is visible /// Check if the RAGECOOP chat is visible
/// </summary> /// </summary>
public static bool IsChatFocused public static bool IsChatFocused => Main.MainChat.Focused;
{
get { return Main.MainChat.Focused; }
}
/// <summary> /// <summary>
/// Check if the RAGECOOP list of players is visible /// Check if the RAGECOOP list of players is visible
/// </summary> /// </summary>
public static bool IsPlayerListVisible public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
{
get { return Util.GetTickCount64() - PlayerList.Pressed < 5000; }
}
/// <summary> /// <summary>
/// Get the version of RAGECOOP /// Get the version of RAGECOOP
/// </summary> /// </summary>
public static Version CurrentVersion public static Version CurrentVersion => Main.Version;
{
get { return Main.Version; }
}
/// <summary> /// <summary>
/// Get a <see cref="Core.Logger"/> that RAGECOOP is currently using. /// Get a <see cref="Core.Logger"/> that RAGECOOP is currently using.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static Logger Logger public static Logger Logger => Main.Logger;
{ /// <summary>
get /// Get all players indexed by their ID
{ /// </summary>
return Main.Logger; public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players);
}
}
#endregion #endregion
#region FUNCTIONS #region FUNCTIONS
@ -245,6 +237,16 @@ namespace RageCoop.Client.Scripting
Networking.ToggleConnection(null); Networking.ToggleConnection(null);
} }
} }
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
public static List<ServerInfo> ListServers()
{
return JsonConvert.DeserializeObject<List<ServerInfo>>(HttpHelper.DownloadString(Main.Settings.MasterServer));
}
/// <summary> /// <summary>
/// Send a local chat message to this player /// Send a local chat message to this player
/// </summary> /// </summary>
@ -256,21 +258,12 @@ namespace RageCoop.Client.Scripting
} }
/// <summary> /// <summary>
/// Queue an action to be executed on next tick. /// Send a chat message or command to server/other players
/// </summary> /// </summary>
/// <param name="a"></param> /// <param name="message"></param>
public static void QueueAction(Action a) public static void SendChatMessage(string message)
{ {
Main.QueueAction(a); Networking.SendChatMessage(message);
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> An <see cref="Func{T, TResult}"/> to be executed with a return value indicating whether it can be removed after execution.</param>
public static void QueueAction(Func<bool> a)
{
Main.QueueAction(a);
} }
/// <summary> /// <summary>
@ -278,7 +271,7 @@ namespace RageCoop.Client.Scripting
/// </summary> /// </summary>
/// <param name="eventHash">An unique identifier of the event</param> /// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param> /// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
public static void SendCustomEvent(int eventHash, params object[] args) public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
{ {
Networking.Peer.SendTo(new Packets.CustomEvent() Networking.Peer.SendTo(new Packets.CustomEvent()
@ -287,13 +280,27 @@ namespace RageCoop.Client.Scripting
Hash = eventHash Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered); }, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
} }
/// <summary>
/// Send an event and data to the server
/// </summary>
/// <param name="flags"></param>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
{
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
{
Args = args,
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <summary> /// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from backgound thread, use <see cref="QueueAction(Action)"/> in the handler to dispatch code to script thread. /// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from backgound thread, use <see cref="QueueAction(Action)"/> in the handler to dispatch code to script thread.
/// </summary> /// </summary>
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param> /// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param> /// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(int hash, Action<CustomEventReceivedArgs> handler) public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
{ {
lock (CustomEventHandlers) lock (CustomEventHandlers)
{ {
@ -333,5 +340,41 @@ namespace RageCoop.Client.Scripting
}); });
} }
#endregion #endregion
/// <summary>
/// Queue an action to be executed on next tick.
/// </summary>
/// <param name="a"></param>
public static void QueueAction(Action a)
{
WorldThread.QueueAction(a);
}
public static void QueueActionAndWait(Action a, int timeout = 15000)
{
var done = new AutoResetEvent(false);
Exception e = null;
QueueAction(() =>
{
try
{
a();
done.Set();
}
catch (Exception ex) { e = ex; }
});
if (e != null) { throw e; }
else if (!done.WaitOne(timeout)) { throw new TimeoutException(); }
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> An <see cref="Func{T, TResult}"/> to be executed with a return value indicating whether it can be removed after execution.</param>
public static void QueueAction(Func<bool> a)
{
WorldThread.QueueAction(a);
}
} }
} }

View File

@ -9,10 +9,10 @@ using System.Threading.Tasks;
namespace RageCoop.Client.Scripting namespace RageCoop.Client.Scripting
{ {
internal class BaseScript : ClientScript internal static class BaseScript
{ {
private bool _isHost = false; private static bool _isHost = false;
public override void OnStart() public static void OnStart()
{ {
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); }; API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); }; API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
@ -57,19 +57,19 @@ namespace RageCoop.Client.Scripting
}); });
} }
private void WeatherTimeSync(CustomEventReceivedArgs e) private static void WeatherTimeSync(CustomEventReceivedArgs e)
{ {
World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]); World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
Function.Call(Hash._SET_WEATHER_TYPE_TRANSITION, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]); Function.Call(Hash._SET_WEATHER_TYPE_TRANSITION, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
} }
private void SetDisplayNameTag(CustomEventReceivedArgs e) private static void SetDisplayNameTag(CustomEventReceivedArgs e)
{ {
var p = PlayerList.GetPlayer((int)e.Args[0]); var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; } if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; }
} }
private void UpdatePedBlip(CustomEventReceivedArgs e) private static void UpdatePedBlip(CustomEventReceivedArgs e)
{ {
var p = Entity.FromHandle((int)e.Args[0]); var p = Entity.FromHandle((int)e.Args[0]);
if (p == null) { return; } if (p == null) { return; }
@ -89,14 +89,13 @@ namespace RageCoop.Client.Scripting
} }
} }
private void CreateVehicle(CustomEventReceivedArgs e) private static void CreateVehicle(CustomEventReceivedArgs e)
{ {
var vehicleModel = (Model)e.Args[1]; var vehicleModel = (Model)e.Args[1];
vehicleModel.Request(1000); vehicleModel.Request(1000);
Vehicle veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]); Vehicle veh;
while (veh==null) while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
{ {
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
Thread.Sleep(10); Thread.Sleep(10);
} }
veh.CanPretendOccupants = false; veh.CanPretendOccupants = false;
@ -109,7 +108,7 @@ namespace RageCoop.Client.Scripting
EntityPool.Add(v); EntityPool.Add(v);
} }
private void DeleteServerBlip(CustomEventReceivedArgs e) private static void DeleteServerBlip(CustomEventReceivedArgs e)
{ {
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip)) if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
{ {
@ -118,7 +117,7 @@ namespace RageCoop.Client.Scripting
} }
} }
private void ServerBlipSync(CustomEventReceivedArgs obj) private static void ServerBlipSync(CustomEventReceivedArgs obj)
{ {
int id = (int)obj.Args[0]; int id = (int)obj.Args[0];
var sprite = (BlipSprite)(ushort)obj.Args[1]; var sprite = (BlipSprite)(ushort)obj.Args[1];
@ -127,8 +126,7 @@ namespace RageCoop.Client.Scripting
var pos = (Vector3)obj.Args[4]; var pos = (Vector3)obj.Args[4];
int rot = (int)obj.Args[5]; int rot = (int)obj.Args[5];
var name = (string)obj.Args[6]; var name = (string)obj.Args[6];
Blip blip; if (!EntityPool.ServerBlips.TryGetValue(id, out Blip blip))
if (!EntityPool.ServerBlips.TryGetValue(id, out blip))
{ {
EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos)); EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos));
} }
@ -141,15 +139,12 @@ namespace RageCoop.Client.Scripting
} }
private void DeleteEntity(CustomEventReceivedArgs e) private static void DeleteEntity(CustomEventReceivedArgs e)
{ {
Entity.FromHandle((int)e.Args[0])?.Delete(); Entity.FromHandle((int)e.Args[0])?.Delete();
} }
public override void OnStop() private static void SetNameTag(CustomEventReceivedArgs e)
{
}
private void SetNameTag(CustomEventReceivedArgs e)
{ {
var p = PlayerList.GetPlayer((int)e.Args[0]); var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null) if (p != null)
@ -157,11 +152,11 @@ namespace RageCoop.Client.Scripting
p.DisplayNameTag = (bool)e.Args[1]; p.DisplayNameTag = (bool)e.Args[1];
} }
} }
private void SetAutoRespawn(CustomEventReceivedArgs args) private static void SetAutoRespawn(CustomEventReceivedArgs args)
{ {
API.Config.EnableAutoRespawn = (bool)args.Args[0]; API.Config.EnableAutoRespawn = (bool)args.Args[0];
} }
private void DeleteServerProp(CustomEventReceivedArgs e) private static void DeleteServerProp(CustomEventReceivedArgs e)
{ {
var id = (int)e.Args[0]; var id = (int)e.Args[0];
if (EntityPool.ServerProps.TryGetValue(id, out var prop)) if (EntityPool.ServerProps.TryGetValue(id, out var prop))
@ -171,7 +166,7 @@ namespace RageCoop.Client.Scripting
} }
} }
private void ServerObjectSync(CustomEventReceivedArgs e) private static void ServerObjectSync(CustomEventReceivedArgs e)
{ {
SyncedProp prop; SyncedProp prop;
var id = (int)e.Args[0]; var id = (int)e.Args[0];
@ -186,9 +181,10 @@ namespace RageCoop.Client.Scripting
prop.Model = (Model)e.Args[1]; prop.Model = (Model)e.Args[1];
prop.Position = (Vector3)e.Args[2]; prop.Position = (Vector3)e.Args[2];
prop.Rotation = (Vector3)e.Args[3]; prop.Rotation = (Vector3)e.Args[3];
prop.Model.Request(1000);
prop.Update(); prop.Update();
} }
private void NativeCall(CustomEventReceivedArgs e) private static void NativeCall(CustomEventReceivedArgs e)
{ {
List<InputArgument> arguments = new List<InputArgument>(); List<InputArgument> arguments = new List<InputArgument>();
int i; int i;
@ -272,7 +268,7 @@ namespace RageCoop.Client.Scripting
} }
} }
private InputArgument GetInputArgument(object obj) private static InputArgument GetInputArgument(object obj)
{ {
// Implicit conversion // Implicit conversion
switch (obj) switch (obj)

View File

@ -5,15 +5,16 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script. /// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary> /// </summary>
public abstract class ClientScript public abstract class ClientScript : GTA.Script
{ {
/// <summary> /// <summary>
/// This method would be called from background thread, call <see cref="API.QueueAction(System.Action)"/> to dispatch it to main thread. /// This method would be called from main thread, right after all script constructors are invoked.
/// </summary> /// </summary>
public abstract void OnStart(); public abstract void OnStart();
/// <summary> /// <summary>
/// This method would be called from background thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method. /// This method would be called from main thread right before the whole <see cref="System.AppDomain"/> is unloded but prior to <see cref="GTA.Script.Aborted"/>.
/// </summary> /// </summary>
public abstract void OnStop(); public abstract void OnStop();
@ -30,7 +31,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Eqivalent of <see cref="ClientResource.Logger"/> in <see cref="CurrentResource"/> /// Eqivalent of <see cref="ClientResource.Logger"/> in <see cref="CurrentResource"/>
/// </summary> /// </summary>
public Core.Logger Logger { get { return CurrentResource.Logger; } } public Core.Logger Logger => CurrentResource.Logger;
} }
} }

View File

@ -0,0 +1,190 @@
using ICSharpCode.SharpZipLib.Zip;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace RageCoop.Client.Scripting
{
/// <summary>
///
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Directory where the scripts is loaded from
/// </summary>
public string ScriptsDirectory { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
/// <summary>
/// Get all <see cref="ClientScript"/> instance in this resource.
/// </summary>
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
/// <summary>
/// Get the <see cref="ResourceFile"/> where this script is loaded from.
/// </summary>
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
/// <summary>
/// A <see cref="Core.Logger"/> instance that can be used to debug your resource.
/// </summary>
public Logger Logger { get; internal set; }
}
internal class Resources
{
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new ConcurrentDictionary<string, ClientResource>();
private Logger Logger { get; set; }
public Resources()
{
Logger = Main.Logger;
}
public void Load(string path, string[] zips)
{
LoadedResources.Clear();
foreach (var zip in zips)
{
var zipPath = Path.Combine(path, zip);
Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
Unpack(zipPath, Path.Combine(path, "Data"));
}
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored()).ForEach(x => File.Delete(x));
// Load it in main thread
API.QueueActionAndWait(() =>
{
Main.QueueToMainThreadAndWait(() =>
{
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).ForEach(x => ScriptDomain.CurrentDomain.StartScripts(x));
SetupScripts();
});
});
}
public void Unload()
{
StopScripts();
LoadedResources.Clear();
Loader.LoaderContext.RequestUnload();
}
private void Unpack(string zipPath, string dataFolderRoot)
{
var r = new ClientResource()
{
Logger = API.Logger,
Scripts = new List<ClientScript>(),
Name = Path.GetFileNameWithoutExtension(zipPath),
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
ScriptsDirectory = Path.Combine(Directory.GetParent(zipPath).FullName, Path.GetFileNameWithoutExtension(zipPath))
};
Directory.CreateDirectory(r.DataFolder);
var scriptsDir = r.ScriptsDirectory;
if (Directory.Exists(scriptsDir)) { Directory.Delete(scriptsDir, true); }
else if (File.Exists(scriptsDir)) { File.Delete(scriptsDir); }
Directory.CreateDirectory(scriptsDir);
new FastZip().ExtractZip(zipPath, scriptsDir, null);
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
{
r.Files.Add(dir, new ResourceFile()
{
IsDirectory = true,
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
});
}
var assemblies = new Dictionary<ResourceFile, Assembly>();
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
{
if (Path.GetFileName(file).CanBeIgnored())
{
try
{
File.Delete(file);
}
catch (Exception ex)
{
API.Logger.Warning($"Failed to delete API assembly: {file}. This may or may cause some unexpected behaviours.\n{ex}");
}
continue;
}
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
var rfile = new ResourceFile()
{
GetStream = () => { return new FileStream(file, FileMode.Open, FileAccess.Read); },
IsDirectory = false,
Name = relativeName
};
r.Files.Add(relativeName, rfile);
}
LoadedResources.TryAdd(r.Name, r);
}
private void SetupScripts()
{
foreach (var s in GetClientScripts())
{
try
{
API.Logger.Debug("Starting script: " + s.GetType().FullName);
var script = (ClientScript)s;
if (LoadedResources.TryGetValue(Directory.GetParent(script.Filename).Name, out var r))
{
script.CurrentResource = r;
}
else
{
API.Logger.Warning("Failed to locate resource for script: " + script.Filename);
}
var res = script.CurrentResource;
script.CurrentFile = res?.Files.Values.Where(x => x.Name.ToLower() == script.Filename.Substring(res.ScriptsDirectory.Length + 1).Replace('\\', '/')).FirstOrDefault();
res?.Scripts.Add(script);
script.OnStart();
}
catch (Exception ex)
{
API.Logger.Error($"Failed to start {s.GetType().FullName}", ex);
}
API.Logger.Debug("Started script: " + s.GetType().FullName);
}
}
private void StopScripts()
{
foreach (var s in GetClientScripts())
{
try
{
API.Logger.Debug("Stopping script: " + s.GetType().FullName);
((ClientScript)s).OnStop();
}
catch (Exception ex)
{
API.Logger.Error($"Failed to stop {s.GetType().FullName}", ex);
}
}
}
public static object[] GetClientScripts()
{
return ScriptDomain.CurrentDomain.RunningScripts.Where(x =>
x.ScriptInstance.GetType().IsScript(typeof(ClientScript))).Select(x => x.ScriptInstance).ToArray();
}
}
}

View File

@ -7,7 +7,7 @@ namespace RageCoop.Client
{ {
public RSA ServerRSA { get; set; } public RSA ServerRSA { get; set; }
public Aes ClientAes { get; set; } = Aes.Create(); public Aes ClientAes { get; set; } = Aes.Create();
private Logger Logger; private readonly Logger Logger;
public Security(Logger logger) public Security(Logger logger)
{ {
Logger = logger; Logger = logger;

View File

@ -36,7 +36,7 @@ namespace RageCoop.Client
/// LogLevel for RageCoop. /// LogLevel for RageCoop.
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error /// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary> /// </summary>
public int LogLevel = 2; public int LogLevel = 1;
/// <summary> /// <summary>
/// The key to open menu /// The key to open menu
@ -70,6 +70,11 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// The directory where log and resources downloaded from server will be placed. /// The directory where log and resources downloaded from server will be placed.
/// </summary> /// </summary>
public string DataDirectory { get; set; } = "Scripts\\RageCoop\\Data"; public string DataDirectory { get; set; } = "RageCoop\\Data";
/// <summary>
/// Show the owner name of the entity you're aiming at
/// </summary>
public bool ShowEntityOwnerName { get; set; } = false;
} }
} }

View File

@ -40,8 +40,10 @@ namespace RageCoop.Client
var flag = AnimationFlags.Loop; var flag = AnimationFlags.Loop;
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3)) if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
{ {
if (LoadAnim(animDict) == null) { return; }
MainPed.Task.ClearAll(); MainPed.Task.ClearAll();
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim(animDict), ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1); Function.Call(Hash.TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
} }
} }
} }
@ -116,10 +118,10 @@ namespace RageCoop.Client
case unchecked((uint)-1357824103): case unchecked((uint)-1357824103):
case unchecked((uint)-1074790547): case unchecked((uint)-1074790547):
case unchecked((uint)2132975508): case unchecked(2132975508):
case unchecked((uint)-2084633992): case unchecked((uint)-2084633992):
case unchecked((uint)-952879014): case unchecked((uint)-952879014):
case unchecked((uint)100416529): case unchecked(100416529):
case unchecked((uint)WeaponHash.Gusenberg): case unchecked((uint)WeaponHash.Gusenberg):
case unchecked((uint)WeaponHash.MG): case unchecked((uint)WeaponHash.MG):
case unchecked((uint)WeaponHash.CombatMG): case unchecked((uint)WeaponHash.CombatMG):
@ -143,18 +145,11 @@ namespace RageCoop.Client
private string LoadAnim(string anim) private string LoadAnim(string anim)
{ {
ulong startTime = Util.GetTickCount64(); if (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
while (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
{ {
Script.Yield();
Function.Call(Hash.REQUEST_ANIM_DICT, anim); Function.Call(Hash.REQUEST_ANIM_DICT, anim);
if (Util.GetTickCount64() - startTime >= 1000) return null;
{
break;
} }
}
return anim; return anim;
} }
} }

View File

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

View File

@ -15,7 +15,6 @@ namespace RageCoop.Client
/// </summary> /// </summary>
public partial class SyncedPed : SyncedEntity public partial class SyncedPed : SyncedEntity
{ {
#region CONSTRUCTORS
/// <summary> /// <summary>
/// Create a local entity (outgoing sync) /// Create a local entity (outgoing sync)
@ -43,79 +42,10 @@ namespace RageCoop.Client
ID = id; ID = id;
LastSynced = Main.Ticked; LastSynced = Main.Ticked;
} }
#endregion
internal Blip PedBlip = null;
internal BlipColor BlipColor = (BlipColor)255;
internal BlipSprite BlipSprite = 0;
internal float BlipScale = 1;
internal int VehicleID
{
get => CurrentVehicle?.ID ?? 0;
set
{
if (CurrentVehicle == null || value != CurrentVehicle?.ID)
{
CurrentVehicle=EntityPool.GetVehicleByID(value);
}
}
}
internal SyncedVehicle CurrentVehicle { get; private set; }
internal VehicleSeat Seat;
public bool IsPlayer { get => OwnerID == ID && ID != 0; }
public Ped MainPed { get; internal set; }
internal int Health { get; set; }
internal Vector3 HeadPosition { get; set; }
internal Vector3 RightFootPosition { get; set; }
internal Vector3 LeftFootPosition { get; set; }
internal byte WeaponTint { get; set; }
internal Vehicle _lastVehicle { get; set; }
internal int _lastVehicleID { get; set; }
private bool _lastRagdoll = false;
private ulong _lastRagdollTime = 0;
private bool _lastInCover = false;
private byte[] _lastClothes = null;
internal byte[] Clothes { get; set; }
internal float Heading { get; set; }
internal ulong LastSpeakingTime { get; set; } = 0;
internal bool IsSpeaking { get; set; } = false;
#region -- VARIABLES --
public byte Speed { get; set; }
private bool _lastIsJumping = false;
internal PedDataFlags Flags;
internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming);
internal bool _lastDriveBy;
internal bool IsReloading => Flags.HasPedFlag(PedDataFlags.IsReloading);
internal bool IsJumping => Flags.HasPedFlag(PedDataFlags.IsJumping);
internal bool IsRagdoll => Flags.HasPedFlag(PedDataFlags.IsRagdoll);
internal bool IsOnFire => Flags.HasPedFlag(PedDataFlags.IsOnFire);
internal bool IsInParachuteFreeFall => Flags.HasPedFlag(PedDataFlags.IsInParachuteFreeFall);
internal bool IsParachuteOpen => Flags.HasPedFlag(PedDataFlags.IsParachuteOpen);
internal bool IsOnLadder => Flags.HasPedFlag(PedDataFlags.IsOnLadder);
internal bool IsVaulting => Flags.HasPedFlag(PedDataFlags.IsVaulting);
internal bool IsInCover => Flags.HasPedFlag(PedDataFlags.IsInCover);
internal bool IsInLowCover => Flags.HasPedFlag(PedDataFlags.IsInLowCover);
internal bool IsInCoverFacingLeft => Flags.HasPedFlag(PedDataFlags.IsInCoverFacingLeft);
internal bool IsBlindFiring => Flags.HasPedFlag(PedDataFlags.IsBlindFiring);
internal bool IsInStealthMode => Flags.HasPedFlag(PedDataFlags.IsInStealthMode);
internal Prop ParachuteProp { get; set; } = null;
internal uint CurrentWeaponHash { get; set; }
private Dictionary<uint, bool> _lastWeaponComponents = null;
internal Dictionary<uint, bool> WeaponComponents { get; set; } = null;
private int _lastWeaponObj = 0;
#endregion
internal Vector3 AimCoords { get; set; }
private WeaponAsset WeaponAsset { get; set; }
internal override void Update() internal override void Update()
{ {
if (Owner==null) { return; } if (Owner == null) { OwnerID = OwnerID; return; }
if (IsPlayer) if (IsPlayer)
{ {
RenderNameTag(); RenderNameTag();
@ -155,11 +85,7 @@ namespace RageCoop.Client
{ {
PedBlip = MainPed.AddBlip(); PedBlip = MainPed.AddBlip();
if (IsPlayer)
{
// Main.Logger.Debug("blip:"+Player.Username);
Main.QueueAction(() => { PedBlip.Name=Owner.Username; });
}
PedBlip.Color = BlipColor; PedBlip.Color = BlipColor;
PedBlip.Sprite = BlipSprite; PedBlip.Sprite = BlipSprite;
PedBlip.Scale = BlipScale; PedBlip.Scale = BlipScale;
@ -174,12 +100,18 @@ namespace RageCoop.Client
{ {
PedBlip.Sprite = BlipSprite; PedBlip.Sprite = BlipSprite;
} }
if (IsPlayer)
{
PedBlip.Name = Owner.Username;
}
} }
if (!Clothes.SequenceEqual(_lastClothes)) if (!Clothes.SequenceEqual(_lastClothes))
{ {
SetClothes(); SetClothes();
} }
CheckCurrentWeapon();
} }
if (MainPed.IsDead) if (MainPed.IsDead)
@ -207,13 +139,14 @@ namespace RageCoop.Client
return; return;
} }
} }
if (Speed >= 4) if (Speed >= 4)
{ {
DisplayInVehicle(); DisplayInVehicle();
} }
else else
{ {
if (MainPed.IsInVehicle()) { MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut); } if (MainPed.IsInVehicle()) { MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut); return; }
DisplayOnFoot(); DisplayOnFoot();
} }
@ -340,13 +273,9 @@ namespace RageCoop.Client
} }
#region ONFOOT
private string[] _currentAnimation = new string[2] { "", "" };
private void DisplayOnFoot() private void DisplayOnFoot()
{ {
CheckCurrentWeapon();
if (IsInParachuteFreeFall) if (IsInParachuteFreeFall)
{ {
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f); MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
@ -354,7 +283,10 @@ namespace RageCoop.Client
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3)) if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
{ {
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, LoadAnim("skydive@base"), "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1); // Skip update if animation is not loaded
var dict = LoadAnim("skydive@base");
if (dict == null) { return; }
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
} }
return; return;
} }
@ -383,7 +315,9 @@ namespace RageCoop.Client
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3)) if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3))
{ {
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim("skydive@parachute@first_person"), "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1); var dict = LoadAnim("skydive@parachute@first_person");
if (dict == null) { return; }
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
} }
return; return;
@ -504,8 +438,6 @@ namespace RageCoop.Client
} }
return; return;
} }
else
{
if (MainPed.IsRagdoll) if (MainPed.IsRagdoll)
{ {
if (Speed == 0) if (Speed == 0)
@ -516,10 +448,8 @@ namespace RageCoop.Client
{ {
MainPed.Task.ClearAllImmediately(); MainPed.Task.ClearAllImmediately();
} }
return;
}
_lastRagdoll = false; _lastRagdoll = false;
return;
} }
if (IsReloading) if (IsReloading)
@ -579,17 +509,15 @@ namespace RageCoop.Client
} }
} }
#region WEAPON
private void CheckCurrentWeapon() private void CheckCurrentWeapon()
{ {
if (!WeaponAsset.IsLoaded) { WeaponAsset.Request(); } if (MainPed.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash || !WeaponComponents.Compare(_lastWeaponComponents) || (Speed <= 3 && _weaponObj?.IsVisible != true))
if (MainPed.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash || !WeaponComponents.Compare(_lastWeaponComponents))
{ {
if (WeaponAsset!=null) { WeaponAsset.MarkAsNoLongerNeeded(); } new WeaponAsset(CurrentWeaponHash).Request();
WeaponAsset=new WeaponAsset(CurrentWeaponHash);
MainPed.Weapons.RemoveAll();
_lastWeaponObj = Function.Call<int>(Hash.CREATE_WEAPON_OBJECT, CurrentWeaponHash, -1, Position.X, Position.Y, Position.Z, true, 0, 0);
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 (CurrentWeaponHash != (uint)WeaponHash.Unarmed)
{ {
if (WeaponComponents != null && WeaponComponents.Count != 0) if (WeaponComponents != null && WeaponComponents.Count != 0)
@ -598,11 +526,11 @@ namespace RageCoop.Client
{ {
if (comp.Value) if (comp.Value)
{ {
Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, _lastWeaponObj, comp.Key); Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, _weaponObj, comp.Key);
} }
} }
} }
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _lastWeaponObj, MainPed.Handle); Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _weaponObj, MainPed.Handle);
} }
_lastWeaponComponents = WeaponComponents; _lastWeaponComponents = WeaponComponents;
} }
@ -627,14 +555,12 @@ namespace RageCoop.Client
} }
SmoothTransition(); SmoothTransition();
} }
#endregion
private bool LastMoving;
private void WalkTo() private void WalkTo()
{ {
MainPed.Task.ClearAll(); MainPed.Task.ClearAll();
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0); Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
Vector3 predictPosition = Position + (Position - MainPed.ReadPosition()) + Velocity * 0.5f; Vector3 predictPosition = Predict(Position) + Velocity;
float range = predictPosition.DistanceToSquared(MainPed.ReadPosition()); float range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
switch (Speed) switch (Speed)
@ -684,19 +610,25 @@ namespace RageCoop.Client
private void SmoothTransition() private void SmoothTransition()
{ {
var localRagdoll = MainPed.IsRagdoll; var localRagdoll = MainPed.IsRagdoll;
var dist = Position.DistanceTo(MainPed.ReadPosition()); var predicted = Predict(Position);
if (dist>3) var dist = predicted.DistanceTo(MainPed.ReadPosition());
if (IsOff(dist))
{ {
MainPed.PositionNoOffset=Position; MainPed.PositionNoOffset = predicted;
return; return;
} }
if (!(localRagdoll || MainPed.IsDead)) if (!(localRagdoll || MainPed.IsDead))
{ {
if (!IsAiming && !MainPed.IsGettingUp) if (!IsAiming && !MainPed.IsGettingUp)
{ {
MainPed.Heading=Heading; var cur = MainPed.Heading;
var diff = Heading - cur;
if (diff > 180) { diff -= 360; }
else if (diff < -180) { diff += 360; }
MainPed.Heading = cur + diff / 2;
} }
MainPed.Velocity=Velocity+5*dist*(Position-MainPed.ReadPosition()); MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
} }
else if (Main.Ticked - _lastRagdollTime < 10) else if (Main.Ticked - _lastRagdollTime < 10)
{ {
@ -708,44 +640,49 @@ namespace RageCoop.Client
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];
Vector3 amount;
// 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);
if (amount.Length() > 50) { amount = amount.Normalized * 50; }
helper.EqualizeAmount = 1; helper.EqualizeAmount = 1;
helper.PartIndex = 20; helper.PartIndex = 20;
helper.Impulse=20*(HeadPosition-head.Position); helper.Impulse = amount;
helper.Start(); helper.Start();
helper.Stop(); helper.Stop();
amount = 20 * (Predict(RightFootPosition) - rightFoot.Position);
if (amount.Length() > 50) { amount = amount.Normalized * 50; }
helper.EqualizeAmount = 1; helper.EqualizeAmount = 1;
helper.PartIndex = 6; helper.PartIndex = 6;
helper.Impulse=20*(RightFootPosition-rightFoot.Position); helper.Impulse = amount;
helper.Start(); helper.Start();
helper.Stop(); helper.Stop();
amount = 20 * (Predict(LeftFootPosition) - leftFoot.Position);
if (amount.Length() > 50) { amount = amount.Normalized * 50; }
helper.EqualizeAmount = 1; helper.EqualizeAmount = 1;
helper.PartIndex = 3; helper.PartIndex = 3;
helper.Impulse=20*(LeftFootPosition-leftFoot.Position); helper.Impulse = amount;
helper.Start(); helper.Start();
helper.Stop(); helper.Stop();
} }
else else
{ {
MainPed.Velocity=Velocity+5*dist*(Position-MainPed.ReadPosition()); // localRagdoll
var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
if (force.Length() > 20) { force = force.Normalized * 20; }
MainPed.ApplyForce(force);
} }
} }
#endregion
private void DisplayInVehicle() private void DisplayInVehicle()
{ {
if (CurrentVehicle==null || CurrentVehicle.MainVehicle==null) { Main.Logger.Error("Veh not found"); return; } if (CurrentVehicle?.MainVehicle == null) { return; }
switch (Speed) switch (Speed)
{ {
case 4: case 4:
if (MainPed.CurrentVehicle!=CurrentVehicle.MainVehicle) if (MainPed.CurrentVehicle != CurrentVehicle.MainVehicle || MainPed.SeatIndex != Seat || (!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
{ {
MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat); MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
} }
@ -757,7 +694,6 @@ namespace RageCoop.Client
if (MainPed.VehicleWeapon == VehicleWeaponHash.Invalid) if (MainPed.VehicleWeapon == VehicleWeaponHash.Invalid)
{ {
// World.DrawMarker(MarkerType.DebugSphere,AimCoords,default,default,new Vector3(0.2f,0.2f,0.2f),Color.AliceBlue); // World.DrawMarker(MarkerType.DebugSphere,AimCoords,default,default,new Vector3(0.2f,0.2f,0.2f),Color.AliceBlue);
CheckCurrentWeapon();
if (IsAiming) if (IsAiming)
{ {
Function.Call(Hash.SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z); Function.Call(Hash.SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z);

View File

@ -36,6 +36,10 @@ namespace RageCoop.Client
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)
{
Owner.Character = ((SyncedPed)this);
}
} }
} }
@ -80,16 +84,33 @@ namespace RageCoop.Client
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
internal protected bool _lastFrozen = false; 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 = Main.Ticked + frames;
} }
protected Vector3 Predict(Vector3 input)
{
return (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Velocity + input;
}
private float _accumulatedOff = 0;
protected bool IsOff(float thisOff, float tolerance = 3, float limit = 30)
{
_accumulatedOff += thisOff - tolerance;
if (_accumulatedOff < 0) { _accumulatedOff = 0; }
else if (_accumulatedOff >= limit)
{
_accumulatedOff = 0;
return true;
}
return false;
}
} }
} }

View File

@ -1,7 +1,7 @@
using GTA; using GTA;
using GTA.Math; using GTA.Math;
using RageCoop.Core;
using GTA.Native; using GTA.Native;
using RageCoop.Core;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -19,12 +19,11 @@ namespace RageCoop.Client
public SyncedEntity Shooter { get; set; } public SyncedEntity Shooter { get; set; }
public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded); public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded);
internal override Player Owner => Shooter.Owner;
/// <summary> /// <summary>
/// Invalid property for projectile. /// Invalid property for projectile.
/// </summary> /// </summary>
private new int OwnerID { set { } } private new int OwnerID { set { } }
internal override Player Owner => Shooter.Owner;
public WeaponHash WeaponHash { get; set; } public WeaponHash WeaponHash { get; set; }
private WeaponAsset Asset { get; set; } private WeaponAsset Asset { get; set; }
public void ExtractData(ref Packets.ProjectileSync p) public void ExtractData(ref Packets.ProjectileSync p)
@ -101,7 +100,7 @@ namespace RageCoop.Client
CreateProjectile(); CreateProjectile();
return; return;
} }
MainProjectile.Velocity=Velocity+(Position+Shooter.Owner.PacketTravelTime*Velocity-MainProjectile.Position); MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position);
MainProjectile.Rotation = Rotation; MainProjectile.Rotation = Rotation;
LastUpdated = Main.Ticked; LastUpdated = Main.Ticked;
} }
@ -113,15 +112,14 @@ namespace RageCoop.Client
if (Shooter == null) { 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;
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,owner); 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);
var ps = World.GetAllProjectiles(); var ps = World.GetAllProjectiles();
MainProjectile = ps[ps.Length - 1]; MainProjectile = ps[ps.Length - 1];
MainProjectile.IsCollisionEnabled=false;
MainProjectile.Position = Position; MainProjectile.Position = Position;
MainProjectile.Rotation = Rotation; MainProjectile.Rotation = Rotation;
MainProjectile.Velocity = Velocity; MainProjectile.Velocity = Velocity;
Main.Delay(()=>MainProjectile.IsCollisionEnabled=true, 100);
EntityPool.Add(this); EntityPool.Add(this);
} }
} }

View File

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

View File

@ -1,15 +1,13 @@
using System;
using RageCoop.Core;
using GTA; using GTA;
using System.Diagnostics;
using System.Collections.Generic;
using GTA.Math; using GTA.Math;
using GTA.Native; using RageCoop.Core;
using System.Collections.Generic;
namespace RageCoop.Client{ namespace RageCoop.Client
public partial class SyncedVehicle{ {
public partial class SyncedVehicle
{
public Vehicle MainVehicle { get; internal set; } public Vehicle MainVehicle { get; internal set; }
public Stopwatch LastSyncedStopWatch = new Stopwatch();
#region -- SYNC DATA -- #region -- SYNC DATA --
@ -69,11 +67,9 @@ namespace RageCoop.Client{
private bool _lastHornActive = false; private bool _lastHornActive = false;
private bool _lastTransformed = false; private bool _lastTransformed = false;
internal int _lastLivery = -1; internal int _lastLivery = -1;
List<Vector3> _predictedTrace = new List<Vector3>(); private readonly List<Vector3> _predictedTrace = new List<Vector3>();
List<Vector3> _orgTrace = new List<Vector3>(); private readonly List<Vector3> _orgTrace = new List<Vector3>();
private Vector3 _predictedPosition; private Vector3 _predictedPosition;
float _elapsed;
#endregion #endregion
#region OUTGOING #region OUTGOING

View File

@ -4,8 +4,6 @@ using GTA.Native;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -31,7 +29,9 @@ namespace RageCoop.Client
SetUpFixedData(); SetUpFixedData();
} }
private void SetUpFixedData(){ internal void SetUpFixedData()
{
if (MainVehicle == null) { return; }
IsAircraft = MainVehicle.IsAircraft; IsAircraft = MainVehicle.IsAircraft;
IsMotorcycle = MainVehicle.IsMotorcycle; IsMotorcycle = MainVehicle.IsMotorcycle;
@ -85,6 +85,8 @@ namespace RageCoop.Client
} }
} }
DisplayVehicle();
// Skip update if no new sync message has arrived. // Skip update if no new sync message has arrived.
if (!NeedUpdate) if (!NeedUpdate)
{ {
@ -97,8 +99,6 @@ namespace RageCoop.Client
} }
MainVehicle.ThrottlePower = ThrottlePower; MainVehicle.ThrottlePower = ThrottlePower;
MainVehicle.BrakePower = BrakePower; MainVehicle.BrakePower = BrakePower;
var v = Main.P.CurrentVehicle;
DisplayVehicle(v != null && MainVehicle.IsTouching(v));
if (IsDead) if (IsDead)
{ {
@ -113,7 +113,7 @@ namespace RageCoop.Client
{ {
if (MainVehicle.IsDead) if (MainVehicle.IsDead)
{ {
Main.Delay(() => WorldThread.Delay(() =>
{ {
if (MainVehicle.IsDead && !IsDead) if (MainVehicle.IsDead && !IsDead)
{ {
@ -149,8 +149,6 @@ namespace RageCoop.Client
MainVehicle.AreHighBeamsOn = HighBeamsOn; MainVehicle.AreHighBeamsOn = HighBeamsOn;
} }
if (IsAircraft) if (IsAircraft)
{ {
if (LandingGear != (byte)MainVehicle.LandingGearState) if (LandingGear != (byte)MainVehicle.LandingGearState)
@ -184,10 +182,12 @@ namespace RageCoop.Client
MainVehicle.RoofState = RoofState; MainVehicle.RoofState = RoofState;
} }
if(HasRocketBoost && Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) != MainVehicle.IsRocketBoostActive()){ if (HasRocketBoost && Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) != MainVehicle.IsRocketBoostActive())
{
MainVehicle.SetRocketBoostActive(Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive)); MainVehicle.SetRocketBoostActive(Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive));
} }
if(HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) != MainVehicle.IsParachuteActive()){ if (HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) != MainVehicle.IsParachuteActive())
{
MainVehicle.SetParachuteActive(Flags.HasFlag(VehicleDataFlags.IsParachuteActive)); MainVehicle.SetParachuteActive(Flags.HasFlag(VehicleDataFlags.IsParachuteActive));
} }
if (IsSubmarineCar) if (IsSubmarineCar)
@ -206,22 +206,23 @@ namespace RageCoop.Client
Function.Call(Hash._TRANSFORM_SUBMARINE_TO_VEHICLE, MainVehicle.Handle, false); Function.Call(Hash._TRANSFORM_SUBMARINE_TO_VEHICLE, MainVehicle.Handle, false);
} }
} }
else if(IsDeluxo && IsDeluxoHovering!=MainVehicle.IsDeluxoHovering()){ else if (IsDeluxo)
{
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering); MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
if(IsDeluxoHovering){ if (IsDeluxoHovering)
{
MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio); MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
} }
} }
Function.Call(Hash.SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn); Function.Call(Hash.SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
MainVehicle.SetDamageModel(DamageModel);
} }
MainVehicle.LockStatus = LockStatus; MainVehicle.LockStatus = LockStatus;
if (LastFullSynced >= LastUpdated) if (LastFullSynced >= LastUpdated)
{ {
#region -- SYNC STATE --
if (Flags.HasVehFlag(VehicleDataFlags.Repaired)) if (Flags.HasVehFlag(VehicleDataFlags.Repaired))
{ {
MainVehicle.Repair(); MainVehicle.Repair();
@ -255,14 +256,14 @@ namespace RageCoop.Client
Function.Call(Hash.SET_VEHICLE_LIVERY, MainVehicle, Livery); Function.Call(Hash.SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery = Livery; _lastLivery = Livery;
} }
#endregion MainVehicle.SetDamageModel(DamageModel);
} }
LastUpdated = Main.Ticked; LastUpdated = Main.Ticked;
} }
void DisplayVehicle(bool touching)
private void DisplayVehicle()
{ {
_elapsed = Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds; _predictedPosition = Predict(Position);
_predictedPosition = Position + _elapsed * Velocity;
var current = MainVehicle.ReadPosition(); var current = MainVehicle.ReadPosition();
var dist = current.DistanceTo(_predictedPosition); var dist = current.DistanceTo(_predictedPosition);
var cali = dist * (_predictedPosition - current); var cali = dist * (_predictedPosition - current);
@ -274,41 +275,20 @@ namespace RageCoop.Client
MainVehicle.Quaternion = Quaternion; MainVehicle.Quaternion = Quaternion;
return; return;
} }
if (dist > 0.03)
{
MainVehicle.Velocity = Velocity + cali; MainVehicle.Velocity = Velocity + cali;
}
if (IsFlipped) Vector3 calirot;
if (IsFlipped || (calirot = GetCalibrationRotation()).Length() > 50)
{ {
MainVehicle.Quaternion = Quaternion.Slerp(MainVehicle.ReadQuaternion(), Quaternion, 0.5f); MainVehicle.Quaternion = Quaternion.Slerp(MainVehicle.ReadQuaternion(), Quaternion, 0.5f);
MainVehicle.RotationVelocity = RotationVelocity; MainVehicle.RotationVelocity = RotationVelocity;
return; return;
} }
Vector3 calirot = GetCalibrationRotation();
if (calirot.Length() < 50)
{
MainVehicle.RotationVelocity = RotationVelocity + calirot * 0.2f; MainVehicle.RotationVelocity = RotationVelocity + calirot * 0.2f;
} }
else
{
MainVehicle.Quaternion = Quaternion;
MainVehicle.RotationVelocity = RotationVelocity;
}
#if DEBUG_VEH
if (_orgTrace.Count >= 30)
{
_orgTrace.RemoveAt(0);
}
if (_predictedTrace.Count >= 30)
{
_predictedTrace.RemoveAt(0);
}
_orgTrace.Add(Position);
_predictedTrace.Add(_predictedPos);
#endif
}
private Vector3 GetCalibrationRotation() private Vector3 GetCalibrationRotation()
{ {
var rot = Quaternion.LookRotation(Quaternion * Vector3.RelativeFront, Quaternion * Vector3.RelativeTop).ToEulerAngles(); var rot = Quaternion.LookRotation(Quaternion * Vector3.RelativeFront, Quaternion * Vector3.RelativeTop).ToEulerAngles();
@ -334,7 +314,7 @@ namespace RageCoop.Client
// GTA.UI.Notification.Show($"~r~(Vehicle)Model ({CurrentVehicleModelHash}) cannot be loaded!"); // GTA.UI.Notification.Show($"~r~(Vehicle)Model ({CurrentVehicleModelHash}) cannot be loaded!");
return false; return false;
} }
else if (MainVehicle==null) if (MainVehicle == null)
{ {
Model.Request(); Model.Request();
return false; return false;
@ -393,7 +373,5 @@ namespace RageCoop.Client
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast)); MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
} }
#endregion #endregion
} }
} }

View File

@ -1,18 +1,17 @@
using GTA; using GTA;
using GTA.Native; using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting; using RageCoop.Client.Scripting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using Lidgren.Network;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal class EntityPool internal class EntityPool
{ {
public static object PedsLock = new object(); public static object PedsLock = new object();
public static int CharactersCount { get { return PedsByID.Count; } }
#if BENCHMARK #if BENCHMARK
private static Stopwatch PerfCounter=new Stopwatch(); private static Stopwatch PerfCounter=new Stopwatch();
private static Stopwatch PerfCounter2=Stopwatch.StartNew(); private static Stopwatch PerfCounter2=Stopwatch.StartNew();
@ -44,10 +43,10 @@ namespace RageCoop.Client
#endregion #endregion
public static void Cleanup(bool keepPlayer = true, bool keepMine = true) public static void Cleanup(bool keepPlayer = true, bool keepMine = true)
{ {
foreach (int id in new List<int>(PedsByID.Keys)) foreach (var ped in PedsByID.Values)
{ {
if (keepPlayer && (id==Main.LocalPlayerID)|| keepMine && (PedsByID[id].OwnerID == Main.LocalPlayerID)) { continue; } if ((keepPlayer && (ped.ID == Main.LocalPlayerID)) || (keepMine && (ped.OwnerID == Main.LocalPlayerID))) { continue; }
RemovePed(id); RemovePed(ped.ID);
} }
PedsByID.Clear(); PedsByID.Clear();
PedsByHandle.Clear(); PedsByHandle.Clear();
@ -93,6 +92,8 @@ namespace RageCoop.Client
public static bool AddPlayer() public static bool AddPlayer()
{ {
Ped p = Game.Player.Character; Ped p = Game.Player.Character;
// var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
// Function.Call(Hash.SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
SyncedPed player = GetPedByID(Main.LocalPlayerID); SyncedPed player = GetPedByID(Main.LocalPlayerID);
if (player == null) if (player == null)
{ {
@ -287,11 +288,11 @@ namespace RageCoop.Client
public static bool VehicleExists(int id) => VehiclesByID.ContainsKey(id); public static bool VehicleExists(int id) => VehiclesByID.ContainsKey(id);
public static bool ProjectileExists(int id) => ProjectilesByID.ContainsKey(id); public static bool ProjectileExists(int id) => ProjectilesByID.ContainsKey(id);
#endregion #endregion
static int vehStateIndex; private static int vehStateIndex;
static int pedStateIndex; private static int pedStateIndex;
static int vehStatesPerFrame; private static int vehStatesPerFrame;
static int pedStatesPerFrame; private static int pedStatesPerFrame;
static int i; private static int i;
public static Ped[] allPeds = new Ped[0]; public static Ped[] allPeds = new Ped[0];
public static Vehicle[] allVehicles = new Vehicle[0]; public static Vehicle[] allVehicles = new Vehicle[0];
public static Projectile[] allProjectiles = new Projectile[0]; public static Projectile[] allProjectiles = new Projectile[0];
@ -308,17 +309,6 @@ namespace RageCoop.Client
allProjectiles = World.GetAllProjectiles(); allProjectiles = World.GetAllProjectiles();
vehStatesPerFrame = allVehicles.Length * 2 / (int)Game.FPS + 1; vehStatesPerFrame = allVehicles.Length * 2 / (int)Game.FPS + 1;
pedStatesPerFrame = allPeds.Length * 2 / (int)Game.FPS + 1; pedStatesPerFrame = allPeds.Length * 2 / (int)Game.FPS + 1;
/*
if (Main.Ticked%50==0)
{
bool flag1 = allVehicles.Length>Main.Settings.WorldVehicleSoftLimit && Main.Settings.WorldVehicleSoftLimit>-1;
bool flag2 = allPeds.Length>Main.Settings.WorldPedSoftLimit && Main.Settings.WorldPedSoftLimit>-1;
if ((flag1||flag2) && _trafficSpawning)
{ SetBudget(0); _trafficSpawning=false; }
else if(!_trafficSpawning)
{ SetBudget(1); _trafficSpawning=true; }
}
*/
#if BENCHMARK #if BENCHMARK
Debug.TimeStamps[TimeStamp.GetAllEntities]=PerfCounter.ElapsedTicks; Debug.TimeStamps[TimeStamp.GetAllEntities]=PerfCounter.ElapsedTicks;
@ -343,7 +333,7 @@ namespace RageCoop.Client
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.Position.DistanceTo(p.Origin)<2) if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank || (p.MainProjectile.OwnerEntity?.EntityType == EntityType.Vehicle && p.MainProjectile.Position.DistanceTo(p.Origin) < 2))
{ {
continue; continue;
} }
@ -368,14 +358,14 @@ namespace RageCoop.Client
lock (PedsLock) lock (PedsLock)
{ {
EntityPool.AddPlayer(); AddPlayer();
foreach (Ped p in allPeds) foreach (Ped p in allPeds)
{ {
SyncedPed c = EntityPool.GetPedByHandle(p.Handle); SyncedPed c = GetPedByHandle(p.Handle);
if (c == null && (p != Game.Player.Character)) if (c == null && (p != Game.Player.Character))
{ {
if (allPeds.Length>Main.Settings.WorldPedSoftLimit && p.PopulationType != EntityPopulationType.RandomAmbient) if (allPeds.Length > Main.Settings.WorldPedSoftLimit && p.PopulationType == EntityPopulationType.RandomAmbient)
{ {
p.Delete(); p.Delete();
continue; continue;
@ -383,7 +373,7 @@ namespace RageCoop.Client
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}"); // Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c = new SyncedPed(p); c = new SyncedPed(p);
EntityPool.Add(c); Add(c);
} }
} }
#if BENCHMARK #if BENCHMARK
@ -402,7 +392,7 @@ namespace RageCoop.Client
i++; i++;
if ((c.MainPed != null) && (!c.MainPed.Exists())) if ((c.MainPed != null) && (!c.MainPed.Exists()))
{ {
EntityPool.RemovePed(c.ID, "non-existent"); RemovePed(c.ID, "non-existent");
continue; continue;
} }
@ -439,9 +429,8 @@ namespace RageCoop.Client
Debug.TimeStamps[TimeStamp.PedTotal]=PerfCounter.ElapsedTicks; Debug.TimeStamps[TimeStamp.PedTotal]=PerfCounter.ElapsedTicks;
#endif #endif
} }
var check = Main.Ticked % 100 == 0;
i = -1; i = -1;
lock (VehiclesLock) lock (VehiclesLock)
{ {
foreach (Vehicle veh in allVehicles) foreach (Vehicle veh in allVehicles)
@ -456,10 +445,10 @@ namespace RageCoop.Client
foreach (var p in veh.Occupants) foreach (var p in veh.Occupants)
{ {
p.Delete(); p.Delete();
var c = EntityPool.GetPedByHandle(p.Handle); var c = GetPedByHandle(p.Handle);
if (c != null) if (c != null)
{ {
EntityPool.RemovePed(c.ID, "ThrottleTraffic"); RemovePed(c.ID, "ThrottleTraffic");
} }
} }
veh.Delete(); veh.Delete();
@ -486,10 +475,13 @@ namespace RageCoop.Client
i++; i++;
if ((v.MainVehicle != null) && (!v.MainVehicle.Exists())) if ((v.MainVehicle != null) && (!v.MainVehicle.Exists()))
{ {
EntityPool.RemoveVehicle(v.ID, "non-existent"); RemoveVehicle(v.ID, "non-existent");
continue; continue;
} }
if (check)
{
v.SetUpFixedData();
}
// Outgoing sync // Outgoing sync
if (v.IsLocal) if (v.IsLocal)
{ {
@ -514,7 +506,8 @@ namespace RageCoop.Client
} }
Networking.Peer.FlushSendQueue(); Networking.Peer.FlushSendQueue();
} }
static void UpdateTargets()
private static void UpdateTargets()
{ {
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())
@ -579,21 +572,21 @@ namespace RageCoop.Client
{ {
public static void Add(SyncedVehicle v) public static void Add(SyncedVehicle v)
{ {
lock (EntityPool.VehiclesLock) lock (VehiclesLock)
{ {
EntityPool.Add(v); EntityPool.Add(v);
} }
} }
public static void Add(SyncedPed p) public static void Add(SyncedPed p)
{ {
lock (EntityPool.PedsLock) lock (PedsLock)
{ {
EntityPool.Add(p); EntityPool.Add(p);
} }
} }
public static void Add(SyncedProjectile sp) public static void Add(SyncedProjectile sp)
{ {
lock (EntityPool.ProjectilesLock) lock (ProjectilesLock)
{ {
EntityPool.Add(sp); EntityPool.Add(sp);
} }

View File

@ -1,15 +1,14 @@
using GTA; using GTA;
using GTA.Math; using GTA.Math;
using Lidgren.Network;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Threading;
using System.Threading.Tasks;
using Lidgren.Network;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static class SyncEvents internal static class SyncEvents
{ {
#region TRIGGER #region TRIGGER
public static void TriggerPedKilled(SyncedPed victim) public static void TriggerPedKilled(SyncedPed victim)
{ {
@ -73,9 +72,8 @@ namespace RageCoop.Client
#region HANDLE #region HANDLE
public static ParticleEffectAsset CorePFXAsset = new ParticleEffectAsset("core"); public static ParticleEffectAsset CorePFXAsset = new ParticleEffectAsset("core");
private static WeaponAsset _weaponAsset = default;
static WeaponAsset _weaponAsset = default; private static uint _lastWeaponHash;
static uint _lastWeaponHash;
private static void HandlePedKilled(Packets.PedKilled p) private static void HandlePedKilled(Packets.PedKilled p)
{ {
@ -162,44 +160,39 @@ namespace RageCoop.Client
WeaponUtil.GetFlashFX((WeaponHash)p.WeaponHash), WeaponUtil.GetFlashFX((WeaponHash)p.WeaponHash),
b.Position, b.ForwardVector.ToEulerRotation(v.Bones[35].UpVector), 1); b.Position, b.ForwardVector.ToEulerRotation(v.Bones[35].UpVector), 1);
} }
public static void HandleEvent(PacketType type, byte[] data) public static void HandleEvent(PacketType type, NetIncomingMessage msg)
{ {
switch (type) switch (type)
{ {
case PacketType.BulletShot: case PacketType.BulletShot:
{ {
Packets.BulletShot p = new Packets.BulletShot(); Packets.BulletShot p = new Packets.BulletShot();
p.Deserialize(data); p.Deserialize(msg);
HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID); HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID);
break; break;
} }
case PacketType.VehicleBulletShot: case PacketType.VehicleBulletShot:
{ {
HandleVehicleBulletShot(data.GetPacket<Packets.VehicleBulletShot>()); HandleVehicleBulletShot(msg.GetPacket<Packets.VehicleBulletShot>());
break; break;
} }
case PacketType.OwnerChanged: case PacketType.OwnerChanged:
{ {
Packets.OwnerChanged packet = new Packets.OwnerChanged(); HandleOwnerChanged(msg.GetPacket<Packets.OwnerChanged>());
packet.Deserialize(data);
HandleOwnerChanged(packet);
} }
break; break;
case PacketType.PedKilled: case PacketType.PedKilled:
{ {
var packet = new Packets.PedKilled(); HandlePedKilled(msg.GetPacket<Packets.PedKilled>());
packet.Deserialize(data);
HandlePedKilled(packet);
} }
break; break;
case PacketType.NozzleTransform: case PacketType.NozzleTransform:
{ {
var packet = new Packets.NozzleTransform(); HandleNozzleTransform(msg.GetPacket<Packets.NozzleTransform>());
packet.Deserialize(data);
HandleNozzleTransform(packet);
break; break;
} }
} }
Networking.Peer.Recycle(msg);
} }
#endregion #endregion
@ -264,7 +257,7 @@ namespace RageCoop.Client
if (!getBulletImpact()) if (!getBulletImpact())
{ {
Main.QueueAction(getBulletImpact); Scripting.API.QueueAction(getBulletImpact);
} }
} }
else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default) else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default)

View File

@ -1,6 +1,5 @@
using System.Threading; using NAudio.Wave;
using System.Threading;
using NAudio.Wave;
namespace RageCoop.Client namespace RageCoop.Client
{ {

View File

@ -0,0 +1,59 @@
using GTA;
namespace RageCoop.Client
{
/// <summary>
/// Class providing support for addon mods
/// </summary>
internal class AddOnDataProvider
{
public static int GetMuzzleIndex(Model model)
{
switch (model.Hash)
{
// f14a2
case -848721350:
return 48;
// f15e
case 881261972:
return 32;
// f16c
case -2051171080:
return 25;
// F22A
case 2061630439:
return 14;
// f35c
case -343547392:
return 44;
// mig29a
case 513887552:
return 18;
// su30sm
case -733985185:
return 34;
// su33
case -722216722:
return 34;
// su35s
case -268602544:
return 28;
// su57
case 1490050781:
return 21;
default:
return -1;
}
}
}
}

View File

@ -1,18 +1,19 @@
 
using GTA;
using GTA.Math;
using RageCoop.Core;
using SHVDN;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GTA.Math;
using GTA;
using SHVDN;
using RageCoop.Core;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal unsafe class MemPatch{ internal unsafe class MemPatch
private byte[] _data; {
private byte[] _orginal; private readonly byte[] _data;
private IntPtr _address; private readonly byte[] _orginal;
private readonly IntPtr _address;
public MemPatch(byte* address, byte[] data) public MemPatch(byte* address, byte[] data)
{ {
_data = data; _data = data;
@ -68,8 +69,8 @@ internal static unsafe class Memory
public const int MatrixOffset = 96; public const int MatrixOffset = 96;
#endregion #endregion
#region OPCODE #region OPCODE
const byte XOR_32_64 = 0x31; private const byte XOR_32_64 = 0x31;
const byte RET = 0xC3; private const byte RET = 0xC3;
#endregion #endregion
public static Vector3 ReadPosition(this Entity e) => ReadVector3(e.MemoryAddress + PositionOffset); public static Vector3 ReadPosition(this Entity e) => ReadVector3(e.MemoryAddress + PositionOffset);
public static Quaternion ReadQuaternion(this Entity e) => Quaternion.RotationMatrix(e.Matrix); public static Quaternion ReadQuaternion(this Entity e) => Quaternion.RotationMatrix(e.Matrix);

View File

@ -0,0 +1,145 @@
using GTA.Math;
using GTA.Native;
using System;
using System.Runtime.InteropServices;
namespace RageCoop.Client
{
[StructLayout(LayoutKind.Explicit, Size = 80)]
public struct HeadBlendData
{
[FieldOffset(0)]
public int ShapeFirst;
[FieldOffset(8)]
public int ShapeSecond;
[FieldOffset(16)]
public int ShapeThird;
[FieldOffset(24)]
public int SkinFirst;
[FieldOffset(32)]
public int SkinSecond;
[FieldOffset(40)]
public int SkinThird;
[FieldOffset(48)]
public float ShapeMix;
[FieldOffset(56)]
public float SkinMix;
[FieldOffset(64)]
public float ThirdMix;
}
[StructLayout(LayoutKind.Explicit, Size = 24)]
public struct NativeVector3
{
[FieldOffset(0)]
public float X;
[FieldOffset(8)]
public float Y;
[FieldOffset(16)]
public float Z;
public static implicit operator Vector3(NativeVector3 vec)
{
return new Vector3() { X = vec.X, Y = vec.Y, Z = vec.Z };
}
public static implicit operator NativeVector3(Vector3 vec)
{
return new NativeVector3() { X = vec.X, Y = vec.Y, Z = vec.Z };
}
}
public static class NativeCaller
{
// These are borrowed from ScriptHookVDotNet's
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeInit@@YAX_K@Z")]
private static extern void NativeInit(ulong hash);
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativePush64@@YAX_K@Z")]
private static extern void NativePush64(ulong val);
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeCall@@YAPEA_KXZ")]
private static extern unsafe ulong* NativeCall();
// These are from ScriptHookV's nativeCaller.h
private static unsafe void NativePush<T>(T val) where T : unmanaged
{
ulong val64 = 0;
*(T*)(&val64) = val;
NativePush64(val64);
}
public static unsafe R Invoke<R>(ulong hash) where R : unmanaged
{
NativeInit(hash);
return *(R*)(NativeCall());
}
public static unsafe R Invoke<R>(Hash hash, params object[] args)
where R : unmanaged
{
NativeInit((ulong)hash);
var arguments = ConvertPrimitiveArguments(args);
foreach (var arg in arguments)
NativePush(arg);
return *(R*)(NativeCall());
}
/// <summary>
/// Helper function that converts an array of primitive values to a native stack.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private static unsafe ulong[] ConvertPrimitiveArguments(object[] args)
{
var result = new ulong[args.Length];
for (int i = 0; i < args.Length; ++i)
{
if (args[i] is bool valueBool)
{
result[i] = valueBool ? 1ul : 0ul;
continue;
}
if (args[i] is byte valueByte)
{
result[i] = valueByte;
continue;
}
if (args[i] is int valueInt32)
{
result[i] = (ulong)valueInt32;
continue;
}
if (args[i] is ulong valueUInt64)
{
result[i] = valueUInt64;
continue;
}
if (args[i] is float valueFloat)
{
result[i] = *(ulong*)&valueFloat;
continue;
}
if (args[i] is IntPtr valueIntPtr)
{
result[i] = (ulong)valueIntPtr.ToInt64();
continue;
}
throw new ArgumentException("Unknown primitive type in native argument list", nameof(args));
}
return result;
}
}
}

View File

@ -2,8 +2,8 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using RageCoop.Core; using RageCoop.Core;
using System.Collections.Generic;
using System; using System;
using System.Collections.Generic;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -146,7 +146,8 @@ namespace RageCoop.Client
{ {
flags |= PedDataFlags.IsInCover; flags |= PedDataFlags.IsInCover;
} }
if (!Function.Call<bool>(Hash.IS_PED_IN_HIGH_COVER, ped)){ if (!Function.Call<bool>(Hash.IS_PED_IN_HIGH_COVER, ped))
{
flags |= PedDataFlags.IsInLowCover; flags |= PedDataFlags.IsInLowCover;
} }
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire)) if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire))
@ -155,6 +156,11 @@ namespace RageCoop.Client
} }
} }
if (ped.IsInvincible)
{
flags |= PedDataFlags.IsInvincible;
}
if (Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped)) if (Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped))
{ {
flags |= PedDataFlags.IsInStealthMode; flags |= PedDataFlags.IsInStealthMode;

View File

@ -1,22 +1,32 @@
using GTA; using GTA;
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using Newtonsoft.Json;
using RageCoop.Core; using RageCoop.Core;
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Collections.Generic;
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
namespace RageCoop.Client namespace RageCoop.Client
{ {
internal static class Util internal static class Util
{ {
public static void StartUpCheck()
{
if (AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext") == null)
{
var error = $"Client not loaded with loader, please re-install using the installer to fix this issue";
try
{
GTA.UI.Notification.Show("~r~" + error);
}
catch { }
throw new Exception(error);
}
}
public static SizeF ResolutionMaintainRatio public static SizeF ResolutionMaintainRatio
{ {
get get
@ -111,53 +121,40 @@ namespace RageCoop.Client
#endregion #endregion
public static string SettingsPath = "Scripts\\RageCoop\\Data\\RageCoop.Client.Settings.xml"; public static string SettingsPath = "RageCoop\\Settings.json";
public static Settings ReadSettings() public static Settings ReadSettings(string path = null)
{ {
XmlSerializer ser = new XmlSerializer(typeof(Settings)); path = path ?? SettingsPath;
string path = SettingsPath;
Directory.CreateDirectory(Directory.GetParent(path).FullName); Directory.CreateDirectory(Directory.GetParent(path).FullName);
Settings settings = null; Settings settings;
try
if (File.Exists(path))
{ {
using (FileStream stream = File.OpenRead(path)) settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));
{
settings = (RageCoop.Client.Settings)ser.Deserialize(stream);
} }
catch (Exception ex)
using (FileStream stream = new FileStream(path, FileMode.Truncate, FileAccess.ReadWrite))
{ {
ser.Serialize(stream, settings); Main.Logger?.Error(ex);
} File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented));
}
else
{
using (FileStream stream = File.OpenWrite(path))
{
ser.Serialize(stream, settings = new Settings());
}
} }
return settings; return settings;
} }
public static void SaveSettings() public static bool SaveSettings(string path = null, Settings settings = null)
{ {
try try
{ {
string path = SettingsPath; path = path ?? SettingsPath;
settings = settings ?? Main.Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName); Directory.CreateDirectory(Directory.GetParent(path).FullName);
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite)) File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
{ return true;
XmlSerializer ser = new XmlSerializer(typeof(Settings));
ser.Serialize(stream, Main.Settings);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
GTA.UI.Notification.Show("Error saving player settings: " + ex.Message); Main.Logger?.Error(ex);
return false;
} }
} }
@ -217,62 +214,8 @@ namespace RageCoop.Client
Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index); Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index);
} }
#region WIN32
const UInt32 WM_KEYDOWN = 0x0100;
public static void Reload()
{
string reloadKey = "None";
var lines = File.ReadAllLines("ScriptHookVDotNet.ini");
foreach (var l in lines)
{
var ss = l.Split('=');
if (ss.Length > 0 && ss[0]=="ReloadKey")
{
reloadKey = ss[1];
}
}
var lineList = lines.ToList();
if (reloadKey=="None")
{
foreach (var l in lines)
{
var ss = l.Split('=');
if (ss.Length > 0 && ss[0]=="ReloadKey")
{
reloadKey = ss[1];
lineList.Remove(l);
}
}
lineList.Add("ReloadKey=Insert");
File.WriteAllLines("ScriptHookVDotNet.ini", lineList.ToArray());
GTA.UI.Notification.Show("Reload cannot be performed automatically, please type \"Reload()\" manually in the SHVDN console.");
}
Keys key = (Keys)Enum.Parse(typeof(Keys), reloadKey, true);
// Move log file so it doesn't get deleted
Main.Logger.Dispose();
var path = Main.Logger.LogPath+".last.log";
try
{
if (File.Exists(path)) { File.Delete(path); }
if (File.Exists(Main.Logger.LogPath)) { File.Move(Main.Logger.LogPath, path); }
}
catch (Exception ex)
{
GTA.UI.Notification.Show(ex.Message);
}
PostMessage(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, WM_KEYDOWN, (int)key, 0);
}
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
public static extern ulong GetTickCount64(); public static extern ulong GetTickCount64();
#endregion
} }
} }

View File

@ -54,38 +54,45 @@ namespace RageCoop.Client
{ {
flags |= VehicleDataFlags.IsTransformed; flags |= VehicleDataFlags.IsTransformed;
} }
if (v.IsAircraft) if (v.IsAircraft)
{ {
flags |= VehicleDataFlags.IsAircraft; flags |= VehicleDataFlags.IsAircraft;
} }
if (v.IsDeluxo && veh.IsDeluxoHovering()) if (v.IsDeluxo && veh.IsDeluxoHovering())
{ {
flags |= VehicleDataFlags.IsDeluxoHovering; flags |= VehicleDataFlags.IsDeluxoHovering;
} }
if (v.HasRoof) if (v.HasRoof)
{ {
flags |= VehicleDataFlags.HasRoof; flags |= VehicleDataFlags.HasRoof;
} }
if (v.HasRocketBoost && veh.IsRocketBoostActive()) if (v.HasRocketBoost && veh.IsRocketBoostActive())
{ {
flags |= VehicleDataFlags.IsRocketBoostActive; flags |= VehicleDataFlags.IsRocketBoostActive;
} }
if(v.HasParachute && veh.IsParachuteActive()){
if (v.HasParachute && veh.IsParachuteActive())
{
flags |= VehicleDataFlags.IsParachuteActive; flags |= VehicleDataFlags.IsParachuteActive;
} }
if (veh.IsOnFire) if (veh.IsOnFire)
{ {
flags |= VehicleDataFlags.IsOnFire; flags |= VehicleDataFlags.IsOnFire;
} }
return flags; return flags;
} }
public static bool IsRocketBoostActive(this Vehicle veh) public static bool IsRocketBoostActive(this Vehicle veh)
{ {
return Function.Call<bool>(Hash._IS_VEHICLE_ROCKET_BOOST_ACTIVE, veh); return Function.Call<bool>(Hash._IS_VEHICLE_ROCKET_BOOST_ACTIVE, veh);
} }
public static bool IsParachuteActive(this Vehicle veh){ public static bool IsParachuteActive(this Vehicle veh)
{
return Function.Call<bool>((Hash)0x3DE51E9C80B116CF, veh); return Function.Call<bool>((Hash)0x3DE51E9C80B116CF, veh);
} }
public static void SetRocketBoostActive(this Vehicle veh, bool toggle) public static void SetRocketBoostActive(this Vehicle veh, bool toggle)
@ -133,7 +140,6 @@ namespace RageCoop.Client
} }
} }
// Bursted tires // Bursted tires
short burstedTires = 0; short burstedTires = 0;
foreach (VehicleWheel wheel in veh.Wheels.GetAllWheels()) foreach (VehicleWheel wheel in veh.Wheels.GetAllWheels())
@ -228,13 +234,12 @@ namespace RageCoop.Client
{ {
if (p.IsSittingInVehicle()) if (p.IsSittingInVehicle())
{ {
ps.Add((int)p.SeatIndex, (int)p.GetSyncEntity().ID); ps.Add((int)p.SeatIndex, p.GetSyncEntity().ID);
} }
} }
return ps; return ps;
} }
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover) public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{ {
Function.Call(Hash._SET_VEHICLE_HOVER_TRANSFORM_PERCENTAGE, deluxo, hover ? 1f : 0f); Function.Call(Hash._SET_VEHICLE_HOVER_TRANSFORM_PERCENTAGE, deluxo, hover ? 1f : 0f);
@ -283,8 +288,6 @@ namespace RageCoop.Client
{ {
Function.Call(Hash.SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio); Function.Call(Hash.SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
} }
#endregion #endregion
} }
} }

View File

@ -2,7 +2,6 @@
using GTA.Math; using GTA.Math;
using GTA.Native; using GTA.Native;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml;
namespace RageCoop.Client namespace RageCoop.Client
{ {
@ -46,7 +45,8 @@ namespace RageCoop.Client
} }
return p.Bones[Bone.SkelRightHand].Position; return p.Bones[Bone.SkelRightHand].Position;
} }
static long BulletsShot = 0;
private static long BulletsShot = 0;
public static float GetWeaponDamage(this Ped P, uint hash) public static float GetWeaponDamage(this Ped P, uint hash)
{ {
@ -362,7 +362,7 @@ namespace RageCoop.Client
return 30; return 30;
default: default:
return -1; return AddOnDataProvider.GetMuzzleIndex(v.Model.Hash);
} }
} }
public static bool IsUsingProjectileWeapon(this Ped p) public static bool IsUsingProjectileWeapon(this Ped p)
@ -371,18 +371,12 @@ namespace RageCoop.Client
var type = Function.Call<int>(Hash.GET_WEAPON_DAMAGE_TYPE, vp); var type = Function.Call<int>(Hash.GET_WEAPON_DAMAGE_TYPE, vp);
if (vp != VehicleWeaponHash.Invalid) if (vp != VehicleWeaponHash.Invalid)
{ {
if (type==3) return type == 3 ? false : VehicleProjectileWeapons.Contains(vp) || (type == 5 && !ExplosiveBullets.Contains((uint)vp));
{
return false;
} }
return VehicleProjectileWeapons.Contains(vp) || (type==5 && !ExplosiveBullets.Contains((uint)vp));
}
else
{
var w = p.Weapons.Current; var w = p.Weapons.Current;
return w.Group == WeaponGroup.Thrown || ProjectileWeapons.Contains(w.Hash); return w.Group == WeaponGroup.Thrown || ProjectileWeapons.Contains(w.Hash);
} }
}
public static readonly HashSet<uint> ExplosiveBullets = new HashSet<uint> public static readonly HashSet<uint> ExplosiveBullets = new HashSet<uint>
{ {
@ -460,7 +454,6 @@ namespace RageCoop.Client
}; };
public static readonly HashSet<WeaponHash> ProjectileWeapons = new HashSet<WeaponHash> { public static readonly HashSet<WeaponHash> ProjectileWeapons = new HashSet<WeaponHash> {
WeaponHash.HomingLauncher, WeaponHash.HomingLauncher,
WeaponHash.RPG, WeaponHash.RPG,

View File

@ -1,61 +1,80 @@
using GTA; using GTA;
using GTA.Native; using GTA.Native;
using System; using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace RageCoop.Client namespace RageCoop.Client
{ {
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
public class WorldThread : Script [ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class WorldThread : Script
{ {
private static bool _lastDisableTraffic = false; public static Script Instance;
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
/// <summary> /// <summary>
/// Don't use it! /// Don't use it!
/// </summary> /// </summary>
public WorldThread() public WorldThread()
{ {
Util.StartUpCheck();
Instance = this;
Tick += OnTick; Tick += OnTick;
Aborted += (sender, e) => Aborted += (sender, e) =>
{ {
if (_lastDisableTraffic) ChangeTraffic(true);
{
Traffic(true);
}
}; };
} }
private static bool _trafficEnabled;
private void OnTick(object sender, EventArgs e) private void OnTick(object sender, EventArgs e)
{ {
DoQueuedActions();
if (Game.IsLoading || !Networking.IsOnServer) if (Game.IsLoading || !Networking.IsOnServer)
{ {
return; return;
} }
Game.DisableControlThisFrame(Control.FrontendPause); Game.DisableControlThisFrame(Control.FrontendPause);
Game.DisableControlThisFrame(Control.VehicleExit);
Game.DisableControlThisFrame(Control.Enter);
if (Main.Settings.DisableAlternatePause) if (Main.Settings.DisableAlternatePause)
{ {
Game.DisableControlThisFrame(Control.FrontendPauseAlternate); Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
} }
var P = Game.Player.Character;
// Sets a value that determines how aggressive the ocean waves will be. // 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. // Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
Function.Call(Hash.SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player Function.Call(Hash.SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
// Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, Game.Player.Character.Handle, true, false); if (Main.Settings.ShowEntityOwnerName)
if (Main.Settings==null) { return; }
if (Main.Settings.DisableTraffic)
{ {
if (!_lastDisableTraffic) unsafe
{ {
Traffic(false); int handle;
if (Function.Call<bool>(Hash.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";
}
GTA.UI.Screen.ShowHelpTextThisFrame("Entity owner: " + owner);
} }
}
}
}
if (!_trafficEnabled)
{
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0); Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0); Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f); Function.Call(Hash.SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
@ -64,15 +83,13 @@ namespace RageCoop.Client
Function.Call(Hash.SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME); Function.Call(Hash.SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
Function.Call(Hash.SUPPRESS_AGITATION_EVENTS_NEXT_FRAME); Function.Call(Hash.SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
} }
else if (_lastDisableTraffic)
{
Traffic(true);
} }
_lastDisableTraffic = Main.Settings.DisableTraffic;
}
public static void Traffic(bool enable) public static void Traffic(bool enable)
{
ChangeTraffic(enable);
_trafficEnabled = enable;
}
private static void ChangeTraffic(bool enable)
{ {
if (enable) if (enable)
{ {
@ -81,15 +98,15 @@ namespace RageCoop.Client
Function.Call(Hash.SET_RANDOM_TRAINS, true); Function.Call(Hash.SET_RANDOM_TRAINS, true);
Function.Call(Hash.SET_RANDOM_BOATS, true); Function.Call(Hash.SET_RANDOM_BOATS, true);
Function.Call(Hash.SET_GARBAGE_TRUCKS, true); Function.Call(Hash.SET_GARBAGE_TRUCKS, true);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 1); // 0 - 3 Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3); // 0 - 3
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 1); // 0 - 3 Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
Function.Call(Hash.SET_ALL_VEHICLE_GENERATORS_ACTIVE); Function.Call(Hash.SET_ALL_VEHICLE_GENERATORS_ACTIVE);
Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true); Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, -1); Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, -1);
Function.Call(Hash.SET_DISTANT_CARS_ENABLED, true); Function.Call(Hash.SET_DISTANT_CARS_ENABLED, true);
Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, false); Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, false);
} }
else else if (Networking.IsOnServer)
{ {
Function.Call(Hash.ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f, 1000.0f, 0, 1, 1, 1); Function.Call(Hash.ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f, 1000.0f, 0, 1, 1, 1);
Function.Call(Hash.SET_CREATE_RANDOM_COPS, false); Function.Call(Hash.SET_CREATE_RANDOM_COPS, false);
@ -97,34 +114,31 @@ namespace RageCoop.Client
Function.Call(Hash.SET_RANDOM_BOATS, false); Function.Call(Hash.SET_RANDOM_BOATS, false);
Function.Call(Hash.SET_GARBAGE_TRUCKS, false); Function.Call(Hash.SET_GARBAGE_TRUCKS, false);
Function.Call(Hash.DELETE_ALL_TRAINS); Function.Call(Hash.DELETE_ALL_TRAINS);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3); Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 3); Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false); Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Function.Call(Hash.SET_FAR_DRAW_VEHICLES, false); Function.Call(Hash.SET_FAR_DRAW_VEHICLES, false);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, 0); Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, 0);
Function.Call(Hash.SET_DISTANT_CARS_ENABLED, false); Function.Call(Hash.SET_DISTANT_CARS_ENABLED, false);
Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, true); Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, true);
foreach (Ped ped in World.GetAllPeds()) foreach (Ped ped in World.GetAllPeds())
{ {
if (ped == Game.Player.Character) { continue; }
SyncedPed c = EntityPool.GetPedByHandle(ped.Handle); SyncedPed c = EntityPool.GetPedByHandle(ped.Handle);
if ((c == null) || (c.IsLocal && (ped.Handle != Game.Player.Character.Handle) && ped.PopulationType != EntityPopulationType.Mission)) if ((c == null) || (c.IsLocal && (ped.Handle != Game.Player.Character.Handle) && ped.PopulationType != EntityPopulationType.Mission))
{ {
if (ped.Handle==Game.Player.Character.Handle) { continue; }
// Main.Logger.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic"); Main.Logger.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
ped.CurrentVehicle?.Delete(); ped.CurrentVehicle?.Delete();
ped.Kill(); ped.Kill();
ped.Delete(); ped.Delete();
} }
} }
foreach (Vehicle veh in World.GetAllVehicles()) foreach (Vehicle veh in World.GetAllVehicles())
{ {
SyncedVehicle v = veh.GetSyncEntity(); SyncedVehicle v = veh.GetSyncEntity();
if (v.MainVehicle==Game.Player.LastVehicle) if (v.MainVehicle == Game.Player.LastVehicle || v.MainVehicle == Game.Player.Character.CurrentVehicle)
{ {
// Don't delete player's vehicle // Don't delete player's vehicle
continue; continue;
@ -138,5 +152,61 @@ namespace RageCoop.Client
} }
} }
} }
public static void Delay(Action a, int time)
{
Task.Run(() =>
{
Thread.Sleep(time);
QueueAction(a);
});
}
internal static void DoQueuedActions()
{
lock (QueuedActions)
{
foreach (var action in QueuedActions.ToArray())
{
try
{
if (action())
{
QueuedActions.Remove(action);
}
}
catch (Exception ex)
{
Main.Logger.Error(ex);
QueuedActions.Remove(action);
}
}
}
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
internal static void QueueAction(Func<bool> a)
{
lock (QueuedActions)
{
QueuedActions.Add(a);
}
}
internal static void QueueAction(Action a)
{
lock (QueuedActions)
{
QueuedActions.Add(() => { a(); return true; });
}
}
/// <summary>
/// Clears all queued actions
/// </summary>
internal static void ClearQueuedActions()
{
lock (QueuedActions) { QueuedActions.Clear(); }
}
} }
} }

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

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Costura.Fody" version="5.7.0" targetFramework="net48" developmentDependency="true" />
<package id="Fody" version="6.6.3" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.Extensions.ObjectPool" version="6.0.8" targetFramework="net48" /> <package id="Microsoft.Extensions.ObjectPool" version="6.0.8" targetFramework="net48" />
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net48" /> <package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net48" />
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net48" /> <package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net48" />
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" /> <package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" />
<package id="NAudio" version="2.1.0" targetFramework="net481" /> <package id="NAudio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Asio" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Asio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Core" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Core" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Midi" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Midi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net481" /> <package id="NAudio.Wasapi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net481" /> <package id="NAudio.WinForms" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net481" /> <package id="NAudio.WinMM" version="2.1.0" targetFramework="net48" />
<package id="NETStandard.Library" version="1.6.1" targetFramework="net48" /> <package id="NETStandard.Library" version="1.6.1" targetFramework="net48" />
<package id="SharpZipLib" version="1.3.3" targetFramework="net48" /> <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
<package id="SharpZipLib" version="1.4.0" targetFramework="net48" />
<package id="System.AppContext" version="4.3.0" targetFramework="net48" /> <package id="System.AppContext" version="4.3.0" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections" version="4.3.0" targetFramework="net48" /> <package id="System.Collections" version="4.3.0" targetFramework="net48" />
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net48" /> <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net48" />
<package id="System.Console" version="4.3.0" targetFramework="net48" /> <package id="System.Console" version="4.3.0" targetFramework="net48" />
@ -32,15 +32,18 @@
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net48" /> <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Linq" version="4.3.0" targetFramework="net48" /> <package id="System.Linq" version="4.3.0" targetFramework="net48" />
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net48" /> <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.4" targetFramework="net48" />
<package id="System.Net.Http" version="4.3.0" targetFramework="net48" /> <package id="System.Net.Http" version="4.3.0" targetFramework="net48" />
<package id="System.Net.Primitives" version="4.3.0" targetFramework="net48" /> <package id="System.Net.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net48" /> <package id="System.Net.Sockets" version="4.3.0" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.ObjectModel" version="4.3.0" targetFramework="net48" /> <package id="System.ObjectModel" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection" version="4.3.0" targetFramework="net48" /> <package id="System.Reflection" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net48" /> <package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net48" /> <package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net48" /> <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime" version="4.3.0" targetFramework="net48" /> <package id="System.Runtime" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net48" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net48" /> <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.Handles" version="4.3.0" targetFramework="net48" /> <package id="System.Runtime.Handles" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net48" /> <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net48" />
@ -57,6 +60,7 @@
<package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net48" /> <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net48" />
<package id="System.Threading" version="4.3.0" targetFramework="net48" /> <package id="System.Threading" version="4.3.0" targetFramework="net48" />
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net48" /> <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.2" targetFramework="net48" />
<package id="System.Threading.Timer" version="4.3.0" targetFramework="net48" /> <package id="System.Threading.Timer" version="4.3.0" targetFramework="net48" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net48" /> <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net48" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" /> <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" />

View File

@ -1,8 +1,6 @@
using System; using GTA.Math;
using System.Text;
using System.Linq;
using GTA.Math;
using System.IO; using System.IO;
using System.Text;
namespace RageCoop.Core namespace RageCoop.Core
{ {

View File

@ -1,73 +1,113 @@
using System; using GTA.Math;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA.Math;
using System.Security.Cryptography;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.IO;
using System.Runtime.CompilerServices;
using Lidgren.Network; using Lidgren.Network;
using Newtonsoft.Json; using Newtonsoft.Json;
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
[assembly: InternalsVisibleTo("RageCoop.Server")] [assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")] [assembly: InternalsVisibleTo("RageCoop.Client")]
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
[assembly: InternalsVisibleTo("RageCoop.ResourceBuilder")]
namespace RageCoop.Core namespace RageCoop.Core
{ {
internal static class CoreUtils internal static class CoreUtils
{ {
private static readonly HashSet<string> ToIgnore = new HashSet<string>() private static readonly HashSet<string> ToIgnore = new HashSet<string>()
{ {
"RageCoop.Client.dll", "RageCoop.Client",
"RageCoop.Core.dll", "RageCoop.Client.Loader",
"RageCoop.Server.dll", "RageCoop.Client.Installer",
"ScriptHookVDotNet3.dll", "RageCoop.Core",
"ScriptHookVDotNet.dll" "RageCoop.Server",
"ScriptHookVDotNet2",
"ScriptHookVDotNet3",
"ScriptHookVDotNet"
}; };
public static void GetDependencies(Assembly assembly, ref HashSet<string> existing)
{
if (assembly.FullName.StartsWith("System")) { return; }
foreach(var name in assembly.GetReferencedAssemblies())
{
if (name.FullName.StartsWith("System")) { continue; }
try
{
var asm = Assembly.Load(name);
GetDependencies(asm,ref existing);
}
catch { }
}
if (!existing.Contains(assembly.FullName))
{
Console.WriteLine(assembly.FullName);
existing.Add(assembly.FullName);
}
}
public static Version GetLatestVersion(string branch = "dev-nightly")
{
var url = $"https://raw.githubusercontent.com/RAGECOOP/RAGECOOP-V/{branch}/RageCoop.Server/Properties/AssemblyInfo.cs";
var versionLine = HttpHelper.DownloadString(url).Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.Contains("[assembly: AssemblyVersion(")).First();
var start = versionLine.IndexOf('\"') + 1;
var end = versionLine.LastIndexOf('\"');
return Version.Parse(versionLine.Substring(start, end - start));
}
public static bool CanBeIgnored(this string name) public static bool CanBeIgnored(this string name)
{ {
return ToIgnore.Contains(name); return ToIgnore.Contains(Path.GetFileNameWithoutExtension(name));
} }
public static (byte, byte[]) GetBytesFromObject(object obj) public static string ToFullPath(this string path)
{
return Path.GetFullPath(path);
}
public static void GetBytesFromObject(object obj, NetOutgoingMessage m)
{ {
switch (obj) switch (obj)
{ {
case byte _: case byte value:
return (0x01, new byte[] { (byte)obj }); m.Write((byte)0x01); m.Write(value); break;
case short _: case short value:
return (0x02, BitConverter.GetBytes((short)obj)); m.Write((byte)0x02); m.Write(value); break;
case ushort _: case ushort value:
return (0x03, BitConverter.GetBytes((ushort)obj)); m.Write((byte)0x03); m.Write(value); break;
case int _: case int value:
return (0x04, BitConverter.GetBytes((int)obj)); m.Write((byte)0x04); m.Write(value); break;
case uint _: case uint value:
return (0x05, BitConverter.GetBytes((uint)obj)); m.Write((byte)0x05); m.Write(value); break;
case long _: case long value:
return (0x06, BitConverter.GetBytes((long)obj)); m.Write((byte)0x06); m.Write(value); break;
case ulong _: case ulong value:
return (0x07, BitConverter.GetBytes((ulong)obj)); m.Write((byte)0x07); m.Write(value); break;
case float _: case float value:
return (0x08, BitConverter.GetBytes((float)obj)); m.Write((byte)0x08); m.Write(value); break;
case bool _: case bool value:
return (0x09, BitConverter.GetBytes((bool)obj)); m.Write((byte)0x09); m.Write(value); break;
case string _: case string value:
return (0x10, ((string)obj).GetBytesWithLength()); m.Write((byte)0x10); m.Write(value); break;
case Vector3 _: case Vector3 value:
return (0x11,((Vector3)obj).GetBytes()); m.Write((byte)0x11); m.Write(value); break;
case Quaternion _: case Quaternion value:
return (0x12, ((Quaternion)obj).GetBytes()); m.Write((byte)0x12); m.Write(value); break;
case GTA.Model _: case GTA.Model value:
return (0x13, BitConverter.GetBytes((GTA.Model)obj)); m.Write((byte)0x13); m.Write(value); break;
case Vector2 _: case Vector2 value:
return (0x14, ((Vector2)obj).GetBytes()); m.Write((byte)0x14); m.Write(value); break;
case Tuple<byte, byte[]> _: case byte[] value:
var tup = (Tuple<byte, byte[]>)obj; m.Write((byte)0x15); m.WriteByteArray(value); break;
return (tup.Item1, tup.Item2); case Tuple<byte, byte[]> value:
m.Write(value.Item1); m.Write(value.Item2); break;
default: default:
return (0x0, null); throw new Exception("Unsupported object type: " + obj.GetType());
} }
} }
public static IPEndPoint StringToEndPoint(string endpointstring) public static IPEndPoint StringToEndPoint(string endpointstring)
@ -134,9 +174,8 @@ namespace RageCoop.Core
private static int getPort(string p) private static int getPort(string p)
{ {
int port;
if (!int.TryParse(p, out port) if (!int.TryParse(p, out int port)
|| port < IPEndPoint.MinPort || port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort) || port > IPEndPoint.MaxPort)
{ {
@ -181,10 +220,54 @@ namespace RageCoop.Core
return JsonConvert.DeserializeObject<IpInfo>(content); return JsonConvert.DeserializeObject<IpInfo>(content);
} }
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
foreach (DirectoryInfo dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (FileInfo file in source.GetFiles())
file.CopyTo(Path.Combine(target.FullName, file.Name), true);
} }
internal struct IpInfo public static string GetInvariantRID()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "win-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "linux-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "osx-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
}
return "unknown";
}
/// <summary>
/// Get local ip addresses on all network interfaces
/// </summary>
/// <returns></returns>
public static List<IPAddress> GetLocalAddress()
{
var addresses = new List<IPAddress>();
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
{
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
{
addresses.Add(addr.Address);
}
}
return addresses;
}
public static StreamWriter OpenWriter(string path, FileMode mode = FileMode.Create, FileAccess access = FileAccess.Write, FileShare share = FileShare.ReadWrite)
{
return new StreamWriter(File.Open(path, mode, access, share));
}
}
internal class IpInfo
{ {
[JsonProperty("ip")] [JsonProperty("ip")]
public string Address { get; set; } public string Address { get; set; }
@ -194,74 +277,10 @@ namespace RageCoop.Core
} }
internal static class Extensions internal static class Extensions
{ {
public static void AddVector3(this List<byte> bytes, Vector3 vec3)
{
bytes.AddRange(BitConverter.GetBytes(vec3.X));
bytes.AddRange(BitConverter.GetBytes(vec3.Y));
bytes.AddRange(BitConverter.GetBytes(vec3.Z));
}
public static void AddQuaternion(this List<byte> bytes, Quaternion quat)
{
bytes.AddRange(BitConverter.GetBytes(quat.X));
bytes.AddRange(BitConverter.GetBytes(quat.Y));
bytes.AddRange(BitConverter.GetBytes(quat.Z));
bytes.AddRange(BitConverter.GetBytes(quat.W));
}
public static void AddInt(this List<byte> bytes,int i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUint(this List<byte> bytes, uint i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddShort(this List<byte> bytes, short i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUshort(this List<byte> bytes, ushort i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddLong(this List<byte> bytes, long i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUlong(this List<byte> bytes, ulong i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddFloat(this List<byte> bytes, float i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddBool(this List<byte> bytes, bool b)
{
bytes.Add(b? (byte)1 :(byte)0);
}
public static void AddString(this List<byte> bytes, string s)
{
var sb = Encoding.UTF8.GetBytes(s);
bytes.AddInt(sb.Length);
bytes.AddRange(sb);
}
public static void AddArray(this List<byte> bytes, byte[] toadd)
{
bytes.AddInt(toadd.Length);
bytes.AddRange(toadd);
}
public static byte[] GetBytes(this string s) public static byte[] GetBytes(this string s)
{ {
return Encoding.UTF8.GetBytes(s); return Encoding.UTF8.GetBytes(s);
} }
public static byte[] GetBytesWithLength(this string s)
{
var data = new List<byte>(100);
var sb = Encoding.UTF8.GetBytes(s);
data.AddInt(sb.Length);
data.AddRange(sb);
return data.ToArray();
}
public static string GetString(this byte[] data) public static string GetString(this byte[] data)
{ {
return Encoding.UTF8.GetString(data); return Encoding.UTF8.GetString(data);
@ -288,35 +307,33 @@ namespace RageCoop.Core
// 16 bytes // 16 bytes
return new List<byte[]>() { BitConverter.GetBytes(qua.X), BitConverter.GetBytes(qua.Y), BitConverter.GetBytes(qua.Z), BitConverter.GetBytes(qua.W) }.Join(4); return new List<byte[]>() { BitConverter.GetBytes(qua.X), BitConverter.GetBytes(qua.Y), BitConverter.GetBytes(qua.Z), BitConverter.GetBytes(qua.W) }.Join(4);
} }
public static T GetPacket<T>(this NetIncomingMessage msg) where T : Packet, new()
public static T GetPacket<T>(this NetIncomingMessage msg, T existingPacket = null) where T : Packet, new()
{ {
msg.ReadByte(); var p = new T();
return GetPacket<T>(msg.ReadBytes(msg.ReadInt32()),existingPacket); p.Deserialize(msg);
}
public static T GetPacket<T>(this byte[] data, T existingPacket=null) where T : Packet, new()
{
var p = existingPacket??new T();
p.Deserialize(data);
return p; return p;
} }
public static bool HasPedFlag(this PedDataFlags flagToCheck, PedDataFlags flag) public static bool HasPedFlag(this PedDataFlags flags, PedDataFlags flag)
{ {
return (flagToCheck & flag)!=0; return (flags & flag) != 0;
} }
public static bool HasProjDataFlag(this ProjectileDataFlags flagToCheck, ProjectileDataFlags flag) public static bool HasProjDataFlag(this ProjectileDataFlags flags, ProjectileDataFlags flag)
{ {
return (flagToCheck & flag)!=0; return (flags & flag) != 0;
} }
public static bool HasVehFlag(this VehicleDataFlags flagToCheck, VehicleDataFlags flag) public static bool HasVehFlag(this VehicleDataFlags flags, VehicleDataFlags flag)
{ {
return (flagToCheck & flag)!=0; return (flags & flag) != 0;
} }
public static bool HasConfigFlag(this PlayerConfigFlags flagToCheck, PlayerConfigFlags flag) public static bool HasConfigFlag(this PlayerConfigFlags flags, PlayerConfigFlags flag)
{ {
return (flagToCheck & flag)!=0; return (flags & flag) != 0;
}
public static bool HasEventFlag(this CustomEventFlags flags, CustomEventFlags flag)
{
return (flags & flag) != 0;
} }
public static Type GetActualType(this TypeCode code) public static Type GetActualType(this TypeCode code)
{ {
@ -391,7 +408,7 @@ namespace RageCoop.Core
} }
public static string Dump<T>(this IEnumerable<T> objects) public static string Dump<T>(this IEnumerable<T> objects)
{ {
return "{"+string.Join(",",objects)+"}"; return $"{{{string.Join(",", objects)}}}";
} }
public static void ForEach<T>(this IEnumerable<T> objects, Action<T> action) public static void ForEach<T>(this IEnumerable<T> objects, Action<T> action)
{ {
@ -411,6 +428,12 @@ namespace RageCoop.Core
return memoryStream.ToArray(); return memoryStream.ToArray();
} }
} }
public static MemoryStream ToMemStream(this Stream stream)
{
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream;
}
public static byte[] Join(this List<byte[]> arrays, int lengthPerArray = -1) public static byte[] Join(this List<byte[]> arrays, int lengthPerArray = -1)
{ {
if (arrays.Count == 1) { return arrays[0]; } if (arrays.Count == 1) { return arrays[0]; }
@ -424,13 +447,11 @@ namespace RageCoop.Core
return output; return output;
} }
public static bool IsSubclassOf(this Type type, string baseTypeName) public static bool IsScript(this Type type, Type scriptType)
{ {
for (Type t = type.BaseType; t != null; t = t.BaseType) return !type.IsAbstract && type.IsSubclassOf(scriptType);
if (t.FullName == baseTypeName)
return true;
return false;
} }
} }
/// <summary> /// <summary>
@ -468,5 +489,6 @@ namespace RageCoop.Core
{ {
return IPAddress.Parse(ip); return IPAddress.Parse(ip);
} }
} }
} }

176
Core/Logger.cs Normal file
View File

@ -0,0 +1,176 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace RageCoop.Core
{
public enum LogLevel
{
Trace = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4
}
/// <summary>
///
/// </summary>
public class Logger : MarshalByRefObject, IDisposable
{
public class LogLine
{
internal LogLine() { }
public DateTime TimeStamp;
public LogLevel LogLevel;
public string Message;
}
/// <summary>
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 0;
/// <summary>
/// Name of this logger
/// </summary>
public string Name = "Logger";
public readonly string DateTimeFormat = "HH:mm:ss";
/// <summary>
/// Whether to use UTC time for timestamping the log
/// </summary>
public readonly bool UseUtc = false;
public List<StreamWriter> Writers = new List<StreamWriter> { new StreamWriter(Console.OpenStandardOutput()) };
public int FlushInterval = 1000;
public event FlushDelegate OnFlush;
public bool FlushImmediately = false;
public delegate void FlushDelegate(LogLine line, string fomatted);
private readonly Thread LoggerThread;
private bool Stopping = false;
private readonly ConcurrentQueue<LogLine> _queuedLines = new ConcurrentQueue<LogLine>();
internal Logger()
{
Name = Process.GetCurrentProcess().Id.ToString();
if (!FlushImmediately)
{
LoggerThread = new Thread(() =>
{
while (!Stopping)
{
Flush();
Thread.Sleep(1000);
}
Flush();
});
LoggerThread.Start();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Info(string message)
{
Enqueue(2, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Warning(string message)
{
Enqueue(3, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Error(string message)
{
Enqueue(4, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="error"></param>
public void Error(string message, Exception error)
{
Enqueue(4, $"{message}:\n {error}");
}
/// <summary>
///
/// </summary>
/// <param name="ex"></param>
public void Error(Exception ex)
{
Enqueue(4, ex.ToString());
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Debug(string message)
{
Enqueue(1, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Trace(string message)
{
Enqueue(0, message);
}
public void Enqueue(int level, string message)
{
if (level < LogLevel) { return; }
_queuedLines.Enqueue(new LogLine()
{
Message = message,
TimeStamp = UseUtc ? DateTime.UtcNow : DateTime.Now,
LogLevel = (LogLevel)level
});
if (FlushImmediately)
{
Flush();
}
}
private string Format(LogLine line)
{
return string.Format("[{0}][{2}] [{3}] {1}", line.TimeStamp.ToString(DateTimeFormat), line.Message, Name, line.LogLevel.ToString());
}
/// <summary>
///
/// </summary>
public void Flush()
{
lock (_queuedLines)
{
try
{
while (_queuedLines.TryDequeue(out var line))
{
var formatted = Format(line);
Writers.ForEach(x => { x.WriteLine(formatted); x.Flush(); });
OnFlush?.Invoke(line, formatted);
}
}
catch { }
}
}
/// <summary>
/// Stop backdround thread and flush all pending messages.
/// </summary>
public void Dispose()
{
Stopping = true;
LoggerThread?.Join();
}
}
}

View File

@ -1,9 +1,8 @@
using System; using GTA.Math;
using GTA.Math; using System;
namespace RageCoop.Core namespace RageCoop.Core
{ {
internal static class MathExtensions internal static class MathExtensions
{ {
public const float Deg2Rad = (float)(Math.PI * 2) / 360; public const float Deg2Rad = (float)(Math.PI * 2) / 360;
@ -82,14 +81,14 @@ namespace RageCoop.Core
vect = vect.ToRadians(); vect = vect.ToRadians();
float rollOver2 = vect.Z * 0.5f; float rollOver2 = vect.Z * 0.5f;
float sinRollOver2 = (float)Math.Sin((double)rollOver2); float sinRollOver2 = (float)Math.Sin(rollOver2);
float cosRollOver2 = (float)Math.Cos((double)rollOver2); float cosRollOver2 = (float)Math.Cos(rollOver2);
float pitchOver2 = vect.Y * 0.5f; float pitchOver2 = vect.Y * 0.5f;
float sinPitchOver2 = (float)Math.Sin((double)pitchOver2); float sinPitchOver2 = (float)Math.Sin(pitchOver2);
float cosPitchOver2 = (float)Math.Cos((double)pitchOver2); float cosPitchOver2 = (float)Math.Cos(pitchOver2);
float yawOver2 = vect.X * 0.5f; // pitch float yawOver2 = vect.X * 0.5f; // pitch
float sinYawOver2 = (float)Math.Sin((double)yawOver2); float sinYawOver2 = (float)Math.Sin(yawOver2);
float cosYawOver2 = (float)Math.Cos((double)yawOver2); float cosYawOver2 = (float)Math.Cos(yawOver2);
Quaternion result = new Quaternion() Quaternion result = new Quaternion()
{ {
X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2, X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2,
@ -146,16 +145,12 @@ namespace RageCoop.Core
} }
private static float CopySign(double x, double y) private static float CopySign(double x, double y)
{ {
bool isPositive = y>=0; if (y >= 0)
if (isPositive)
{ {
if (x>=0) { return (float)x; } else { return (float)-x; } return x >= 0 ? (float)x : (float)-x;
} }
else
{
if (x>=0) { return (float)-x; } else { return (float)x; }
} return x >= 0 ? (float)-x : (float)x;
} }
public static double AngelTo(this Vector3 v1, Vector3 v2) public static double AngelTo(this Vector3 v1, Vector3 v2)
{ {
@ -163,7 +158,6 @@ namespace RageCoop.Core
} }
public static float GetCosTheta(this Vector3 v1, Vector3 v2) public static float GetCosTheta(this Vector3 v1, Vector3 v2)
{ {
return Vector3.Dot(v1, v2) / (v1.Length() * v2.Length()); return Vector3.Dot(v1, v2) / (v1.Length() * v2.Length());
} }

View File

@ -1,7 +1,6 @@
using System; using Lidgren.Network;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
using System.Threading; using System.Threading;
namespace RageCoop.Core namespace RageCoop.Core
@ -57,6 +56,5 @@ namespace RageCoop.Core
p.Pack(outgoingMessage); p.Pack(outgoingMessage);
SendMessage(outgoingMessage, cons, method, (int)channel); SendMessage(outgoingMessage, cons, method, (int)channel);
} }
} }
} }

View File

@ -0,0 +1,42 @@
using System;
using System.IO;
using System.Net;
using System.Threading;
namespace RageCoop.Core
{
internal static class HttpHelper
{
public static void DownloadFile(string url, string destination, Action<int> progressCallback)
{
if (File.Exists(destination)) { File.Delete(destination); }
AutoResetEvent ae = new AutoResetEvent(false);
WebClient client = new WebClient();
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
client.DownloadProgressChanged += (s, e1) => progressCallback?.Invoke(e1.ProgressPercentage);
client.DownloadFileCompleted += (s, e2) =>
{
ae.Set();
};
client.DownloadFileAsync(new Uri(url), destination);
ae.WaitOne();
}
public static string DownloadString(string url)
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new WebClient();
return client.DownloadString(url);
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace RageCoop.Core
{
internal class PublicKey
{
public PublicKey()
{
}
public static PublicKey FromServerInfo(ServerInfo info)
{
return new PublicKey
{
Modulus = Convert.FromBase64String(info.publicKeyModulus),
Exponent = Convert.FromBase64String(info.publicKeyExponent)
};
}
public byte[] Modulus;
public byte[] Exponent;
}
}

View File

@ -1,13 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace RageCoop.Core namespace RageCoop.Core
{ {
/// <summary>
internal class ServerInfo /// A json object representing a server's information as annouced to master server.
/// </summary>
[Serializable]
public class ServerInfo
{ {
#pragma warning disable 1591
public string address { get; set; } public string address { get; set; }
public string port { get; set; } public string port { get; set; }
public string name { get; set; } public string name { get; set; }

View File

@ -1,11 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;
using System.Net;
using Newtonsoft.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace RageCoop.Core namespace RageCoop.Core
@ -93,7 +91,8 @@ namespace RageCoop.Core
} }
public static Dictionary<string, ZeroTierNetwork> Networks public static Dictionary<string, ZeroTierNetwork> Networks
{ {
get { get
{
Dictionary<string, ZeroTierNetwork> networks = new Dictionary<string, ZeroTierNetwork>(); Dictionary<string, ZeroTierNetwork> networks = new Dictionary<string, ZeroTierNetwork>();
var p = Run("listnetworks"); var p = Run("listnetworks");
var lines = Regex.Split(p.StandardOutput.ReadToEnd(), "\n").Skip(1); var lines = Regex.Split(p.StandardOutput.ReadToEnd(), "\n").Skip(1);
@ -131,5 +130,9 @@ namespace RageCoop.Core
var p = Run(command); var p = Run(command);
return p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd(); return p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
} }
public static void Check()
{
}
} }
} }

68
Core/PacketExtensions.cs Normal file
View File

@ -0,0 +1,68 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal static class PacketExtensions
{
#region MESSAGE-READ
public static Vector3 ReadVector3(this NetIncomingMessage m)
{
return new Vector3
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
Z = m.ReadFloat(),
};
}
public static Vector2 ReadVector2(this NetIncomingMessage m)
{
return new Vector2
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
};
}
public static Quaternion ReadQuaternion(this NetIncomingMessage m)
{
return new Quaternion
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
Z = m.ReadFloat(),
W = m.ReadFloat(),
};
}
public static byte[] ReadByteArray(this NetIncomingMessage m)
{
return m.ReadBytes(m.ReadInt32());
}
#endregion
#region MESSAGE-WRITE
public static void Write(this NetOutgoingMessage m, Vector3 v)
{
m.Write(v.X);
m.Write(v.Y);
m.Write(v.Z);
}
public static void Write(this NetOutgoingMessage m, Quaternion q)
{
m.Write(q.X);
m.Write(q.Y);
m.Write(q.Z);
m.Write(q.W);
}
public static void WriteByteArray(this NetOutgoingMessage m, byte[] b)
{
m.Write(b.Length);
m.Write(b);
}
#endregion
internal static bool IsSyncEvent(this PacketType p)
{
return (30 <= (byte)p) && ((byte)p <= 40);
}
}
}

View File

@ -1,6 +1,5 @@
using System; using Lidgren.Network;
using System.Collections.Generic; using System;
using Lidgren.Network;
namespace RageCoop.Core namespace RageCoop.Core
{ {
@ -11,8 +10,8 @@ namespace RageCoop.Core
internal class ChatMessage : Packet internal class ChatMessage : Packet
{ {
public override PacketType Type => PacketType.ChatMessage; public override PacketType Type => PacketType.ChatMessage;
private Func<string, byte[]> crypt; private readonly Func<string, byte[]> crypt;
private Func<byte[], byte[]> decrypt; private readonly Func<byte[], byte[]> decrypt;
public ChatMessage(Func<string, byte[]> crypter) public ChatMessage(Func<string, byte[]> crypter)
{ {
crypt = crypter; crypt = crypter;
@ -25,33 +24,31 @@ namespace RageCoop.Core
public string Message { get; set; } public string Message { get; set; }
public override byte[] Serialize() protected override void Serialize(NetOutgoingMessage m)
{ {
List<byte> byteArray = new List<byte>();
// Write Username // Write Username
byteArray.AddString(Username); m.Write(Username);
// Write Message // Write Message
byteArray.AddArray(crypt(Message)); m.WriteByteArray(crypt(Message));
return byteArray.ToArray();
} }
public override void Deserialize(byte[] array) public override void Deserialize(NetIncomingMessage m)
{ {
#region NetIncomingMessageToPacket #region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read username // Read username
Username = reader.ReadString(); Username = m.ReadString();
Message = decrypt(reader.ReadByteArray()).GetString(); Message = decrypt(m.ReadByteArray()).GetString();
#endregion #endregion
} }
} }

View File

@ -0,0 +1,88 @@
using Lidgren.Network;
using RageCoop.Core.Scripting;
using System;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class CustomEvent : Packet
{
public static Func<byte, NetIncomingMessage, object> ResolveHandle = null;
public CustomEventFlags Flags;
public override PacketType Type => PacketType.CustomEvent;
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
{
Flags = flags;
}
public int Hash { get; set; }
public object[] Args { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
Args = Args ?? new object[] { };
m.Write((byte)Flags);
m.Write(Hash);
m.Write(Args.Length);
foreach (var arg in Args)
{
CoreUtils.GetBytesFromObject(arg, m);
}
}
public override void Deserialize(NetIncomingMessage m)
{
Flags = (CustomEventFlags)m.ReadByte();
Hash = m.ReadInt32();
Args = new object[m.ReadInt32()];
for (int i = 0; i < Args.Length; i++)
{
byte type = m.ReadByte();
switch (type)
{
case 0x01:
Args[i] = m.ReadByte(); break;
case 0x02:
Args[i] = m.ReadInt32(); break;
case 0x03:
Args[i] = m.ReadUInt16(); break;
case 0x04:
Args[i] = m.ReadInt32(); break;
case 0x05:
Args[i] = m.ReadUInt32(); break;
case 0x06:
Args[i] = m.ReadInt64(); break;
case 0x07:
Args[i] = m.ReadUInt64(); break;
case 0x08:
Args[i] = m.ReadFloat(); break;
case 0x09:
Args[i] = m.ReadBoolean(); break;
case 0x10:
Args[i] = m.ReadString(); break;
case 0x11:
Args[i] = m.ReadVector3(); break;
case 0x12:
Args[i] = m.ReadQuaternion(); break;
case 0x13:
Args[i] = (GTA.Model)m.ReadInt32(); break;
case 0x14:
Args[i] = m.ReadVector2(); break;
case 0x15:
Args[i] = m.ReadByteArray(); break;
default:
if (ResolveHandle == null)
{
throw new InvalidOperationException($"Unexpected type: {type}");
}
else
{
Args[i] = ResolveHandle(type, m); break;
}
}
}
}
}
}
}

127
Core/Packets/FilePackets.cs Normal file
View File

@ -0,0 +1,127 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal enum FileResponse : byte
{
NeedToDownload = 0,
AlreadyExists = 1,
Completed = 2,
Loaded = 3,
LoadFailed = 4,
}
internal partial class Packets
{
internal class FileTransferRequest : Packet
{
public override PacketType Type => PacketType.FileTransferRequest;
public int ID { get; set; }
public string Name { get; set; }
public long FileLength { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID from the download
m.Write(ID);
// The name of the file
m.Write(Name);
// The length of the file
m.Write(FileLength);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
Name = m.ReadString();
FileLength = m.ReadInt64();
}
}
internal class FileTransferResponse : Packet
{
public override PacketType Type => PacketType.FileTransferResponse;
public int ID { get; set; }
public FileResponse Response { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID from the download
m.Write(ID);
m.Write((byte)Response);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
Response = (FileResponse)m.ReadByte();
}
}
internal class FileTransferChunk : Packet
{
public override PacketType Type => PacketType.FileTransferChunk;
public int ID { get; set; }
public byte[] FileChunk { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID from the download
m.Write(ID);
m.WriteByteArray(FileChunk);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
FileChunk = m.ReadByteArray();
}
}
internal class FileTransferComplete : Packet
{
public override PacketType Type => PacketType.FileTransferComplete;
public int ID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID for the download
m.Write(ID);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
}
}
internal class AllResourcesSent : Packet
{
public override PacketType Type => PacketType.AllResourcesSent;
}
}
}

67
Core/Packets/HolePunch.cs Normal file
View File

@ -0,0 +1,67 @@
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class HolePunchInit : Packet
{
public override PacketType Type => PacketType.HolePunchInit;
public int TargetID { get; set; }
public string TargetInternal { get; set; }
public string TargetExternal { get; set; }
public bool Connect { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(TargetID);
m.Write(TargetInternal);
m.Write(TargetExternal);
m.Write(Connect);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
TargetID = m.ReadInt32();
TargetInternal = m.ReadString();
TargetExternal = m.ReadString();
Connect = m.ReadBoolean();
#endregion
}
}
internal class HolePunch : Packet
{
public override PacketType Type => PacketType.HolePunch;
public int Puncher { get; set; }
/// <summary>
/// 1:initial, 2:acknowledged, 3:confirmed
/// </summary>
public byte Status { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(Puncher);
m.Write(Status);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
Puncher = m.ReadInt32();
Status = m.ReadByte();
#endregion
}
}
}
}

View File

@ -1,4 +1,4 @@
using System; using Lidgren.Network;
using System.Collections.Generic; using System.Collections.Generic;
namespace RageCoop.Core namespace RageCoop.Core
@ -12,16 +12,15 @@ namespace RageCoop.Core
{ {
public int TargetID { get; set; } public int TargetID { get; set; }
public override PacketType Type => PacketType.ConnectionRequest; public override PacketType Type => PacketType.ConnectionRequest;
public override byte[] Serialize() protected override void Serialize(NetOutgoingMessage m)
{ {
var data = new List<byte>(10); var data = new List<byte>(10);
data.AddInt(TargetID); m.Write(TargetID);
return data.ToArray();
} }
public override void Deserialize(byte[] array) public override void Deserialize(NetIncomingMessage m)
{ {
var reader=new BitReader(array);
TargetID = reader.ReadInt32(); TargetID = m.ReadInt32();
} }
} }
@ -33,16 +32,16 @@ namespace RageCoop.Core
{ {
public int ID { get; set; } public int ID { get; set; }
public override PacketType Type => PacketType.P2PConnect; public override PacketType Type => PacketType.P2PConnect;
public override byte[] Serialize() protected override void Serialize(NetOutgoingMessage m)
{ {
var data = new List<byte>(10); var data = new List<byte>(10);
data.AddInt(ID); m.Write(ID);
return data.ToArray();
} }
public override void Deserialize(byte[] array) public override void Deserialize(NetIncomingMessage m)
{ {
var reader = new BitReader(array);
ID = reader.ReadInt32(); ID = m.ReadInt32();
} }
} }
} }

View File

@ -1,9 +1,5 @@
using System; using Lidgren.Network;
using System.Collections.Generic; using System;
using System.Text;
using Lidgren.Network;
using Newtonsoft.Json;
using GTA.Math;
namespace RageCoop.Core namespace RageCoop.Core
{ {
@ -28,7 +24,6 @@ namespace RageCoop.Core
AllResourcesSent = 15, AllResourcesSent = 15,
CustomEvent = 16, CustomEvent = 16,
CustomEventQueued = 17,
ConnectionRequest = 18, ConnectionRequest = 18,
P2PConnect = 19, P2PConnect = 19,
@ -55,14 +50,6 @@ namespace RageCoop.Core
Unknown = 255 Unknown = 255
} }
internal static class PacketExtensions
{
internal static bool IsSyncEvent(this PacketType p)
{
return (30<=(byte)p)&&((byte)p<=40);
}
}
internal enum ConnectionChannel internal enum ConnectionChannel
{ {
Default = 0, Default = 0,
@ -98,6 +85,7 @@ namespace RageCoop.Core
IsInLowCover = 1 << 11, IsInLowCover = 1 << 11,
IsInCoverFacingLeft = 1 << 12, IsInCoverFacingLeft = 1 << 12,
IsBlindFiring = 1 << 13, IsBlindFiring = 1 << 13,
IsInvincible = 1 << 14,
IsFullSync = 1 << 15, IsFullSync = 1 << 15,
} }
@ -152,25 +140,19 @@ namespace RageCoop.Core
internal interface IPacket internal interface IPacket
{ {
PacketType Type { get; } PacketType Type { get; }
byte[] Serialize();
void Deserialize(byte[] data); void Deserialize(NetIncomingMessage m);
} }
internal abstract class Packet : IPacket internal abstract class Packet : IPacket
{ {
public abstract PacketType Type { get; } public abstract PacketType Type { get; }
public virtual byte[] Serialize() public void Pack(NetOutgoingMessage m)
{ {
return new byte[0]; m.Write((byte)Type);
} Serialize(m);
public virtual void Deserialize(byte[] array) { }
public void Pack(NetOutgoingMessage message)
{
var d=Serialize();
message.Write((byte)Type);
message.Write(d.Length);
message.Write(d);
} }
protected virtual void Serialize(NetOutgoingMessage m) { }
public virtual void Deserialize(NetIncomingMessage m) { }
} }
} }

View File

@ -1,9 +1,7 @@
using System; using GTA;
using System.Collections.Generic;
using System.Text;
using GTA.Math; using GTA.Math;
using GTA;
using Lidgren.Network; using Lidgren.Network;
using System.Collections.Generic;
namespace RageCoop.Core namespace RageCoop.Core
{ {
@ -61,92 +59,92 @@ namespace RageCoop.Core
public float BlipScale { get; set; } = 1; public float BlipScale { get; set; } = 1;
#endregion #endregion
public override byte[] Serialize() protected override void Serialize(NetOutgoingMessage m)
{ {
List<byte> byteArray = new List<byte>();
byteArray.AddInt(ID); m.Write(ID);
byteArray.AddInt(OwnerID); m.Write(OwnerID);
byteArray.AddRange(BitConverter.GetBytes((ushort)Flags)); m.Write((ushort)Flags);
byteArray.AddRange(BitConverter.GetBytes(Health)); m.Write(Health);
byteArray.Add(Speed); m.Write(Speed);
if (Flags.HasPedFlag(PedDataFlags.IsRagdoll)) if (Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{ {
byteArray.AddVector3(HeadPosition); m.Write(HeadPosition);
byteArray.AddVector3(RightFootPosition); m.Write(RightFootPosition);
byteArray.AddVector3(LeftFootPosition); m.Write(LeftFootPosition);
} }
else else
{ {
if (Speed >= 4) if (Speed >= 4)
{ {
byteArray.AddInt(VehicleID); m.Write(VehicleID);
byteArray.Add((byte)(Seat+3)); m.Write((byte)(Seat + 3));
} }
byteArray.AddVector3(Position); m.Write(Position);
} }
byteArray.AddVector3(Rotation); m.Write(Rotation);
byteArray.AddVector3(Velocity); m.Write(Velocity);
if (Flags.HasPedFlag(PedDataFlags.IsAiming)) if (Flags.HasPedFlag(PedDataFlags.IsAiming))
{ {
byteArray.AddVector3(AimCoords); m.Write(AimCoords);
} }
byteArray.AddFloat(Heading); m.Write(Heading);
if (Flags.HasPedFlag(PedDataFlags.IsFullSync)) if (Flags.HasPedFlag(PedDataFlags.IsFullSync))
{ {
byteArray.AddInt(ModelHash); m.Write(ModelHash);
byteArray.AddUint(CurrentWeaponHash); m.Write(CurrentWeaponHash);
byteArray.AddRange(Clothes); m.Write(Clothes);
if (WeaponComponents != null) if (WeaponComponents != null)
{ {
byteArray.Add(0x01); m.Write(true);
byteArray.AddRange(BitConverter.GetBytes((ushort)WeaponComponents.Count)); m.Write((ushort)WeaponComponents.Count);
foreach (KeyValuePair<uint, bool> component in WeaponComponents) foreach (KeyValuePair<uint, bool> component in WeaponComponents)
{ {
byteArray.AddRange(BitConverter.GetBytes(component.Key)); m.Write(component.Key);
byteArray.AddRange(BitConverter.GetBytes(component.Value)); m.Write(component.Value);
} }
} }
else else
{ {
// Player weapon doesn't have any components // Player weapon doesn't have any components
byteArray.Add(0x00); m.Write(false);
} }
byteArray.Add(WeaponTint); m.Write(WeaponTint);
byteArray.Add((byte)BlipColor); m.Write((byte)BlipColor);
if ((byte)BlipColor != 255) if ((byte)BlipColor != 255)
{ {
byteArray.AddUshort((ushort)BlipSprite); m.Write((ushort)BlipSprite);
byteArray.AddFloat(BlipScale); m.Write(BlipScale);
} }
} }
return byteArray.ToArray();
} }
public override void Deserialize(byte[] array) public override void Deserialize(NetIncomingMessage m)
{ {
#region NetIncomingMessageToPacket #region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
ID = reader.ReadInt32();
OwnerID=reader.ReadInt32(); ID = m.ReadInt32();
Flags = (PedDataFlags)reader.ReadUInt16(); OwnerID = m.ReadInt32();
Health = reader.ReadInt32(); Flags = (PedDataFlags)m.ReadUInt16();
Speed = reader.ReadByte(); Health = m.ReadInt32();
Speed = m.ReadByte();
if (Flags.HasPedFlag(PedDataFlags.IsRagdoll)) if (Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{ {
HeadPosition=reader.ReadVector3(); HeadPosition = m.ReadVector3();
RightFootPosition=reader.ReadVector3(); RightFootPosition = m.ReadVector3();
LeftFootPosition=reader.ReadVector3(); LeftFootPosition = m.ReadVector3();
Position = HeadPosition; Position = HeadPosition;
} }
else else
@ -154,54 +152,54 @@ namespace RageCoop.Core
// Vehicle related // Vehicle related
if (Speed >= 4) if (Speed >= 4)
{ {
VehicleID=reader.ReadInt32(); VehicleID = m.ReadInt32();
Seat=(VehicleSeat)(reader.ReadByte()-3); Seat = (VehicleSeat)(m.ReadByte() - 3);
} }
// Read player position // Read player position
Position = reader.ReadVector3(); Position = m.ReadVector3();
} }
Rotation = reader.ReadVector3(); Rotation = m.ReadVector3();
Velocity = reader.ReadVector3(); Velocity = m.ReadVector3();
if (Flags.HasPedFlag(PedDataFlags.IsAiming)) if (Flags.HasPedFlag(PedDataFlags.IsAiming))
{ {
// Read player aim coords // Read player aim coords
AimCoords = reader.ReadVector3(); AimCoords = m.ReadVector3();
} }
Heading=reader.ReadSingle(); Heading = m.ReadFloat();
if (Flags.HasPedFlag(PedDataFlags.IsFullSync)) if (Flags.HasPedFlag(PedDataFlags.IsFullSync))
{ {
// Read player model hash // Read player model hash
ModelHash = reader.ReadInt32(); ModelHash = m.ReadInt32();
// Read player weapon hash // Read player weapon hash
CurrentWeaponHash = reader.ReadUInt32(); CurrentWeaponHash = m.ReadUInt32();
// Read player clothes // Read player clothes
Clothes =reader.ReadBytes(36); Clothes = m.ReadBytes(36);
// Read player weapon components // Read player weapon components
if (reader.ReadBoolean()) if (m.ReadBoolean())
{ {
WeaponComponents = new Dictionary<uint, bool>(); WeaponComponents = new Dictionary<uint, bool>();
ushort comCount = reader.ReadUInt16(); ushort comCount = m.ReadUInt16();
for (ushort i = 0; i < comCount; i++) for (ushort i = 0; i < comCount; i++)
{ {
WeaponComponents.Add(reader.ReadUInt32(), reader.ReadBoolean()); WeaponComponents.Add(m.ReadUInt32(), m.ReadBoolean());
} }
} }
WeaponTint=reader.ReadByte(); WeaponTint = m.ReadByte();
BlipColor=(BlipColor)reader.ReadByte(); BlipColor = (BlipColor)m.ReadByte();
if ((byte)BlipColor != 255) if ((byte)BlipColor != 255)
{ {
BlipSprite=(BlipSprite)reader.ReadUInt16(); BlipSprite = (BlipSprite)m.ReadUInt16();
BlipScale=reader.ReadSingle(); BlipScale = m.ReadFloat();
} }
} }
#endregion #endregion

View File

@ -0,0 +1,249 @@
using GTA.Math;
using Lidgren.Network;
using System.Net;
namespace RageCoop.Core
{
internal partial class Packets
{
internal struct PlayerData
{
public int ID;
public string Username;
}
public class Handshake : Packet
{
public override PacketType Type => PacketType.Handshake;
public int PedID { get; set; }
public string Username { get; set; }
public string ModVersion { get; set; }
/// <summary>
/// The asymetrically crypted Aes key
/// </summary>
public byte[] AesKeyCrypted;
/// <summary>
/// The asymetrically crypted Aes IV
/// </summary>
public byte[] AesIVCrypted;
/// <summary>
/// The password hash with client Aes
/// </summary>
public byte[] PasswordEncrypted { get; set; }
public IPEndPoint InternalEndPoint { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write Player Ped ID
m.Write(PedID);
// Write Username
m.Write(Username);
// Write ModVersion
m.Write(ModVersion);
m.Write(InternalEndPoint.ToString());
// Write AesKeyCrypted
m.WriteByteArray(AesKeyCrypted);
// Write AesIVCrypted
m.WriteByteArray(AesIVCrypted);
// Write PassHash
m.WriteByteArray(PasswordEncrypted);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read player netHandle
PedID = m.ReadInt32();
// Read Username
Username = m.ReadString();
// Read ModVersion
ModVersion = m.ReadString();
InternalEndPoint = CoreUtils.StringToEndPoint(m.ReadString());
AesKeyCrypted = m.ReadByteArray();
AesIVCrypted = m.ReadByteArray();
PasswordEncrypted = m.ReadByteArray();
#endregion
}
}
public class HandshakeSuccess : Packet
{
public PlayerData[] Players { get; set; }
public override PacketType Type => PacketType.HandshakeSuccess;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(Players.Length);
foreach (var p in Players)
{
m.Write(p.ID);
m.Write(p.Username);
}
}
public override void Deserialize(NetIncomingMessage m)
{
Players = new PlayerData[m.ReadInt32()];
for (int i = 0; i < Players.Length; i++)
{
Players[i] = new PlayerData()
{
ID = m.ReadInt32(),
Username = m.ReadString(),
};
}
}
}
public class PlayerConnect : Packet
{
public override PacketType Type => PacketType.PlayerConnect;
public int PedID { get; set; }
public string Username { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write NetHandle
m.Write(PedID);
m.Write(Username);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read player netHandle
PedID = m.ReadInt32();
// Read Username
Username = m.ReadString();
#endregion
}
}
public class PlayerDisconnect : Packet
{
public override PacketType Type => PacketType.PlayerDisconnect;
public int PedID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(PedID);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
PedID = m.ReadInt32();
#endregion
}
}
public class PlayerInfoUpdate : Packet
{
public override PacketType Type => PacketType.PlayerInfoUpdate;
/// <summary>
/// Ped ID for this Player
/// </summary>
public int PedID { get; set; }
public string Username { get; set; }
public float Latency { get; set; }
public Vector3 Position { get; set; }
public bool IsHost;
protected override void Serialize(NetOutgoingMessage m)
{
// Write ID
m.Write(PedID);
// Write Username
m.Write(Username);
// Write Latency
m.Write(Latency);
m.Write(Position);
m.Write(IsHost);
}
public override void Deserialize(NetIncomingMessage m)
{
// Read player ID
PedID = m.ReadInt32();
// Read Username
Username = m.ReadString();
Latency = m.ReadFloat();
Position = m.ReadVector3();
IsHost = m.ReadBoolean();
}
}
public class PublicKeyResponse : Packet
{
public override PacketType Type => PacketType.PublicKeyResponse;
public byte[] Modulus;
public byte[] Exponent;
protected override void Serialize(NetOutgoingMessage m)
{
m.WriteByteArray(Modulus);
m.WriteByteArray(Exponent);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
Modulus = m.ReadByteArray();
Exponent = m.ReadByteArray();
#endregion
}
}
public class PublicKeyRequest : Packet
{
public override PacketType Type => PacketType.PublicKeyRequest;
}
}
}

View File

@ -0,0 +1,82 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class ProjectileSync : Packet
{
public override PacketType Type => PacketType.ProjectileSync;
public int ID { get; set; }
public int ShooterID { get; set; }
public uint WeaponHash { get; set; }
public Vector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public ProjectileDataFlags Flags { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write id
m.Write(ID);
// Write ShooterID
m.Write(ShooterID);
m.Write(WeaponHash);
// Write position
m.Write(Position);
// Write rotation
m.Write(Rotation);
// Write velocity
m.Write(Velocity);
m.Write((byte)Flags);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read id
ID = m.ReadInt32();
// Read ShooterID
ShooterID = m.ReadInt32();
WeaponHash = m.ReadUInt32();
// Read position
Position = m.ReadVector3();
// Read rotation
Rotation = m.ReadVector3();
// Read velocity
Velocity = m.ReadVector3();
Flags = (ProjectileDataFlags)m.ReadByte();
#endregion
}
}
}
}

View File

@ -0,0 +1,65 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class BulletShot : Packet
{
public override PacketType Type => PacketType.BulletShot;
public int OwnerID { get; set; }
public uint WeaponHash { get; set; }
public Vector3 StartPosition { get; set; }
public Vector3 EndPosition { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write OwnerID
m.Write(OwnerID);
// Write weapon hash
m.Write(WeaponHash);
// Write StartPosition
m.Write(StartPosition);
// Write EndPosition
m.Write(EndPosition);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read OwnerID
OwnerID = m.ReadInt32();
// Read WeponHash
WeaponHash = m.ReadUInt32();
// Read StartPosition
StartPosition = m.ReadVector3();
// Read EndPosition
EndPosition = m.ReadVector3();
#endregion
}
}
}
}

View File

@ -0,0 +1,42 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class NozzleTransform : Packet
{
public override PacketType Type => PacketType.NozzleTransform;
public int VehicleID { get; set; }
public bool Hover { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(VehicleID);
m.Write(Hover);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
VehicleID = m.ReadInt32();
Hover = m.ReadBoolean();
#endregion
}
}
}
}

View File

@ -0,0 +1,38 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class OwnerChanged : Packet
{
public override PacketType Type => PacketType.OwnerChanged;
public int ID { get; set; }
public int NewOwnerID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(NewOwnerID);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
ID = m.ReadInt32();
NewOwnerID = m.ReadInt32();
#endregion
}
}
}
}

View File

@ -0,0 +1,39 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class PedKilled : Packet
{
public override PacketType Type => PacketType.PedKilled;
public int VictimID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(VictimID);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
VictimID = m.ReadInt32();
#endregion
}
}
}
}

View File

@ -0,0 +1,52 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class VehicleBulletShot : Packet
{
public override PacketType Type => PacketType.VehicleBulletShot;
public int OwnerID { get; set; }
public ushort Bone { get; set; }
public uint WeaponHash { get; set; }
public Vector3 StartPosition { get; set; }
public Vector3 EndPosition { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(OwnerID);
m.Write(Bone);
m.Write(WeaponHash);
m.Write(StartPosition);
m.Write(EndPosition);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
OwnerID = m.ReadInt32();
Bone = m.ReadUInt16();
WeaponHash = m.ReadUInt32();
StartPosition = m.ReadVector3();
EndPosition = m.ReadVector3();
#endregion
}
}
}
}

227
Core/Packets/VehicleSync.cs Normal file
View File

@ -0,0 +1,227 @@
using GTA;
using GTA.Math;
using Lidgren.Network;
using System.Collections.Generic;
namespace RageCoop.Core
{
internal partial class Packets
{
public class VehicleSync : Packet
{
public override PacketType Type => PacketType.VehicleSync;
public int ID { get; set; }
public int OwnerID { get; set; }
public VehicleDataFlags Flags { get; set; }
public Vector3 Position { get; set; }
public Quaternion Quaternion { get; set; }
// public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public Vector3 RotationVelocity { get; set; }
public float ThrottlePower { get; set; }
public float BrakePower { get; set; }
public float SteeringAngle { get; set; }
public float DeluxoWingRatio { get; set; } = -1;
#region FULL-SYNC
public int ModelHash { get; set; }
public float EngineHealth { get; set; }
public byte[] Colors { get; set; }
public Dictionary<int, int> Mods { get; set; }
public VehicleDamageModel DamageModel { get; set; }
public byte LandingGear { get; set; }
public byte RoofState { get; set; }
public VehicleLockStatus LockStatus { get; set; }
public int Livery { get; set; } = -1;
public byte RadioStation { get; set; } = 255;
public string LicensePlate { get; set; }
#endregion
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(OwnerID);
m.Write((ushort)Flags);
m.Write(Position);
m.Write(Quaternion);
m.Write(Velocity);
m.Write(RotationVelocity);
m.Write(ThrottlePower);
m.Write(BrakePower);
m.Write(SteeringAngle);
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
{
m.Write(DeluxoWingRatio);
}
if (Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
m.Write(ModelHash);
m.Write(EngineHealth);
// Check
if (Flags.HasVehFlag(VehicleDataFlags.IsAircraft))
{
// Write the vehicle landing gear
m.Write(LandingGear);
}
if (Flags.HasVehFlag(VehicleDataFlags.HasRoof))
{
m.Write(RoofState);
}
// Write vehicle colors
m.Write(Colors[0]);
m.Write(Colors[1]);
// Write vehicle mods
// Write the count of mods
m.Write((short)Mods.Count);
// Loop the dictionary and add the values
foreach (KeyValuePair<int, int> mod in Mods)
{
// Write the mod value
m.Write(mod.Key);
m.Write(mod.Value);
}
if (!DamageModel.Equals(default(VehicleDamageModel)))
{
// Write boolean = true
m.Write(true);
// Write vehicle damage model
m.Write(DamageModel.BrokenDoors);
m.Write(DamageModel.OpenedDoors);
m.Write(DamageModel.BrokenWindows);
m.Write(DamageModel.BurstedTires);
m.Write(DamageModel.LeftHeadLightBroken);
m.Write(DamageModel.RightHeadLightBroken);
}
else
{
// Write boolean = false
m.Write(false);
}
// Write LockStatus
m.Write((byte)LockStatus);
// Write RadioStation
m.Write(RadioStation);
// Write LicensePlate
m.Write(LicensePlate);
m.Write((byte)(Livery + 1));
}
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
ID = m.ReadInt32();
OwnerID = m.ReadInt32();
Flags = (VehicleDataFlags)m.ReadUInt16();
Position = m.ReadVector3();
Quaternion = m.ReadQuaternion();
Velocity = m.ReadVector3();
RotationVelocity = m.ReadVector3();
ThrottlePower = m.ReadFloat();
BrakePower = m.ReadFloat();
SteeringAngle = m.ReadFloat();
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
{
DeluxoWingRatio = m.ReadFloat();
}
if (Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
// Read vehicle model hash
ModelHash = m.ReadInt32();
// Read vehicle engine health
EngineHealth = m.ReadFloat();
// Check
if (Flags.HasVehFlag(VehicleDataFlags.IsAircraft))
{
// Read vehicle landing gear
LandingGear = m.ReadByte();
}
if (Flags.HasVehFlag(VehicleDataFlags.HasRoof))
{
RoofState = m.ReadByte();
}
// Read vehicle colors
byte vehColor1 = m.ReadByte();
byte vehColor2 = m.ReadByte();
Colors = new byte[] { vehColor1, vehColor2 };
// Read vehicle mods
// Create new Dictionary
Mods = new Dictionary<int, int>();
// Read count of mods
short vehModCount = m.ReadInt16();
// Loop
for (int i = 0; i < vehModCount; i++)
{
// Read the mod value
Mods.Add(m.ReadInt32(), m.ReadInt32());
}
if (m.ReadBoolean())
{
// Read vehicle damage model
DamageModel = new VehicleDamageModel()
{
BrokenDoors = m.ReadByte(),
OpenedDoors = m.ReadByte(),
BrokenWindows = m.ReadByte(),
BurstedTires = m.ReadInt16(),
LeftHeadLightBroken = m.ReadByte(),
RightHeadLightBroken = m.ReadByte()
};
}
// Read LockStatus
LockStatus = (VehicleLockStatus)m.ReadByte();
// Read RadioStation
RadioStation = m.ReadByte();
LicensePlate = m.ReadString();
Livery = m.ReadByte() - 1;
}
#endregion
}
}
}
}

29
Core/Packets/Voice.cs Normal file
View File

@ -0,0 +1,29 @@
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class Voice : Packet
{
public int ID { get; set; }
public byte[] Buffer { get; set; }
public int Recorded { get; set; }
public override PacketType Type => PacketType.Voice;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(Buffer);
m.Write(Recorded);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
Buffer = m.ReadByteArray();
Recorded = m.ReadInt32();
}
}
}
}

View File

@ -24,6 +24,14 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Costura.Fody" Version="5.7.0"> <PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -34,7 +42,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" /> <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup> </ItemGroup>
@ -42,9 +51,6 @@
<Reference Include="Lidgren.Network"> <Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath> <HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3"> <Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath> <HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference> </Reference>

View File

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace RageCoop.Core.Scripting
{
/// <summary>
/// Describes how the event should be sent or processed
/// </summary>
public enum CustomEventFlags : byte
{
None = 0,
/// <summary>
/// Data will be encrypted and decrypted on target client
/// </summary>
Encrypted = 1,
/// <summary>
/// Event will be queued and fired in script thread, specify this flag if your handler will call native functions.
/// </summary>
Queued = 2,
}
/// <summary>
/// Struct to identify different event using hash
/// </summary>
public struct CustomEventHash
{
private static readonly MD5 Hasher = MD5.Create();
private static readonly Dictionary<int, string> Hashed = new Dictionary<int, string>();
/// <summary>
/// Hash value
/// </summary>
public int Hash;
/// <summary>
/// Create from hash
/// </summary>
/// <param name="hash"></param>
public static implicit operator CustomEventHash(int hash)
{
return new CustomEventHash() { Hash = hash };
}
/// <summary>
/// Create from string
/// </summary>
/// <param name="name"></param>
public static implicit operator CustomEventHash(string name)
{
return new CustomEventHash() { Hash = FromString(name) };
}
/// <summary>
/// Get a Int32 hash of a string.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">The exception is thrown when the name did not match a previously computed one and the hash was the same.</exception>
public static int FromString(string s)
{
var hash = BitConverter.ToInt32(Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)), 0);
lock (Hashed)
{
if (Hashed.TryGetValue(hash, out string name))
{
if (name != s)
{
throw new ArgumentException($"Hashed value has collision with another name:{name}, hashed value:{hash}");
}
return hash;
}
Hashed.Add(hash, s);
return hash;
}
}
/// <summary>
/// To int
/// </summary>
/// <param name="h"></param>
public static implicit operator int(CustomEventHash h)
{
return h.Hash;
}
}
/// <summary>
///
/// </summary>
public static class CustomEvents
{
internal static readonly CustomEventHash OnPlayerDied = "RageCoop.OnPlayerDied";
internal static readonly CustomEventHash SetWeather = "RageCoop.SetWeather";
internal static readonly CustomEventHash OnPedDeleted = "RageCoop.OnPedDeleted";
internal static readonly CustomEventHash OnVehicleDeleted = "RageCoop.OnVehicleDeleted";
internal static readonly CustomEventHash SetAutoRespawn = "RageCoop.SetAutoRespawn";
internal static readonly CustomEventHash SetDisplayNameTag = "RageCoop.SetDisplayNameTag";
internal static readonly CustomEventHash NativeCall = "RageCoop.NativeCall";
internal static readonly CustomEventHash NativeResponse = "RageCoop.NativeResponse";
internal static readonly CustomEventHash AllResourcesSent = "RageCoop.AllResourcesSent";
internal static readonly CustomEventHash ServerPropSync = "RageCoop.ServerPropSync";
internal static readonly CustomEventHash ServerBlipSync = "RageCoop.ServerBlipSync";
internal static readonly CustomEventHash SetEntity = "RageCoop.SetEntity";
internal static readonly CustomEventHash DeleteServerProp = "RageCoop.DeleteServerProp";
internal static readonly CustomEventHash UpdatePedBlip = "RageCoop.UpdatePedBlip";
internal static readonly CustomEventHash DeleteEntity = "RageCoop.DeleteEntity";
internal static readonly CustomEventHash DeleteServerBlip = "RageCoop.DeleteServerBlip";
internal static readonly CustomEventHash CreateVehicle = "RageCoop.CreateVehicle";
internal static readonly CustomEventHash WeatherTimeSync = "RageCoop.WeatherTimeSync";
internal static readonly CustomEventHash IsHost = "RageCoop.IsHost";
/// <summary>
/// Get event hash from string.
/// </summary>
/// <returns></returns>
/// <remarks>This method is obsoete, you should use implicit operator or <see cref="CustomEventHash.FromString(string)"/></remarks>
[Obsolete]
public static int Hash(string s)
{
return CustomEventHash.FromString(s);
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.IO;
namespace RageCoop.Core.Scripting
{
/// <summary>
///
/// </summary>
public class ResourceFile
{
/// <summary>
/// Full name with relative path of this file
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Whether this is a directory
/// </summary>
public bool IsDirectory { get; internal set; }
/// <summary>
/// Get a stream that can be used to read file content.
/// </summary>
public Func<Stream> GetStream { get; internal set; }
}
}

86
Core/Worker.cs Normal file
View File

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

View File

@ -1,6 +1,7 @@
# 🌐 RAGECOOP # 🌐 RAGECOOP
[![Downloads][downloads-shield]][downloads-url] [![Downloads][downloads-shield]][downloads-url]
[![Contributors][contributors-shield]][contributors-url] [![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url] [![Forks][forks-shield]][forks-url]
@ -9,24 +10,37 @@
# 🧠 That's it # 🧠 That's it
RAGECOOP brings multiplayer experience to the story mode, you can complete missions together with your friends, use mods without any restriction/getting banned, or just mess around with your fella! RAGECOOP brings multiplayer experience to the story mode, you can complete missions together with your friends, use mods without any restriction/getting banned, or just mess around with your fella!
# 📋 Requirements # 👁 Requirements
- Visual Studio 2022 - ScriptHookV
- .NET 6.0 - ScriptHookVDotNet 3.5.1 or later
- .NET Framework 4.8 - .NET Framework 4.8 Runtime or SDK
# 📚 Libraries # 📋 Building the project
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet/releases/tag/v3.4.0)
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI/releases/tag/v1.6) You'll need:
- Lidgren Network Custom (***PRIVATE***) - .NET 6.0 SDK
- .NET Framework 4.8 SDK
Recommended IDE:
- Visual Studio Code
- Visul Studio 2022
Then run `dotnet build` in the solution directory, built binaries are in the `bin` folder
# 📚 Third-party libraries
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet)
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI)
- Lidgren Network Custom
- - No new features (only improvements) - - No new features (only improvements)
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1) - [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
- [ClearScript](https://github.com/microsoft/ClearScript) - [ClearScript](https://github.com/microsoft/ClearScript)
- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) - [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins) - [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins)
# Features # 👋 Features
1. Synchronized bullets 1. Synchronized bullets
2. Synchronized vehicle/player/NPC 2. Synchronized vehicle/player/NPC
@ -37,15 +51,15 @@ RAGECOOP brings multiplayer experience to the story mode, you can complete missi
7. Optimization for high-Ping condition, play with friends around the world! 7. Optimization for high-Ping condition, play with friends around the world!
8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.online). 8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.online).
# Known issues # Known issues
See [Bugs](https://github.com/RAGECOOP/RAGECOOP-V/issues/33) See [Bugs](https://github.com/RAGECOOP/RAGECOOP-V/issues/33)
## Installation # 🔫 Installation
Refer to the [wiki](https://github.com/RAGECOOP/RAGECOOP-V/wiki) Refer to the [wiki](https://github.com/RAGECOOP/RAGECOOP-V/wiki)
# Downloads # 🧨 Downloads
Download latest release [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/latest) Download latest release [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/latest)
@ -56,6 +70,7 @@ Please note that this is incompatible with all previous versions of ragecoop, re
# 🦆 Special thanks to # 🦆 Special thanks to
- [Makinolo](https://github.com/Makinolo), [oldnapalm](https://github.com/oldnapalm) - [Makinolo](https://github.com/Makinolo), [oldnapalm](https://github.com/oldnapalm)
- - For testing, ideas, contributions and the first modification with the API - - For testing, ideas, contributions and the first modification with the API
- [crosire](https://github.com/crosire) - [crosire](https://github.com/crosire)
@ -64,6 +79,7 @@ Please note that this is incompatible with all previous versions of ragecoop, re
- - For the extensive work in LemonUI - - For the extensive work in LemonUI
# 📝 License # 📝 License
This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOOP-V/blob/main/LICENSE) This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOOP-V/blob/main/LICENSE)
[downloads-shield]: https://img.shields.io/github/downloads/RAGECOOP/RAGECOOP-V/total?style=for-the-badge [downloads-shield]: https://img.shields.io/github/downloads/RAGECOOP/RAGECOOP-V/total?style=for-the-badge
@ -77,3 +93,4 @@ This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOO
[issues-shield]: https://img.shields.io/github/issues/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge [issues-shield]: https://img.shields.io/github/issues/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
[issues-url]: https://github.com/RAGECOOP/RAGECOOP-V/issues [issues-url]: https://github.com/RAGECOOP/RAGECOOP-V/issues

View File

@ -3,11 +3,17 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166 VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "RageCoop.Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "RageCoop.Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "RageCoop.Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "Client\Scripts\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "Client\Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client.Loader", "Client\Loader\RageCoop.Client.Loader.csproj", "{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{531656CF-7269-488D-B042-741BC96C3941}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -41,10 +47,31 @@ Global
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.Build.0 = Release|Any CPU {EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.Build.0 = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.ActiveCfg = Release|Any CPU {EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.ActiveCfg = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.Build.0 = Release|Any CPU {EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.ActiveCfg = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.Build.0 = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Debug|x64.Build.0 = Debug|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|Any CPU.Build.0 = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|x64.ActiveCfg = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681} = {531656CF-7269-488D-B042-741BC96C3941}
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B} = {531656CF-7269-488D-B042-741BC96C3941}
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5} = {531656CF-7269-488D-B042-741BC96C3941}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7} SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
EndGlobalSection EndGlobalSection

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