Merge pull request #44 from RAGECOOP/net7-shvdnc

Use SHVDNC as new runtime
This commit is contained in:
Richard Matthew
2023-03-11 15:30:14 +08:00
committed by GitHub
163 changed files with 2986 additions and 270751 deletions

View File

@ -24,6 +24,9 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies
run: dotnet restore

View File

@ -18,6 +18,9 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies
run: dotnet restore

6
.gitmodules vendored Normal file
View File

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

View File

@ -6,7 +6,6 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using RageCoop.Core;
using static RageCoop.Client.Shared;
@ -116,14 +115,14 @@ namespace RageCoop.Client.Installer
UpdateStatus("Checking conflicts");
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
var settingsPath = Path.Combine(root, SettingsPath);
Settings settings = null;
ClientSettings settings = null;
try
{
settings = Util.ReadSettings(settingsPath);
}
catch
{
settings = new Settings();
settings = new();
}
if (File.Exists(menyooConfig))
@ -141,7 +140,7 @@ namespace RageCoop.Client.Installer
Dispatcher.BeginInvoke(new Action(() =>
KeyDown += (s, e) =>
{
settings.MenuKey = (Keys)KeyInterop.VirtualKeyFromKey(e.Key);
settings.MenuKey = (GTA.Keys)KeyInterop.VirtualKeyFromKey(e.Key);
ae.Set();
}));
ae.WaitOne();

View File

@ -1,57 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AllowedReferenceRelatedFileExtensions>
<PropertyGroup>
<NoAotCompile>true</NoAotCompile>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AllowedReferenceRelatedFileExtensions>
-
</AllowedReferenceRelatedFileExtensions>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\..\bin\Debug\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>DEBUG</DefineConstants>
<WarningLevel>4</WarningLevel>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\..\bin\Release\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\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>
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\..\bin\Debug\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>DEBUG</DefineConstants>
<WarningLevel>4</WarningLevel>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\..\bin\Release\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Scripts\RageCoop.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="bg.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile; runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
#undef DEBUG
using System.Windows.Forms;

namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
public class Settings
public class ClientSettings
{
/// <summary>
/// LogLevel for RageCoop.
@ -47,7 +46,7 @@ namespace RageCoop.Client
/// <summary>
/// The key to open menu
/// </summary>
public Keys MenuKey { get; set; } = Keys.F9;
public Keys MenuKey { get; set; } = Keys.F7;
/// <summary>
/// The key to enter a vehicle as passenger.

View File

@ -1,27 +0,0 @@
using System;
namespace GTA
{
/// <summary>
/// Wrapper that provides access to SHVDN's in-game console
/// </summary>
public class Console
{
private static SHVDN.Console console => (SHVDN.Console)AppDomain.CurrentDomain.GetData("Console");
public static void Warning(object format, params object[] objects)
{
console.PrintInfo("[~o~WARNING~w~] ", format.ToString(), objects);
}
public static void Error(object format, params object[] objects)
{
console.PrintError("[~r~ERROR~w~] ", format.ToString(), objects);
}
public static void Info(object format, params object[] objects)
{
console.PrintWarning("[~b~INFO~w~] ", format.ToString(), objects);
}
}
}

View File

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

View File

@ -5,8 +5,7 @@ using RageCoop.Core;
namespace RageCoop.Client
{
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false,
SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class DevTool : Script
{
public static Vehicle ToMark;
@ -14,14 +13,14 @@ namespace RageCoop.Client
public DevTool()
{
Util.StartUpCheck();
Instance = this;
Tick += OnTick;
Pause();
}
private void OnTick(object sender, EventArgs e)
protected override void OnTick()
{
base.OnTick();
foreach (var p in World.GetAllPeds()) DrawWeaponBone(p);
if (ToMark == null) return;

View File

@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using DXHook.Hook.Common;
using GTA;
using GTA.Native;
using RageCoop.Client.CefHost;

View File

@ -4,7 +4,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using GTA;
using GTA.Math;
using GTA.Native;
@ -12,53 +14,42 @@ using GTA.UI;
using LemonUI.Elements;
using LemonUI.Menus;
using Lidgren.Network;
using Newtonsoft.Json.Linq;
using RageCoop.Client.GUI;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using static RageCoop.Client.Shared;
using Console = GTA.Console;
using Control = GTA.Control;
using Screen = System.Windows.Forms.Screen;
namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
[ScriptAttributes(Author = "RageCoop", NoScriptThread = true,
SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V", NoScriptThread = true)]
internal class Main : Script
{
private static bool _gameLoaded = false;
internal static Version Version = typeof(Main).Assembly.GetName().Version;
internal static Version ModVersion = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0;
internal static RelationshipGroup SyncedPedsGroup;
internal static new Settings Settings = null;
internal static ClientSettings Settings = null;
internal static Chat MainChat = null;
internal static Stopwatch Counter = new Stopwatch();
internal static Logger Logger = null;
internal static Stopwatch Counter = new();
internal static Logger Log = null;
internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition;
internal static Resources Resources = null;
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
internal static Resources MainRes = null;
public static Ped P;
public static float FPS;
private static bool _lastDead;
public static bool CefRunning;
public static bool IsUnloading { get; private set; }
public static Script Instance { get; private set; }
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
Util.StartUpCheck();
Instance = this;
Directory.CreateDirectory(DataPath);
try
{
@ -67,167 +58,100 @@ namespace RageCoop.Client
catch
{
Notification.Show("Malformed configuration, overwriting with default values...");
Settings = new Settings();
Settings = new();
Util.SaveSettings();
}
Logger = new Logger()
Log = new Logger()
{
Writers = new List<StreamWriter> { CoreUtils.OpenWriter(LogPath) },
FlushImmediately = true,
Writers = null,
#if DEBUG
LogLevel = 0,
#else
LogLevel = Settings.LogLevel,
#endif
};
Logger.OnFlush += (line, formatted) =>
Log.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;
}
SHVDN.Logger.Write($"[RageCoop] {line.Message}", (uint)line.LogLevel);
};
Resources = new Resources();
// Run static constructor to register all function pointers and remoting entries
RuntimeHelpers.RunClassConstructor(typeof(API).TypeHandle);
}
protected override void OnAborted(AbortedEventArgs e)
{
base.OnAborted(e);
try
{
IsUnloading = e.IsUnloading;
CleanUp("Abort");
WorldThread.DoQueuedActions();
if (IsUnloading)
{
ThreadManager.OnUnload();
Log.Dispose();
}
}
catch (Exception ex)
{
Log.Error(ex);
}
}
protected override void OnStart()
{
base.OnStart();
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{
Tick += (object sender, EventArgs e) =>
{
if (Game.IsLoading)
{
return;
}
if (!_gameLoaded)
{
Notification.Show("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
_gameLoaded = true;
}
};
return;
throw new NotSupportedException("Please update your GTA5 to v1.0.1290 or newer!");
}
Logger.Info(
$"Starting {typeof(Main).FullName}, domain: {AppDomain.CurrentDomain.Id} {AppDomain.CurrentDomain.FriendlyName}");
MainRes = new();
Log.Info(
$"Main script initialized");
BaseScript.OnStart();
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral,
true);
#if !NON_INTERACTIVE
#endif
MainChat = new Chat();
Aborted += OnAborted;
Tick += OnTick;
KeyDown += OnKeyDown;
KeyUp += OnKeyUp;
Util.NativeMemory();
Counter.Restart();
}
private static void OnAborted(object sender, EventArgs e)
{
try
{
WorldThread.Instance?.Abort();
DevTool.Instance?.Abort();
CleanUp("Abort");
WorldThread.DoQueuedActions();
}
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;
}
}
private static void OnTick(object sender, EventArgs e)
protected override void OnTick()
{
base.OnTick();
P = Game.Player.Character;
PlayerPosition = P.ReadPosition();
FPS = Game.FPS;
if (Game.IsLoading)
{
return;
}
if (!_gameLoaded && (_gameLoaded = true))
{
#if !NON_INTERACTIVE
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
#endif
}
while (TaskQueue.TryDequeue(out var task))
{
try
{
task.Invoke();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
#if CEF
if (CefRunning)
{
CefManager.Tick();
}
#endif
if (!Networking.IsOnServer)
{
return;
}
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
Logger.Error(ex);
Log.Error(ex);
}
if (Game.TimeScale != 1.0f)
{
Game.TimeScale = 1;
@ -235,42 +159,42 @@ namespace RageCoop.Client
if (Networking.ShowNetworkInfo)
{
new ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0),
new ScaledText(new PointF(200, 0),
$"L: {Networking.Latency * 1000:N0}ms", 0.5f)
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30),
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(200, 30),
$"R: {NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f)
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60),
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(200, 60),
$"S: {NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f)
{ Alignment = Alignment.Center }.Draw();
{ Alignment = Alignment.Center }.Draw();
}
MainChat.Tick();
PlayerList.Tick();
if (!API.Config.EnableAutoRespawn)
{
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
Function.Call(Hash.TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
Call(PAUSE_DEATH_ARREST_RESTART, true);
Call(IGNORE_NEXT_RESTART, true);
Call(FORCE_GAME_STATE_PLAYING);
Call(TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
if (P.IsDead)
{
Function.Call(Hash.SET_FADE_OUT_AFTER_DEATH, false);
Call(SET_FADE_OUT_AFTER_DEATH, false);
if (P.Health != 1)
{
P.Health = 1;
Game.Player.WantedLevel = 0;
Logger.Debug("Player died.");
Log.Debug("Player died.");
API.Events.InvokePlayerDied();
}
GTA.UI.Screen.StopEffects();
Screen.StopEffects();
}
else
{
Function.Call(Hash.DISPLAY_HUD, true);
Call(DISPLAY_HUD, true);
}
}
else if (P.IsDead && !_lastDead)
@ -282,27 +206,31 @@ namespace RageCoop.Client
Ticked++;
}
private void OnKeyUp(object sender, KeyEventArgs e)
protected override void OnKeyUp(GTA.KeyEventArgs e)
{
base.OnKeyUp(e);
#if CEF
if (CefRunning)
{
CefManager.KeyUp(e.KeyCode);
}
#endif
}
private static void OnKeyDown(object sender, KeyEventArgs e)
protected unsafe override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (MainChat.Focused)
{
MainChat.OnKeyDown(e.KeyCode);
return;
}
#if CEF
if (CefRunning)
{
CefManager.KeyDown(e.KeyCode);
}
#endif
if (Networking.IsOnServer)
{
if (Voice.WasInitialized())
@ -322,15 +250,15 @@ namespace RageCoop.Client
if (Game.IsControlPressed(Control.FrontendPause))
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU,
Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
Call(ACTIVATE_FRONTEND_MENU,
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU,
Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
Call(ACTIVATE_FRONTEND_MENU,
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
}
@ -423,25 +351,27 @@ namespace RageCoop.Client
API.QueueAction(() =>
{
WorldThread.Traffic(!Settings.DisableTraffic);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
CoopMenu.ConnectedMenuSetting();
MainChat.Init();
Notification.Show("~g~Connected!");
});
Logger.Info(">> Connected <<");
Log.Info(">> Connected <<");
}
private static readonly object _cleanupLock = new();
public static void CleanUp(string reason)
{
if (reason != "Abort")
lock (_cleanupLock)
{
Logger.Info($">> Disconnected << reason: {reason}");
API.QueueAction(() => { Notification.Show("~r~Disconnected: " + reason); });
}
API.QueueAction(() =>
{
if (reason != "Abort")
{
Log.Info($">> Disconnected << reason: {reason}");
Notification.Show("~r~Disconnected: " + reason);
}
if (MainChat.Focused)
{
MainChat.Focused = false;
@ -451,23 +381,24 @@ namespace RageCoop.Client
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
LocalPlayerID = default;
Resources.Unload();
});
Memory.RestorePatches();
MainRes.Unload();
Memory.RestorePatches();
#if CEF
if (CefRunning)
{
CefManager.CleanUp();
}
#endif
HookManager.CleanUp();
DownloadManager.Cleanup();
Voice.ClearAll();
DownloadManager.Cleanup();
Voice.ClearAll();
Networking.Peer?.Dispose();
Networking.Peer = null;
}
}
#if !NON_INTERACTIVE
#endif
}
}

View File

@ -20,7 +20,7 @@ namespace RageCoop.Client.Menus
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "MAIN")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static PopUp PopUp = new PopUp
@ -46,7 +46,7 @@ namespace RageCoop.Client.Menus
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) =>
{
Networking.ToggleConnection(Main.Settings.LastServerAddress);
Networking.ToggleConnection(Settings.LastServerAddress);
};
@ -95,9 +95,9 @@ namespace RageCoop.Client.Menus
scaleform.CallFunction("CREATE_CONTAINER");
scaleform.CallFunction("SET_DATA_SLOT", 0,
Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
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");
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
scaleform.Render2D();
if (Game.IsControlJustPressed(Control.FrontendAccept))
@ -122,7 +122,7 @@ namespace RageCoop.Client.Menus
var newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
if (!string.IsNullOrWhiteSpace(newUsername))
{
Main.Settings.Username = newUsername;
Settings.Username = newUsername;
Util.SaveSettings();
_usernameItem.AltTitle = newUsername;
@ -132,7 +132,7 @@ namespace RageCoop.Client.Menus
private static void _passwordActivated(object sender, EventArgs e)
{
var newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
Main.Settings.Password = newPass;
Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
@ -142,7 +142,7 @@ namespace RageCoop.Client.Menus
var newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
{
Main.Settings.LastServerAddress = newServerIp;
Settings.LastServerAddress = newServerIp;
Util.SaveSettings();
ServerIpItem.AltTitle = newServerIp;
@ -174,20 +174,20 @@ namespace RageCoop.Client.Menus
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username")
{ AltTitle = Main.Settings.Username };
{ AltTitle = Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password")
{ AltTitle = new string('*', Main.Settings.Password.Length) };
{ AltTitle = new string('*', Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP")
{ AltTitle = Main.Settings.LastServerAddress };
{ AltTitle = Settings.LastServerAddress };
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.Version)
Main.ModVersion)
{ LeftBadge = new ScaledTexture("commonmenu", "shop_new_star") };
#endregion

View File

@ -1,10 +1,7 @@
using System;
using System.Drawing;
using GTA;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Client.GUI;
using RageCoop.Client.Loader;
namespace RageCoop.Client
{
@ -13,17 +10,15 @@ namespace RageCoop.Client
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeMenu DiagnosticMenu = new NativeMenu("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : 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");
@ -33,14 +28,6 @@ namespace RageCoop.Client
private static readonly NativeCheckboxItem ShowNetworkInfoItem =
new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
private static readonly NativeCheckboxItem DxHookTest =
new NativeCheckboxItem("Enable D3D11 hook", false);
private static readonly NativeCheckboxItem CefTest =
new NativeCheckboxItem("Test CEF overlay", false);
private static CefClient _testCef;
static DebugMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
@ -51,10 +38,20 @@ namespace RageCoop.Client
{
DiagnosticMenu.Clear();
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
foreach (var pair in Debug.TimeStamps)
DiagnosticMenu.Add(
new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
// foreach (var pair in Debug.TimeStamps)
// DiagnosticMenu.Add(
// new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
};
ShowOwnerItem.CheckboxChanged += (s, e) =>
{
Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
Util.SaveSettings();
};
#if DEBUG
SimulatedLatencyItem.Activated += (s, e) =>
{
try
@ -65,61 +62,14 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error(ex);
Log.Error(ex);
}
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
};
ShowOwnerItem.CheckboxChanged += (s, e) =>
{
Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
Util.SaveSettings();
};
DxHookTest.CheckboxChanged += Hook;
CefTest.CheckboxChanged += CefTestChange;
;
ReloadItem.Activated += ReloadDomain;
Menu.Add(SimulatedLatencyItem);
#endif
Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.Add(ReloadItem);
Menu.AddSubMenu(DiagnosticMenu);
Menu.Add(DxHookTest);
Menu.Add(CefTest);
}
private static void CefTestChange(object sender, EventArgs e)
{
if (CefTest.Checked)
{
_testCef = CefManager.CreateClient(new Size(640, 480));
_testCef.Scale = 0.8f;
_testCef.Opacity = 128;
Script.Wait(2000);
_testCef.Controller.LoadUrl("https://ragecoop.online/");
CefManager.ActiveClient = _testCef;
}
else
{
CefManager.DestroyClient(_testCef);
}
DxHookTest.Checked = HookManager.Hooked;
}
private static void Hook(object sender, EventArgs e)
{
if (DxHookTest.Checked)
HookManager.Initialize();
else
HookManager.CleanUp();
}
private static void ReloadDomain(object sender, EventArgs e)
{
LoaderContext.RequestUnload();
}
}
}

View File

@ -7,8 +7,8 @@ using GTA.UI;
using LemonUI.Menus;
using Newtonsoft.Json;
using RageCoop.Core;
using static RageCoop.Client.Shared;
using Console = GTA.Console;
namespace RageCoop.Client
{
@ -17,7 +17,7 @@ namespace RageCoop.Client
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "DevTool", "Internal testing tools")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Show weapon bones");
@ -37,12 +37,12 @@ namespace RageCoop.Client
{
if (File.Exists(AnimationsDataPath))
{
var anims = JsonConvert.DeserializeObject<AnimDic[]>(File.ReadAllText(AnimationsDataPath));
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(AnimationsDataPath));
foreach (var anim in anims)
foreach (var a in anim.Animations)
if (Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, Main.P, anim.DictionaryName, a, 3))
if (Call<bool>(IS_ENTITY_PLAYING_ANIM, P, anim.DictionaryName, a, 3))
{
Console.Info(anim.DictionaryName + " : " + a);
Console.PrintInfo(anim.DictionaryName + " : " + a);
Notification.Show(anim.DictionaryName + " : " + a);
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Net;
using System.Net.Http;
using System.Threading;
using GTA.UI;
using LemonUI.Menus;
@ -22,7 +23,7 @@ namespace RageCoop.Client.Menus
internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
internal static NativeItem ResultItem = null;
@ -41,8 +42,7 @@ namespace RageCoop.Client.Menus
Menu.Add(ResultItem = new NativeItem("Loading..."));
// Prevent freezing
GetServersThread = new Thread(() => GetAllServers());
GetServersThread.Start();
GetServersThread = ThreadManager.CreateThread(() => GetAllServers(),"GetServers");
};
Menu.Closing += (object sender, CancelEventArgs e) => { CleanUpList(); };
}
@ -56,8 +56,10 @@ namespace RageCoop.Client.Menus
private static void GetAllServers()
{
List<ServerInfo> serverList = null;
var realUrl = Main.Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
var realUrl = Settings.MasterServer;
serverList = null;
try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
catch (Exception ex) { Log.Error(ex); }
// Need to be processed in main thread
API.QueueAction(() =>
@ -81,7 +83,7 @@ namespace RageCoop.Client.Menus
NativeItem tmpItem =
new NativeItem($"[{server.country}] {server.name}",
$"~b~{address}~s~~n~~g~Version {server.version}.x~s~")
{ AltTitle = $"[{server.players}/{server.maxPlayers}]" };
{ AltTitle = $"[{server.players}/{server.maxPlayers}]" };
tmpItem.Activated += (object sender, EventArgs e) =>
{
try
@ -103,7 +105,7 @@ namespace RageCoop.Client.Menus
CoopMenu.Menu.Visible = true;
#endif
Main.Settings.LastServerAddress = address;
Settings.LastServerAddress = address;
Util.SaveSettings();
}
catch (Exception ex)
@ -130,8 +132,8 @@ namespace RageCoop.Client.Menus
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new WebClient();
return client.DownloadString(url);
var client = new HttpClient();
return client.GetStringAsync(url).GetAwaiter().GetResult();
}
catch (Exception ex)
{

View File

@ -1,9 +1,10 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.Menus
{
@ -12,40 +13,40 @@ namespace RageCoop.Client.Menus
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? Alignment.Right : Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
private static readonly NativeCheckboxItem _disableTrafficItem =
new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only",
Main.Settings.DisableTraffic);
Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem =
new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
new NativeCheckboxItem("Flip menu", Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause",
"Don't freeze game time when Esc pressed", Main.Settings.DisableAlternatePause);
"Don't freeze game time when Esc pressed", 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);
Settings.Voice);
private static readonly NativeCheckboxItem _showBlip = new NativeCheckboxItem("Show player blip",
"Show other player's nametag on your screen, only effective if server didn't disable nametag display",
Main.Settings.ShowPlayerBlip);
Settings.ShowPlayerBlip);
private static readonly NativeCheckboxItem _showNametag = new NativeCheckboxItem("Show player nametag",
"Show other player's blip on map, can be overridden by server resource ",
Main.Settings.ShowPlayerNameTag);
Settings.ShowPlayerNameTag);
private static readonly NativeItem _menuKey =
new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString());
new NativeItem("Menu Key", "The key to open menu", Settings.MenuKey.ToString());
private static readonly NativeItem _passengerKey = new NativeItem("Passenger Key",
"The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
"The key to enter a vehicle as passenger", Settings.PassengerKey.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());
Settings.WorldVehicleSoftLimit.ToString());
static SettingsMenu()
{
@ -61,13 +62,12 @@ namespace RageCoop.Client.Menus
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
_showBlip.Activated += (s, e) =>
{
Main.Settings.ShowPlayerBlip = _showBlip.Checked;
Settings.ShowPlayerBlip = _showBlip.Checked;
Util.SaveSettings();
};
_showNametag.Activated += (s, e) =>
{
Main.Settings.ShowPlayerNameTag = _showNametag.Checked;
Util.SaveSettings();
API.Config.ShowPlayerNameTag = _showNametag.Checked;
};
Menu.Add(_disableTrafficItem);
@ -92,13 +92,13 @@ namespace RageCoop.Client.Menus
Voice.ClearAll();
}
Main.Settings.Voice = _disableVoice.Checked;
Settings.Voice = _disableVoice.Checked;
Util.SaveSettings();
}
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
{
Main.Settings.DisableAlternatePause = _disablePauseAlt.Checked;
Settings.DisableAlternatePause = _disablePauseAlt.Checked;
Util.SaveSettings();
}
@ -106,10 +106,10 @@ namespace RageCoop.Client.Menus
{
try
{
Main.Settings.WorldVehicleSoftLimit = int.Parse(
Settings.WorldVehicleSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.WorldVehicleSoftLimit.ToString(), 20));
_menuKey.AltTitle = Main.Settings.WorldVehicleSoftLimit.ToString();
Settings.WorldVehicleSoftLimit.ToString(), 20));
_menuKey.AltTitle = Settings.WorldVehicleSoftLimit.ToString();
Util.SaveSettings();
}
catch
@ -121,11 +121,11 @@ namespace RageCoop.Client.Menus
{
try
{
Main.Settings.MenuKey = (Keys)Enum.Parse(
Settings.MenuKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.MenuKey.ToString(), 20));
_menuKey.AltTitle = Main.Settings.MenuKey.ToString();
Settings.MenuKey.ToString(), 20));
_menuKey.AltTitle = Settings.MenuKey.ToString();
Util.SaveSettings();
}
catch
@ -137,11 +137,11 @@ namespace RageCoop.Client.Menus
{
try
{
Main.Settings.PassengerKey = (Keys)Enum.Parse(
Settings.PassengerKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.PassengerKey.ToString(), 20));
_passengerKey.AltTitle = Main.Settings.PassengerKey.ToString();
Settings.PassengerKey.ToString(), 20));
_passengerKey.AltTitle = Settings.PassengerKey.ToString();
Util.SaveSettings();
}
catch
@ -152,7 +152,7 @@ namespace RageCoop.Client.Menus
public static void DisableTrafficCheckboxChanged(object a, EventArgs b)
{
WorldThread.Traffic(!_disableTrafficItem.Checked);
Main.Settings.DisableTraffic = _disableTrafficItem.Checked;
Settings.DisableTraffic = _disableTrafficItem.Checked;
Util.SaveSettings();
}
@ -161,7 +161,7 @@ namespace RageCoop.Client.Menus
CoopMenu.Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
Main.Settings.FlipMenu = _flipMenuItem.Checked;
Settings.FlipMenu = _flipMenuItem.Checked;
Util.SaveSettings();
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using GTA;
using GTA.Native;
@ -74,7 +73,7 @@ namespace RageCoop.Client
if (!CurrentFocused) return;
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, 0);
Call(DISABLE_ALL_CONTROL_ACTIONS, 0);
}
public void AddMessage(string sender, string msg)
@ -132,6 +131,9 @@ namespace RageCoop.Client
StringBuilder receivingBuffer,
int bufferSize, uint flags, IntPtr kblayout);
[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);
public static string GetCharFromKey(Keys key, bool shift, bool altGr)
{
var buf = new StringBuilder(256);
@ -145,7 +147,7 @@ namespace RageCoop.Client
keyboardState[(int)Keys.Menu] = 0xff;
}
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, InputLanguage.CurrentInputLanguage.Handle);
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, GetKeyboardLayout(0));
return buf.ToString();
}
}

View File

@ -33,7 +33,7 @@ namespace RageCoop.Client
var packet = new Packets.FileTransferComplete();
packet.Deserialize(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}");
Log.Debug($"Finalizing download:{packet.ID}");
Complete(packet.ID);
// Inform the server that the download is completed
@ -48,13 +48,13 @@ namespace RageCoop.Client
try
{
Directory.CreateDirectory(ResourceFolder);
Main.Resources.Load(ResourceFolder, _resources.ToArray());
MainRes.Load(ResourceFolder, _resources.ToArray());
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.Loaded };
}
catch (Exception ex)
{
Main.Logger.Error("Error occurred when loading server resource");
Main.Logger.Error(ex);
Log.Error("Error occurred when loading server resource");
Log.Error(ex);
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.LoadFailed };
}
});
@ -68,13 +68,13 @@ namespace RageCoop.Client
public static bool AddFile(int id, string name, long length)
{
var path = $"{ResourceFolder}\\{name}";
Main.Logger.Debug($"Downloading file to {path} , id:{id}");
Log.Debug($"Downloading file to {path} , id:{id}");
if (!Directory.Exists(Directory.GetParent(path).FullName))
Directory.CreateDirectory(Directory.GetParent(path).FullName);
if (FileAlreadyExists(ResourceFolder, name, length))
{
Main.Logger.Debug($"File already exists! canceling download:{name}");
Log.Debug($"File already exists! canceling download:{name}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
return false;
}
@ -82,7 +82,7 @@ namespace RageCoop.Client
/*
if (!name.EndsWith(".zip"))
{
Main.Logger.Error($"File download blocked! [{name}]");
Log.Error($"File download blocked! [{name}]");
return false;
}
*/
@ -114,7 +114,6 @@ namespace RageCoop.Client
if (File.Exists(filePath))
{
if (new FileInfo(filePath).Length == length) return true;
// Delete the file because the length is wrong (maybe the file was updated)
File.Delete(filePath);
}
@ -129,7 +128,7 @@ namespace RageCoop.Client
if (InProgressDownloads.TryGetValue(id, out var file))
file.Stream.Write(chunk, 0, chunk.Length);
else
Main.Logger.Trace($"Received unhandled file chunk:{id}");
Log.Trace($"Received unhandled file chunk:{id}");
}
}
@ -139,12 +138,12 @@ namespace RageCoop.Client
{
InProgressDownloads.Remove(id);
f.Dispose();
Main.Logger.Info($"Download finished:{f.FileName}");
Log.Info($"Download finished:{f.FileName}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
}
else
{
Main.Logger.Error($"Download not found! {id}");
Log.Error($"Download not found! {id}");
}
}

View File

@ -27,12 +27,12 @@ namespace RageCoop.Client
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null ||
p.Connection.Status == NetConnectionStatus.Disconnected))
{
Main.Logger.Trace(
Log.Trace(
$"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.ID}");
var msg = Networking.Peer.CreateMessage();
new Packets.HolePunch
{
Puncher = Main.LocalPlayerID,
Puncher = LocalPlayerID,
Status = p.HolePunchStatus
}.Pack(msg);
Networking.Peer.SendUnconnectedMessage(msg,
@ -41,7 +41,7 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error(ex);
Log.Error(ex);
}
}
@ -49,33 +49,33 @@ namespace RageCoop.Client
{
if (PlayerList.Players.TryGetValue(p.TargetID, out var player))
{
Main.Logger.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
Log.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
player.InternalEndPoint = CoreUtils.StringToEndPoint(p.TargetInternal);
player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal);
player.ConnectWhenPunched = p.Connect;
}
else
{
Main.Logger.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
Log.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
}
}
public static void Punched(Packets.HolePunch p, IPEndPoint from)
{
Main.Logger.Debug($"HolePunch message received from:{from}, status:{p.Status}");
Log.Debug($"HolePunch message received from:{from}, status:{p.Status}");
if (PlayerList.Players.TryGetValue(p.Puncher, out var puncher))
{
Main.Logger.Debug("Puncher identified as: " + puncher.Username);
Log.Debug("Puncher identified as: " + puncher.Username);
puncher.HolePunchStatus = (byte)(p.Status + 1);
if (p.Status >= 3)
{
Main.Logger.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
Log.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
if (puncher.ConnectWhenPunched && (puncher.Connection == null ||
puncher.Connection.Status == NetConnectionStatus.Disconnected))
{
Main.Logger.Debug("Connecting to peer: " + from);
Log.Debug("Connecting to peer: " + from);
var msg = Networking.Peer.CreateMessage();
new Packets.P2PConnect { ID = Main.LocalPlayerID }.Pack(msg);
new Packets.P2PConnect { ID = LocalPlayerID }.Pack(msg);
puncher.Connection = Networking.Peer.Connect(from, msg);
Networking.Peer.FlushSendQueue();
}

View File

@ -14,25 +14,18 @@ namespace RageCoop.Client
{
internal static partial class Networking
{
public static CoopPeer Peer;
public static bool ShowNetworkInfo = false;
public static Security Security;
public static NetConnection ServerConnection;
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses =
new Dictionary<int, Action<PacketType, NetIncomingMessage>>();
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers =
new Dictionary<PacketType, Func<NetIncomingMessage, Packet>>();
internal static float SimulatedLatency = 0;
public static IPEndPoint _targetServerEP;
static Networking()
{
Security = new Security(Main.Logger);
Packets.CustomEvent.ResolveHandle = _resolveHandle;
}
public static CoopPeer Peer;
public static bool ShowNetworkInfo = false;
public static Security Security = new();
public static NetConnection ServerConnection;
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new();
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new();
internal static float SimulatedLatency = 0;
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
public static bool IsConnecting { get; private set; }
@ -42,35 +35,38 @@ namespace RageCoop.Client
PublicKey publicKey = null)
{
CoopMenu.Menu.Visible = false;
Peer?.Shutdown("Bye");
if (IsOnServer)
{
// ?
}
else if (IsConnecting)
if (IsConnecting)
{
_publicKeyReceived.Set();
IsConnecting = false;
Notification.Show("Connection has been canceled");
}
else
{
Peer?.Dispose();
API.QueueAction(() =>
Notification.Show("Connection has been canceled"));
Peer.Shutdown("bye");
}
else if (IsOnServer)
{
Peer.Shutdown("bye");
}
else
{
IsConnecting = true;
password = password ?? Main.Settings.Password;
username = username ?? Main.Settings.Username;
password ??= Settings.Password;
username ??= Settings.Username;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{
AutoFlushSendQueue = false,
SimulatedMinimumLatency = SimulatedLatency,
SimulatedRandomLatency = 0,
AcceptIncomingConnections = true,
MaximumConnections = 32,
PingInterval = 5
};
#if DEBUG
config.SimulatedMinimumLatency = SimulatedLatency;
config.SimulatedRandomLatency = 0;
#endif
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
@ -95,7 +91,7 @@ namespace RageCoop.Client
return;
}
Task.Run(() =>
ThreadManager.CreateThread(() =>
{
try
{
@ -103,7 +99,7 @@ namespace RageCoop.Client
// Ensure static constructor invocation
DownloadManager.Cleanup();
Peer = new CoopPeer(config);
Peer = new CoopPeer(config,Log);
Peer.OnMessageReceived += (s, m) =>
{
try
@ -112,7 +108,7 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error(ex);
Log.Error(ex);
}
};
API.QueueAction(() => { Notification.Show("~y~Trying to connect..."); });
@ -135,9 +131,9 @@ namespace RageCoop.Client
var outgoingMessage = Peer.CreateMessage();
var handshake = new Packets.Handshake
{
PedID = Main.LocalPlayerID,
PedID = LocalPlayerID,
Username = username,
ModVersion = Main.Version.ToString(),
ModVersion = Main.ModVersion.ToString(),
PasswordEncrypted = Security.Encrypt(password.GetBytes()),
InternalEndPoint = new IPEndPoint(CoreUtils.GetLocalAddress(ip[0]), Peer.Port)
};
@ -148,12 +144,12 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error("Cannot connect to server: ", ex);
API.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
Log.Error("Cannot connect to server: ", ex);
API.QueueAction(() => Notification.Show("~r~Cannot connect to server: " + ex.Message));
}
IsConnecting = false;
});
}, "Connect");
}
}
@ -184,7 +180,7 @@ namespace RageCoop.Client
};
PlayerList.SetPlayer(packet.PedID, packet.Username);
Main.Logger.Debug($"player connected:{p.Username}");
Log.Debug($"player connected:{p.Username}");
API.QueueAction(() =>
Notification.Show($"~h~{p.Username}~h~ connected."));
}

View File

@ -12,25 +12,7 @@ namespace RageCoop.Client
{
internal static partial class Networking
{
/// <summary>
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent" />
/// </summary>
private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
{
switch (t)
{
case 50:
return EntityPool.ServerProps[reader.ReadInt32()].MainProp?.Handle;
case 51:
return EntityPool.GetPedByID(reader.ReadInt32())?.MainPed?.Handle;
case 52:
return EntityPool.GetVehicleByID(reader.ReadInt32())?.MainVehicle?.Handle;
case 60:
return EntityPool.ServerBlips[reader.ReadInt32()].Handle;
default:
throw new ArgumentException("Cannot resolve server side argument: " + t);
}
};
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
@ -57,7 +39,7 @@ namespace RageCoop.Client
var p = new Packets.HandshakeSuccess();
p.Deserialize(response);
foreach (var player in p.Players) PlayerList.SetPlayer(player.ID, player.Username);
Main.Connected();
Connected();
}
else
{
@ -68,11 +50,11 @@ namespace RageCoop.Client
if (PlayerList.Players.TryGetValue(p.ID, out var player))
{
player.Connection = message.SenderConnection;
Main.Logger.Debug($"Direct connection to {player.Username} established");
Log.Debug($"Direct connection to {player.Username} established");
}
else
{
Main.Logger.Info(
Log.Info(
$"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
message.SenderConnection.Disconnect("eat poop");
}
@ -80,7 +62,7 @@ namespace RageCoop.Client
break;
case NetConnectionStatus.Disconnected:
if (message.SenderConnection == ServerConnection) Main.CleanUp(reason);
if (message.SenderConnection == ServerConnection) API.QueueAction(() => CleanUp(reason));
break;
}
@ -122,7 +104,7 @@ namespace RageCoop.Client
}
else
{
Main.Logger.Debug("Did not find a request handler of type: " + realType);
Log.Debug("Did not find a request handler of type: " + realType);
}
break;
@ -141,8 +123,8 @@ namespace RageCoop.Client
Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex);
Log.Error($"[{packetType}] {ex.Message}");
Log.Error(ex);
Peer.Shutdown($"Packet Error [{packetType}]");
}
@ -174,7 +156,7 @@ namespace RageCoop.Client
case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.VerboseDebugMessage:
Main.Logger.Trace(message.ReadString());
Log.Trace(message.ReadString());
break;
}
@ -223,7 +205,7 @@ namespace RageCoop.Client
API.QueueAction(() =>
{
Main.MainChat.AddMessage(packet.Username, packet.Message);
MainChat.AddMessage(packet.Username, packet.Message);
return true;
});
}
@ -231,7 +213,7 @@ namespace RageCoop.Client
case PacketType.Voice:
{
if (Main.Settings.Voice)
if (Settings.Voice)
{
var packet = new Packets.Voice();
packet.Deserialize(msg);
@ -239,7 +221,7 @@ namespace RageCoop.Client
var player = EntityPool.GetPedByID(packet.ID);
player.IsSpeaking = true;
player.LastSpeakingTime = Main.Ticked;
player.LastSpeakingTime = Ticked;
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
}
@ -295,7 +277,7 @@ namespace RageCoop.Client
{
var c = EntityPool.GetPedByID(packet.ID);
if (c == null)
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
// Log.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
var flags = packet.Flags;
c.ID = packet.ID;
@ -378,7 +360,7 @@ namespace RageCoop.Client
if (p == null)
{
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) return;
// Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
// Log.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
}

View File

@ -47,15 +47,15 @@ namespace RageCoop.Client
var veh = ped.CurrentVehicle?.GetSyncEntity() ??
ped.VehicleTryingToEnter?.GetSyncEntity() ?? ped.LastVehicle?.GetSyncEntity();
p.VehicleID = veh?.ID ?? 0;
if (p.VehicleID == 0) Main.Logger.Error("Invalid vehicle");
if (p.VehicleID == 0) Log.Error("Invalid vehicle");
if (p.Speed == 5)
p.Seat = ped.GetSeatTryingToEnter();
else
p.Seat = ped.SeatIndex;
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
{
veh.OwnerID = Main.LocalPlayerID;
SyncEvents.TriggerChangeOwner(veh.ID, Main.LocalPlayerID);
veh.OwnerID = LocalPlayerID;
SyncEvents.TriggerChangeOwner(veh.ID, LocalPlayerID);
}
}
@ -72,7 +72,7 @@ namespace RageCoop.Client
p.Clothes = ped.GetPedClothes();
p.ModelHash = ped.Model.Hash;
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
p.WeaponTint = (byte)Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
p.WeaponTint = (byte)Call<int>(GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
Blip b;
if (sp.IsPlayer)
@ -110,7 +110,7 @@ namespace RageCoop.Client
packet.Position = veh.ReadPosition();
packet.Velocity = veh.Velocity;
packet.Quaternion = veh.ReadQuaternion();
packet.RotationVelocity = veh.RotationVelocity;
packet.RotationVelocity = veh.WorldRotationVelocity;
packet.ThrottlePower = veh.ThrottlePower;
packet.BrakePower = veh.BrakePower;
v.LastSentStopWatch.Restart();
@ -122,7 +122,7 @@ namespace RageCoop.Client
byte secondaryColor = 0;
unsafe
{
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
Call<byte>(GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
packet.Flags |= VehicleDataFlags.IsFullSync;
@ -134,8 +134,8 @@ namespace RageCoop.Client
packet.ModelHash = veh.Model.Hash;
packet.EngineHealth = veh.EngineHealth;
packet.LockStatus = veh.LockStatus;
packet.LicensePlate = Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
packet.Livery = Function.Call<int>(Hash.GET_VEHICLE_LIVERY, veh);
packet.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
packet.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
if (v.MainVehicle == Game.Player.LastVehicle) packet.RadioStation = Util.GetPlayerRadioIndex();
if (packet.EngineHealth > v.LastEngineHealth) packet.Flags |= VehicleDataFlags.Repaired;
v.LastEngineHealth = packet.EngineHealth;
@ -154,14 +154,14 @@ namespace RageCoop.Client
public static void SendChatMessage(string message)
{
Peer.SendTo(new Packets.ChatMessage(s => Security.Encrypt(s.GetBytes()))
{ Username = Main.Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat,
{ Username = Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat,
NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
}
public static void SendVoiceMessage(byte[] buffer, int recorded)
{
SendSync(new Packets.Voice { ID = Main.LocalPlayerID, Buffer = buffer, Recorded = recorded },
SendSync(new Packets.Voice { ID = LocalPlayerID, Buffer = buffer, Recorded = recorded },
ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
}

View File

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

View File

@ -5,6 +5,7 @@ using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using Newtonsoft.Json;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
@ -28,12 +29,12 @@ namespace RageCoop.Client
if (Util.GetTickCount64() - _lastUpdate >= 1000) Update();
if (Util.GetTickCount64() - Pressed < 5000 && !Main.MainChat.Focused
if (Util.GetTickCount64() - Pressed < 5000 && !MainChat.Focused
#if !NON_INTERACTIVE
&& !CoopMenu.MenuPool.AreAnyVisible
#endif
)
Function.Call(Hash.DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
Call(DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
0.28f, 0.6f,
255, 255, 255, 255, 0);
@ -57,7 +58,7 @@ namespace RageCoop.Client
public static void SetPlayer(int id, string username, float latency = 0)
{
Main.Logger.Debug($"{id},{username},{latency}");
Log.Debug($"{id},{username},{latency}");
if (Players.TryGetValue(id, out var p))
{
p.Username = username;
@ -127,9 +128,21 @@ namespace RageCoop.Client
}
}
public class Player
internal class Player
{
internal float _latencyToServer;
internal bool ConnectWhenPunched { get; set; }
[JsonIgnore]
public Blip FakeBlip { get; internal set; }
[JsonIgnore]
public Vector3 Position { get; internal set; }
[JsonIgnore]
public SyncedPed Character { get; internal set; }
[JsonIgnore]
public NetConnection Connection { get; internal set; }
public byte HolePunchStatus { get; internal set; } = 1;
public bool IsHost { get; internal set; }
public string Username { get; internal set; }
@ -139,17 +152,15 @@ namespace RageCoop.Client
/// </summary>
public int ID { get; internal set; }
public int EntityHandle => Character?.MainPed?.Handle ?? 0;
public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; }
internal bool ConnectWhenPunched { get; set; }
public Blip FakeBlip { get; internal set; }
public Vector3 Position { get; internal set; }
public SyncedPed Character { get; internal set; }
/// <summary>
/// Player round-trip time in seconds, will be the rtt to server if not using P2P connection.
/// </summary>
public float Ping => Main.LocalPlayerID == ID ? Networking.Latency * 2 :
public float Ping => LocalPlayerID == ID ? Networking.Latency * 2 :
HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2;
public float PacketTravelTime => HasDirectConnection
@ -157,7 +168,6 @@ namespace RageCoop.Client
: Networking.Latency + _latencyToServer;
public bool DisplayNameTag { get; set; } = true;
public NetConnection Connection { get; internal set; }
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
}
}

View File

@ -1,4 +1,7 @@
using System.Reflection;

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Resources;
// General Information
@ -13,6 +16,7 @@ using System.Resources;
// Version information
[assembly: AssemblyVersion("1.6.0.24")]
[assembly: AssemblyFileVersion("1.6.0.24")]
[assembly: NeutralResourcesLanguageAttribute("en-US")]
[assembly: AssemblyVersion("1.6.0.28")]
[assembly: AssemblyFileVersion("1.6.0.28")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -1,207 +1,64 @@
<?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>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<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>
<OutPutPath>..\..\bin\$(Configuration)\Client\Scripts</OutPutPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<AllowedReferenceRelatedFileExtensions>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableDynamicLoading>true</EnableDynamicLoading>
<NoAotCompile>false</NoAotCompile>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Library</OutputType>
<OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowedReferenceRelatedFileExtensions>
.dll
.pdb
</AllowedReferenceRelatedFileExtensions>
<DocumentationFile>..\..\bin\API\RageCoop.Client.xml</DocumentationFile>
<DebugType>full</DebugType>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>DEBUG</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
</PropertyGroup>
<ItemGroup>
<Compile Include="Console.cs" />
<Compile Include="Debug.cs" />
<Compile Include="DevTools\DevTool.cs" />
<Compile Include="GUI\CefClient.cs" />
<Compile Include="GUI\CefManager.cs" />
<Compile Include="GUI\HookManager.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="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.Variables.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.Variables.cs" />
<Compile Include="Sync\EntityPool.cs" />
<Compile Include="Sync\SyncEvents.cs" />
<Compile Include="Sync\Voice.cs" />
<Compile Include="Util\Memory.cs" />
<Compile Include="Util\NativeCaller.cs" />
<Compile Include="Util\PedConfigFlags.cs" />
<Compile Include="Util\PedExtensions.cs" />
<Compile Include="Shared\Shared.cs" />
<Compile Include="Util\TaskType.cs" />
<Compile Include="Util\Util.cs" />
<Compile Include="Util\VehicleExtensions.cs" />
<Compile Include="Util\WeaponUtil.cs" />
<Compile Include="Util\Win32.cs" />
<Compile Include="WorldThread.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="BitmapUtil, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\libs\BitmapUtil.dll</HintPath>
</Reference>
<Reference Include="DXHook">
<HintPath>..\..\libs\DXHook\DXHook.dll</HintPath>
</Reference>
<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="NativeInvoker">
<HintPath>..\..\libs\NativeInvoker.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="RageCoop.Client.CefHost, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\bin\$(Configuration)\Client\SubProcess\ref\RageCoop.Client.CefHost.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.asi</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3, Version=3.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Drawing.Common, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Drawing.Common.4.5.0\lib\net461\System.Drawing.Common.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\.editorconfig">
<Link>.editorconfig</Link>
</None>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
</Content>
<Content Include="sharpdx_direct3d11_1_effects_x64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent Condition=" '$(DevEnvDir)' != '*Undefined*'">
"$(DevEnvDir)TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt"
</PostBuildEvent>
<PostBuildEvent Condition="'$(Configuration)' != 'API' AND '$(SolutionDir)' != '*Undefined*'">
xcopy "$(SolutionDir)Client\Data" "..\Data" /i /s
</PostBuildEvent>
<PreBuildEvent Condition=" '$(SolutionDir)' != '*Undefined*'">
dotnet build -c $(Configuration) "$(SolutionDir)Client\CefHost\RageCoop.Client.CefHost.csproj"
</PreBuildEvent>
</PropertyGroup>
<NoWarn>CS1591</NoWarn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<TrimmerRootDescriptor Include="Trimming.xml" />
<Compile Remove="GUI\**" />
<EmbeddedResource Remove="GUI\**" />
<None Remove="GUI\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Scripting\ClientScript.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="LemonUI.SHVDNC">
<HintPath>..\..\libs\LemonUI.SHVDNC.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
<ProjectReference Include="..\Scripting\RageCoop.Client.Scripting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NAudio" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\AssemblyInfo.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>AssemblyInfo.tt</DependentUpon>
</Compile>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(DevEnvDir)' != '*Undefined*'">
<Exec Command="&quot;$(DevEnvDir)TextTransform.exe&quot; -a !!BuildConfiguration!$(Configuration) &quot;$(ProjectDir)Properties\AssemblyInfo.tt&quot;" />
</Target>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(SolutionDir)' != '*Undefined*' and '$(Configuration)' != 'API'">
<Exec Command="xcopy &quot;$(SolutionDir)Client\Data&quot; &quot;$(OutDir)..\Data&quot; /i /s /y" />
</Target>
</Project>

View File

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

View File

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

View File

@ -1,41 +1,33 @@
#undef DEBUG
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using GTA;
using Lidgren.Network;
using Newtonsoft.Json;
using RageCoop.Client.Menus;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
[assembly: InternalsVisibleTo("CodeGen")] // For generating api bridge
namespace RageCoop.Client.Scripting
{
/// <summary>
/// </summary>
public class CustomEventReceivedArgs : EventArgs
{
/// <summary>
/// The event hash
/// </summary>
public int Hash { get; set; }
/// <summary>
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion
/// </summary>
public object[] Args { get; set; }
}
/// <summary>
/// Provides vital functionality to interact with RAGECOOP
/// </summary>
public static class API
internal static unsafe partial class API
{
#region INTERNAL
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers =
new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
internal static Dictionary<int, List<CustomEventHandler>> CustomEventHandlers =
new();
#endregion
@ -50,11 +42,11 @@ namespace RageCoop.Client.Scripting
/// </summary>
public static string Username
{
get => Main.Settings.Username;
get => Settings.Username;
set
{
if (Networking.IsOnServer || string.IsNullOrEmpty(value)) return;
Main.Settings.Username = value;
Settings.Username = value;
}
}
@ -77,6 +69,17 @@ namespace RageCoop.Client.Scripting
/// Get or set scale of player's blip
/// </summary>
public static float BlipScale { get; set; } = 1;
public static bool ShowPlayerNameTag
{
get => Settings.ShowPlayerNameTag;
set
{
if (value == ShowPlayerNameTag) return;
Settings.ShowPlayerNameTag = value;
Util.SaveSettings();
}
}
}
/// <summary>
@ -152,12 +155,26 @@ namespace RageCoop.Client.Scripting
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
{
var args = new CustomEventReceivedArgs { Hash = p.Hash, Args = p.Args };
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
// Log.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers))
handlers.ForEach(x => { x.Invoke(args); });
{
fixed (byte* pData = p.Payload)
{
foreach (var handler in handlers)
{
try
{
handler.Invoke(p.Hash, pData, p.Payload.Length);
}
catch (Exception ex)
{
Log.Error("InvokeCustomEvent", ex);
}
}
}
}
}
#endregion
@ -191,7 +208,7 @@ namespace RageCoop.Client.Scripting
/// <summary>
/// Check if the RAGECOOP chat is visible
/// </summary>
public static bool IsChatFocused => Main.MainChat.Focused;
public static bool IsChatFocused => MainChat.Focused;
/// <summary>
/// Check if the RAGECOOP list of players is visible
@ -201,18 +218,12 @@ namespace RageCoop.Client.Scripting
/// <summary>
/// Get the version of RAGECOOP
/// </summary>
public static Version CurrentVersion => Main.Version;
/// <summary>
/// Get a <see cref="Core.Logger" /> that RAGECOOP is currently using.
/// </summary>
/// <returns></returns>
public static Logger Logger => Main.Logger;
public static Version CurrentVersion => Main.ModVersion;
/// <summary>
/// Get all players indexed by their ID
/// </summary>
public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players);
public static Dictionary<int, Player> Players => new(PlayerList.Players);
#endregion
@ -260,56 +271,6 @@ namespace RageCoop.Client.Scripting
WorldThread.QueueAction(a);
}
/// <summary>
/// Connect to a server
/// </summary>
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
public static void Connect(string address)
{
if (Networking.IsOnServer || Networking.IsConnecting)
throw new InvalidOperationException("Cannot connect to server when another connection is active");
Networking.ToggleConnection(address);
}
/// <summary>
/// Disconnect from current server or cancel the connection attempt.
/// </summary>
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting) 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>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
public static void LocalChatMessage(string from, string message)
{
Main.MainChat.AddMessage(from, message);
}
/// <summary>
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="message"></param>
public static void SendChatMessage(string message)
{
Networking.SendChatMessage(message);
}
/// <summary>
/// Send an event and data to the server.
/// </summary>
@ -319,13 +280,7 @@ namespace RageCoop.Client.Scripting
/// types
/// </param>
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
{
Networking.Peer.SendTo(new Packets.CustomEvent
{
Args = args,
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
=> SendCustomEvent(CustomEventFlags.None, eventHash, args);
/// <summary>
/// Send an event and data to the server
@ -338,9 +293,12 @@ namespace RageCoop.Client.Scripting
/// </param>
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
{
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
{
Args = args,
Payload = writer.ToByteArray(writer.Position),
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
@ -350,19 +308,12 @@ namespace RageCoop.Client.Scripting
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">
/// An unique identifier of the event, you can hash your event name with
/// <see cref="Core.Scripting.CustomEvents.Hash(string)" />
/// An unique identifier of the event
/// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
{
lock (CustomEventHandlers)
{
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
handlers.Add(handler);
}
}
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
/// <summary>
/// </summary>
@ -375,9 +326,9 @@ namespace RageCoop.Client.Scripting
};
DownloadManager.DownloadCompleted += handler;
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest
{
Name = name
},
{
Name = name
},
p =>
{
if (p.Response != FileResponse.Loaded)
@ -388,6 +339,150 @@ namespace RageCoop.Client.Scripting
});
}
/// <summary>
/// Connect to a server
/// </summary>
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
[Remoting]
public static void Connect(string address)
{
if (Networking.IsOnServer || Networking.IsConnecting)
throw new InvalidOperationException("Cannot connect to server when another connection is active");
Networking.ToggleConnection(address);
}
/// <summary>
/// Disconnect from current server or cancel the connection attempt.
/// </summary>
[Remoting]
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
}
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
[Remoting]
public static List<ServerInfo> ListServers()
{
return JsonDeserialize<List<ServerInfo>>(
HttpHelper.DownloadString(Settings.MasterServer));
}
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
[Remoting]
public static void LocalChatMessage(string from, string message)
{
MainChat.AddMessage(from, message);
}
/// <summary>
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="message"></param>
[Remoting]
public static void SendChatMessage(string message)
{
if (!IsOnServer)
throw new InvalidOperationException("Not on server");
Networking.SendChatMessage(message);
}
/// <summary>
/// Get the <see cref="ClientResource"/> with this name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Remoting]
public static ClientResource GetResource(string name)
{
if (MainRes.LoadedResources.TryGetValue(name, out var resource))
return resource;
return null;
}
/// <summary>
/// Get <see cref="ClientResource"/> that contains the specified file or directory
/// </summary>
/// <returns></returns>
[Remoting]
public static ClientResource GetResourceFromPath(string path)
{
path = Path.GetFullPath(path).ToLower();
foreach (var res in MainRes.LoadedResources.Values)
{
if (res.ScriptsDirectory.ToLower() == path || res.Files.Any(file => file.Value.FullPath.ToLower() == path))
return res;
}
return null;
}
[Remoting(GenBridge = false)]
public static object GetProperty(string name)
=> typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting(GenBridge = false)]
public static void SetProperty(string name, string jsonVal)
{
var prop = typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public); ;
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
[Remoting]
public static object GetConfig(string name)
=> typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting]
public static void SetConfig(string name, string jsonVal)
{
var prop = typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public);
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">
/// An unique identifier of the event
/// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
[Remoting]
public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler)
{
if (handler.Directory == default)
throw new ArgumentException("Script directory not specified");
if (handler.FunctionPtr == default)
throw new ArgumentException("Function pointer not specified");
lock (CustomEventHandlers)
{
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
CustomEventHandlers.Add(hash, handlers = new());
handlers.Add(handler);
}
}
#endregion
}
}

View File

@ -35,34 +35,34 @@ namespace RageCoop.Client.Scripting
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied,
e => { Notification.Show($"~h~{e.Args[0]}~h~ died."); });
Task.Run(() =>
{
while (true)
{
if (_isHost)
API.QueueAction(() =>
{
unsafe
{
var time = World.CurrentTimeOfDay;
var weather1 = default(int);
var weather2 = default(int);
var percent2 = default(float);
Function.Call(Hash.GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2);
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes,
time.Seconds, weather1, weather2, percent2);
}
});
ThreadManager.CreateThread(() =>
{
while (!IsUnloading)
{
if (Networking.IsOnServer && _isHost)
API.QueueAction(() =>
{
unsafe
{
var time = World.CurrentTimeOfDay;
var weather1 = default(int);
var weather2 = default(int);
var percent2 = default(float);
Call(GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2);
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes,
time.Seconds, weather1, weather2, percent2);
}
});
Thread.Sleep(1000);
}
});
Thread.Sleep(1000);
}
}, "BaseScript");
}
private static void WeatherTimeSync(CustomEventReceivedArgs e)
{
World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
Function.Call(Hash.SET_CURR_WEATHER_STATE, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
Call(SET_CURR_WEATHER_STATE, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
}
private static void SetDisplayNameTag(CustomEventReceivedArgs e)
@ -103,7 +103,7 @@ namespace RageCoop.Client.Scripting
{
ID = (int)e.Args[0],
MainVehicle = veh,
OwnerID = Main.LocalPlayerID
OwnerID = LocalPlayerID
};
EntityPool.Add(v);
}
@ -173,7 +173,7 @@ namespace RageCoop.Client.Scripting
EntityPool.ServerProps.Add(id, prop = new SyncedProp(id));
}
prop.LastSynced = Main.Ticked + 1;
prop.LastSynced = Ticked + 1;
prop.Model = (Model)e.Args[1];
prop.Position = (Vector3)e.Args[2];
prop.Rotation = (Vector3)e.Args[3];
@ -193,11 +193,10 @@ namespace RageCoop.Client.Scripting
if (returnType == TypeCode.Empty)
{
Function.Call(hash, arguments.ToArray());
Call(hash, arguments.ToArray());
return;
}
var t = returnType;
var id = (int)e.Args[1];
@ -205,59 +204,59 @@ namespace RageCoop.Client.Scripting
{
case TypeCode.Boolean:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<bool>(hash, arguments.ToArray()));
Call<bool>(hash, arguments.ToArray()));
break;
case TypeCode.Byte:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<byte>(hash, arguments.ToArray()));
Call<byte>(hash, arguments.ToArray()));
break;
case TypeCode.Char:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<char>(hash, arguments.ToArray()));
Call<char>(hash, arguments.ToArray()));
break;
case TypeCode.Single:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<float>(hash, arguments.ToArray()));
Call<float>(hash, arguments.ToArray()));
break;
case TypeCode.Double:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<double>(hash, arguments.ToArray()));
Call<double>(hash, arguments.ToArray()));
break;
case TypeCode.Int16:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<short>(hash, arguments.ToArray()));
Call<short>(hash, arguments.ToArray()));
break;
case TypeCode.Int32:
API.SendCustomEvent(CustomEvents.NativeResponse, id, Function.Call<int>(hash, arguments.ToArray()));
API.SendCustomEvent(CustomEvents.NativeResponse, id, Call<int>(hash, arguments.ToArray()));
break;
case TypeCode.Int64:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<long>(hash, arguments.ToArray()));
Call<long>(hash, arguments.ToArray()));
break;
case TypeCode.String:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<string>(hash, arguments.ToArray()));
Call<string>(hash, arguments.ToArray()));
break;
case TypeCode.UInt16:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<ushort>(hash, arguments.ToArray()));
Call<ushort>(hash, arguments.ToArray()));
break;
case TypeCode.UInt32:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<uint>(hash, arguments.ToArray()));
Call<uint>(hash, arguments.ToArray()));
break;
case TypeCode.UInt64:
API.SendCustomEvent(CustomEvents.NativeResponse, id,
Function.Call<ulong>(hash, arguments.ToArray()));
Call<ulong>(hash, arguments.ToArray()));
break;
}
}
@ -288,7 +287,7 @@ namespace RageCoop.Client.Scripting
case string stuff:
return stuff;
default:
return null;
return default;
}
}
}

View File

@ -4,10 +4,6 @@ using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
{
/// <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.
/// </summary>
public abstract class ClientScript : Script
{
/// <summary>
@ -24,16 +20,5 @@ namespace RageCoop.Client.Scripting
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
/// </summary>
public Logger Logger => CurrentResource.Logger;
/// <summary>
/// This method would be called from main thread, right after all script constructors are invoked.
/// </summary>
public abstract void OnStart();
/// <summary>
/// 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>
public abstract void OnStop();
}
}

View File

@ -1,62 +1,25 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using ICSharpCode.SharpZipLib.Zip;
using RageCoop.Client.Loader;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
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
{
public static string TempPath;
public static string TempPath = Path.Combine(Path.GetTempPath(), "RageCoop");
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources =
new ConcurrentDictionary<string, ClientResource>();
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new();
static Resources()
{
TempPath = Path.Combine(Path.GetTempPath(), "RageCoop");
if (Directory.Exists(TempPath))
try
{
@ -70,52 +33,45 @@ namespace RageCoop.Client.Scripting
Directory.CreateDirectory(TempPath);
}
public Resources()
{
Logger = Main.Logger;
}
private Logger Logger { get; }
public void Load(string path, string[] zips)
public unsafe 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)}");
Log?.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(() =>
{
foreach (var res in LoadedResources)
Directory.GetFiles(res.Value.ScriptsDirectory, "*.dll", SearchOption.AllDirectories)
.ForEach(x => ScriptDomain.CurrentDomain.StartScripts(x));
SetupScripts();
});
});
}
public void Unload()
public unsafe void Unload()
{
StopScripts();
var dirs = LoadedResources.Values.Select(x => x.ScriptsDirectory);
foreach (var dir in dirs)
{
SHVDN.Core.RuntimeController.RequestUnload(dir);
}
// Unregister associated handler
foreach (var handlers in API.CustomEventHandlers.Values)
{
foreach (var handler in handlers.ToArray())
{
if (dirs.Contains(handler.Directory, StringComparer.OrdinalIgnoreCase))
{
handlers.Remove(handler);
Log.Debug($"Unregistered handler from script directory {handler.Directory}");
}
}
}
LoadedResources.Clear();
LoaderContext.RequestUnload();
}
private ClientResource Unpack(string zipPath, string dataFolderRoot)
private unsafe ClientResource 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(TempPath, Path.GetFileNameWithoutExtension(zipPath))
@ -131,89 +87,26 @@ namespace RageCoop.Client.Scripting
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
r.Files.Add(dir, new ResourceFile
r.Files.Add(dir, new ClientFile
{
IsDirectory = true,
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/'),
FullPath = dir
});
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
var rfile = new ClientFile
{
GetStream = () => { return new FileStream(file, FileMode.Open, FileAccess.Read); },
IsDirectory = false,
Name = relativeName
Name = relativeName,
FullPath = file
};
r.Files.Add(relativeName, rfile);
}
SHVDN.Core.RuntimeController.RequestLoad(scriptsDir);
LoadedResources.TryAdd(r.Name, r);
return 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

@ -6,11 +6,9 @@ namespace RageCoop.Client
{
internal class Security
{
private readonly Logger Logger;
public Security(Logger logger)
public Security()
{
Logger = logger;
ClientAes.GenerateKey();
ClientAes.GenerateIV();
}

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

@ -0,0 +1,48 @@
global using GTA;
global using GTA.Native;
global using static GTA.Native.Function;
global using static GTA.Native.Hash;
global using static RageCoop.Client.Shared;
global using static RageCoop.Client.Main;
global using Console = GTA.Console;
global using static RageCoop.Core.Shared;
using System.IO;
using System;
using SHVDN;
using System.Diagnostics;
namespace RageCoop.Client
{
internal static class Shared
{
private static string GetBasePath()
{
FileInfo info;
string realScriptDir = Directory.GetParent(Instance.FilePath).FullName;
nextTarget:
info = new(realScriptDir);
if (info.LinkTarget != null)
{
realScriptDir = info.LinkTarget;
goto nextTarget;
}
if (Path.GetFileName(realScriptDir).ToLower() != "scripts")
throw new FileNotFoundException($"Unexpected link target {realScriptDir}");
var baseDir = Directory.GetParent(realScriptDir).FullName;
Logger.Debug($"Base directory is {baseDir}");
return baseDir;
}
public static string BasePath = GetBasePath();
public static string DataPath = Path.Combine(BasePath, "Data");
public static string SettingsPath = Path.Combine(DataPath, "Setting.json");
public static string VehicleWeaponDataPath = Path.Combine(DataPath, "VehicleWeapons.json");
public static string WeaponFixDataPath = Path.Combine(DataPath, "WeaponFixes.json");
public static string WeaponInfoDataPath = Path.Combine(DataPath, "Weapons.json");
public static string AnimationsDataPath = Path.Combine(DataPath, "Animations.json");
public static string CefSubProcessPath = Path.Combine(BasePath, "SubProcess", "RageCoop.Client.CefHost.exe");
}
}

View File

@ -1,19 +0,0 @@
using System.IO;
namespace RageCoop.Client
{
internal static class Shared
{
public static string BasePath = "RageCoop";
public static string DataPath = Path.Combine(BasePath, "Data");
public static string LogPath = Path.Combine(DataPath, "RageCoop.Client.log");
public static string SettingsPath = Path.Combine(DataPath, "Setting.json");
public static string VehicleWeaponDataPath = Path.Combine(DataPath, "VehicleWeapons.json");
public static string WeaponFixDataPath = Path.Combine(DataPath, "WeaponFixes.json");
public static string WeaponInfoDataPath = Path.Combine(DataPath, "Weapons.json");
public static string AnimationsDataPath = Path.Combine(DataPath, "Animations.json");
public static string CefSubProcessPath = Path.Combine(BasePath, "SubProcess", "RageCoop.Client.CefHost.exe");
}
}

View File

@ -12,22 +12,22 @@ namespace RageCoop.Client
if (speaking)
{
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mic_chatter", "mp_facial");
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mic_chatter", "mp_facial");
return;
}
switch (MainPed.Gender)
{
case Gender.Male:
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@gen_male@variations@normal");
break;
case Gender.Female:
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@gen_female@variations@normal");
break;
default:
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@mime@variations@normal");
break;
}
@ -41,12 +41,12 @@ namespace RageCoop.Client
if (ourAnim != null && animDict != null)
{
var flag = AnimationFlags.Loop;
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
{
if (LoadAnim(animDict) == null) return;
MainPed.Task.ClearAll();
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
Call(TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
}
}
}
@ -149,9 +149,9 @@ namespace RageCoop.Client
private string LoadAnim(string anim)
{
if (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
if (!Call<bool>(HAS_ANIM_DICT_LOADED, anim))
{
Function.Call(Hash.REQUEST_ANIM_DICT, anim);
Call(REQUEST_ANIM_DICT, anim);
return null;
}

View File

@ -27,7 +27,7 @@ namespace RageCoop.Client
p.CanWrithe = false;
p.IsOnlyDamagedByPlayer = false;
MainPed = p;
OwnerID = Main.LocalPlayerID;
OwnerID = LocalPlayerID;
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
}
@ -38,7 +38,7 @@ namespace RageCoop.Client
internal SyncedPed(int id)
{
ID = id;
LastSynced = Main.Ticked;
LastSynced = Ticked;
}
internal override void Update()
@ -67,7 +67,7 @@ namespace RageCoop.Client
if (!CreateCharacter())
return;
if (!Main.Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255;
if (!Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255;
if ((byte)BlipColor == 255 && PedBlip != null)
{
PedBlip.Delete();
@ -134,7 +134,7 @@ namespace RageCoop.Client
if (IsSpeaking)
{
if (Main.Ticked - LastSpeakingTime < 10)
if (Ticked - LastSpeakingTime < 10)
{
DisplaySpeaking(true);
}
@ -147,13 +147,13 @@ namespace RageCoop.Client
}
}
LastUpdated = Main.Ticked;
LastUpdated = Ticked;
}
private void RenderNameTag()
{
if (!Owner.DisplayNameTag || !Main.Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible ||
!MainPed.IsInRange(Main.PlayerPosition, 40f)) return;
if (!Owner.DisplayNameTag || !Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible ||
!MainPed.IsInRange(PlayerPosition, 40f)) return;
var targetPos = MainPed.Bones[Bone.IKHead].Position + Vector3.WorldUp * 0.5f;
Point toDraw = default;
@ -175,7 +175,7 @@ namespace RageCoop.Client
{
if (MainPed.Exists())
{
// Main.Logger.Debug($"Removing ped {ID}. Reason:CreateCharacter");
// Log.Debug($"Removing ped {ID}. Reason:CreateCharacter");
MainPed.Kill();
MainPed.MarkAsNoLongerNeeded();
MainPed.Delete();
@ -204,17 +204,17 @@ namespace RageCoop.Client
MainPed.CanWrithe = false;
MainPed.CanBeDraggedOutOfVehicle = true;
MainPed.IsOnlyDamagedByPlayer = false;
MainPed.RelationshipGroup = Main.SyncedPedsGroup;
MainPed.RelationshipGroup = SyncedPedsGroup;
MainPed.IsFireProof = false;
MainPed.IsExplosionProof = false;
Function.Call(Hash.SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
Function.Call(Hash.SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
// Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
Call(SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Call(SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
Call(SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
Call(SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Call(SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
// Call(_SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Call(SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
@ -244,7 +244,7 @@ namespace RageCoop.Client
private void SetClothes()
{
for (byte i = 0; i < 12; i++)
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i],
Call(SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i],
(int)Clothes[i + 12], (int)Clothes[i + 24]);
_lastClothes = Clothes;
}
@ -257,12 +257,12 @@ namespace RageCoop.Client
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Quaternion = Rotation.ToQuaternion();
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
{
// 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);
Call(TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
}
return;
@ -293,12 +293,12 @@ namespace RageCoop.Client
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Quaternion = Rotation.ToQuaternion();
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person",
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person",
"chute_idle_right", 3))
{
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);
Call(TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
}
return;
@ -321,7 +321,7 @@ namespace RageCoop.Client
_currentAnimation[1] = anim;
}
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", anim, 3))
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", anim, 3))
MainPed.Task.PlayAnimation("laddersbase", anim, 8f, -1, AnimationFlags.Loop);
}
else
@ -334,7 +334,7 @@ namespace RageCoop.Client
_currentAnimation[1] = "base_left_hand_up";
}
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase",
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase",
"base_left_hand_up", 3))
MainPed.Task.PlayAnimation("laddersbase", "base_left_hand_up", 8f, -1, AnimationFlags.Loop);
}
@ -346,7 +346,7 @@ namespace RageCoop.Client
_currentAnimation[1] = "climb_up";
}
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up",
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up",
3)) MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
}
}
@ -373,8 +373,8 @@ namespace RageCoop.Client
if (!IsVaulting && MainPed.IsVaulting) MainPed.Task.ClearAllImmediately();
if (IsOnFire && !MainPed.IsOnFire)
Function.Call(Hash.START_ENTITY_FIRE, MainPed);
else if (!IsOnFire && MainPed.IsOnFire) Function.Call(Hash.STOP_ENTITY_FIRE, MainPed);
Call(START_ENTITY_FIRE, MainPed);
else if (!IsOnFire && MainPed.IsOnFire) Call(STOP_ENTITY_FIRE, MainPed);
if (IsJumping)
{
@ -397,7 +397,7 @@ namespace RageCoop.Client
if (!_lastRagdoll)
{
_lastRagdoll = true;
_lastRagdollTime = Main.Ticked;
_lastRagdollTime = Ticked;
}
return;
@ -432,7 +432,7 @@ namespace RageCoop.Client
}
else if (IsInCover)
{
if (!_lastInCover) Function.Call(Hash.TASK_STAY_IN_COVER, MainPed.Handle);
if (!_lastInCover) Call(TASK_STAY_IN_COVER, MainPed.Handle);
_lastInCover = true;
if (IsAiming)
@ -476,16 +476,16 @@ namespace RageCoop.Client
}
else
{
var model = Function.Call<uint>(Hash.GET_WEAPONTYPE_MODEL, CurrentWeapon);
if (!Function.Call<bool>(Hash.HAS_MODEL_LOADED, model))
var model = Call<uint>(GET_WEAPONTYPE_MODEL, CurrentWeapon);
if (!Call<bool>(HAS_MODEL_LOADED, model))
{
Function.Call(Hash.REQUEST_MODEL, model);
Call(REQUEST_MODEL, model);
return;
}
if (WeaponObj?.Exists() == true)
WeaponObj.Delete();
MainPed.Weapons.RemoveAll();
WeaponObj = Entity.FromHandle(Function.Call<int>(Hash.CREATE_WEAPON_OBJECT, CurrentWeapon, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
WeaponObj = Entity.FromHandle(Call<int>(CREATE_WEAPON_OBJECT, CurrentWeapon, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
}
if (compChanged)
@ -494,17 +494,17 @@ namespace RageCoop.Client
{
if (comp.Value)
{
Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, WeaponObj.Handle, comp.Key);
Call(GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, WeaponObj.Handle, comp.Key);
}
}
_lastWeaponComponents = WeaponComponents;
}
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, WeaponObj.Handle, MainPed.Handle);
Call(GIVE_WEAPON_OBJECT_TO_PED, WeaponObj.Handle, MainPed.Handle);
_lastWeaponHash = CurrentWeapon;
}
if (Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon) != WeaponTint)
Function.Call<int>(Hash.SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon, WeaponTint);
if (Call<int>(GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon) != WeaponTint)
Call<int>(SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon, WeaponTint);
}
private void DisplayAiming()
@ -512,7 +512,7 @@ namespace RageCoop.Client
if (Velocity == default)
MainPed.Task.AimAt(AimCoords, 1000);
else
Function.Call(Hash.TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
Call(TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
Position.X + Velocity.X, Position.Y + Velocity.Y, Position.Z + Velocity.Z,
AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 0x3F000000, 0x40800000, false, 512, false, 0);
SmoothTransition();
@ -520,7 +520,7 @@ namespace RageCoop.Client
private void WalkTo()
{
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
Call(SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
var predictPosition = Predict(Position) + Velocity;
var range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
@ -533,7 +533,7 @@ namespace RageCoop.Client
if (nrange > 1.0f) nrange = 1.0f;
MainPed.Task.GoStraightTo(predictPosition);
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, nrange);
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, nrange);
}
_lastMoving = true;
@ -542,7 +542,7 @@ namespace RageCoop.Client
if (!MainPed.IsRunning || range > 0.50f)
{
MainPed.Task.RunTo(predictPosition, true);
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
}
_lastMoving = true;
@ -550,10 +550,10 @@ namespace RageCoop.Client
case 3:
if (!MainPed.IsSprinting || range > 0.75f)
{
Function.Call(Hash.TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X,
Call(TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X,
predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
Function.Call(Hash.SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f);
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
Call(SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f);
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
}
_lastMoving = true;
@ -599,7 +599,7 @@ namespace RageCoop.Client
MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
}
else if (Main.Ticked - _lastRagdollTime < 10)
else if (Ticked - _lastRagdollTime < 10)
{
}
else if (IsRagdoll)
@ -640,7 +640,7 @@ namespace RageCoop.Client
// localRagdoll
var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
if (force.Length() > 20) force = force.Normalized * 20;
MainPed.ApplyForce(force);
MainPed.ApplyWorldForceCenterOfMass(force, ForceType.InternalImpulse, true);
}
}
@ -655,7 +655,7 @@ namespace RageCoop.Client
(!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
if (MainPed.IsOnTurretSeat())
Function.Call(Hash.TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y,
Call(TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y,
AimCoords.Z);
// Drive-by
@ -663,13 +663,13 @@ namespace RageCoop.Client
{
if (IsAiming)
{
Function.Call(Hash.SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y,
Call(SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y,
AimCoords.Z);
if (!_lastDriveBy)
{
_lastDriveBy = true;
Function.Call(Hash.TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z,
1, 100, 1, FiringPattern.SingleShot);
Call(TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z,
100000, 100, 1, FiringPattern.SingleShot);
}
}
else if (_lastDriveBy || MainPed.IsTaskActive(TaskType.CTaskAimGunVehicleDriveBy))

View File

@ -16,7 +16,7 @@ namespace RageCoop.Client
/// <summary>
/// Indicates whether the current player is responsible for syncing this entity.
/// </summary>
public bool IsLocal => OwnerID == Main.LocalPlayerID;
public bool IsLocal => OwnerID == LocalPlayerID;
/// <summary>
/// Network ID for this entity
@ -41,7 +41,7 @@ namespace RageCoop.Client
/// <summary>
/// </summary>
public bool IsOutOfSync => Main.Ticked - LastSynced > 200 && ID != 0;
public bool IsOutOfSync => Ticked - LastSynced > 200 && ID != 0;
internal bool IsReady => LastSynced > 0 || LastFullSynced == 0;
@ -60,7 +60,7 @@ namespace RageCoop.Client
internal void PauseUpdate(ulong frames)
{
LastUpdated = Main.Ticked + frames;
LastUpdated = Ticked + frames;
}
protected Vector3 Predict(Vector3 input)
@ -93,10 +93,10 @@ namespace RageCoop.Client
public void SetLastSynced(bool full)
{
LastSyncInterval = LastSentStopWatch.ElapsedMilliseconds;
LastSynced = Main.Ticked;
LastSynced = Ticked;
if (full)
{
LastFullSynced = Main.Ticked;
LastFullSynced = Ticked;
}
LastSyncedStopWatch.Restart();
}

View File

@ -107,7 +107,7 @@ namespace RageCoop.Client
MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position);
MainProjectile.Rotation = Rotation;
LastUpdated = Main.Ticked;
LastUpdated = Ticked;
}
private void CreateProjectile()
@ -125,7 +125,7 @@ namespace RageCoop.Client
Position = (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Shooter.Velocity +
Position;
var end = Position + Velocity;
Function.Call(Hash.SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z,
Call(SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z,
end.X, end.Y, end.Z, 0, 1, WeaponHash, owner?.Handle ?? 0, 1, 0, -1);
var ps = World.GetAllProjectiles();
MainProjectile = ps[ps.Length - 1];

View File

@ -39,7 +39,7 @@ namespace RageCoop.Client
MainProp.Position = Position;
MainProp.Rotation = Rotation;
MainProp.SetFrozen(true);
LastUpdated = Main.Ticked;
LastUpdated = Ticked;
}
}
}

View File

@ -49,9 +49,6 @@ namespace RageCoop.Client
#region FIXED-DATA
internal bool IsFlipped => IsMotorcycle ||
(Quaternion * Vector3.RelativeTop).Z - (Quaternion * Vector3.RelativeBottom).Z < 0.5;
internal bool IsMotorcycle;
internal bool IsAircraft;
internal bool HasRocketBoost;
@ -60,21 +57,14 @@ namespace RageCoop.Client
internal bool IsSubmarineCar;
internal bool IsDeluxo;
internal bool IsTrain;
internal Vector3 TopExtent;
internal Vector3 BottomExtent;
internal Vector3 FrontExtent;
internal Vector3 RearExtent;
internal Vector3 LeftExtent;
internal Vector3 RightExtent;
private const float RotCalMult = 0.2f;
private const float ExtentLength = 5;
private const float RotCalMult = 10f;
#endregion
#region PRIVATE
private byte[] _lastVehicleColors = { 0, 0 };
private Dictionary<int, int> _lastVehicleMods = new Dictionary<int, int>();
private Dictionary<int, int> _lastVehicleMods = new();
private bool _lastHornActive;
private bool _lastTransformed;
internal int _lastLivery = -1;

View File

@ -48,11 +48,11 @@ namespace RageCoop.Client
if (MainVehicle.IsOnFire)
{
if (!Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Function.Call(Hash.STOP_ENTITY_FIRE, MainVehicle);
if (!Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
}
else if (Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
{
Function.Call(Hash.START_ENTITY_FIRE, MainVehicle);
Call(START_ENTITY_FIRE, MainVehicle);
}
if (EngineRunning != MainVehicle.IsEngineRunning) MainVehicle.IsEngineRunning = EngineRunning;
@ -100,13 +100,13 @@ namespace RageCoop.Client
if (!_lastTransformed)
{
_lastTransformed = true;
Function.Call(Hash.TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
}
}
else if (_lastTransformed)
{
_lastTransformed = false;
Function.Call(Hash.TRANSFORM_TO_CAR, MainVehicle.Handle, false);
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
}
}
else if (IsDeluxo)
@ -115,7 +115,7 @@ namespace RageCoop.Client
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
}
Function.Call(Hash.SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
}
MainVehicle.LockStatus = LockStatus;
@ -125,7 +125,7 @@ namespace RageCoop.Client
if (Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
if (Colors != null && Colors != _lastVehicleColors)
{
Function.Call(Hash.SET_VEHICLE_COLOURS, MainVehicle, Colors[0], Colors[1]);
Call(SET_VEHICLE_COLOURS, MainVehicle, Colors[0], Colors[1]);
_lastVehicleColors = Colors;
}
@ -133,26 +133,26 @@ namespace RageCoop.Client
MainVehicle.EngineHealth = EngineHealth;
if (Mods != null && !Mods.Compare(_lastVehicleMods))
{
Function.Call(Hash.SET_VEHICLE_MOD_KIT, MainVehicle, 0);
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (var mod in Mods) MainVehicle.Mods[(VehicleModType)mod.Key].Index = mod.Value;
_lastVehicleMods = Mods;
}
if (Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
Function.Call(Hash.SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
if (Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
if (_lastLivery != Livery)
{
Function.Call(Hash.SET_VEHICLE_LIVERY, MainVehicle, Livery);
Call(SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery = Livery;
}
MainVehicle.SetDamageModel(DamageModel);
}
LastUpdated = Main.Ticked;
LastUpdated = Ticked;
}
private void DisplayVehicle(bool updated)
@ -174,18 +174,12 @@ namespace RageCoop.Client
if (distSquared > 0.03 * 0.03)
{
if (IsTrain || distSquared > 20 * 20) MainVehicle.Velocity = Velocity + cali;
else MainVehicle.ApplyForce(cali);
else MainVehicle.ApplyWorldForceCenterOfMass(cali, ForceType.InternalImpulse, true);
}
Quaternion predictedQuat = updated ? Quaternion :
Quaternion.Lerp(LastQuaternion, Quaternion, 1 + LastSyncedStopWatch.ElapsedMilliseconds / (float)LastSyncInterval);
var curQuat = MainVehicle.Quaternion;
MainVehicle.ApplyForce((predictedQuat * TopExtent - curQuat * TopExtent) * RotCalMult, TopExtent);
MainVehicle.ApplyForce((predictedQuat * FrontExtent - curQuat * FrontExtent) * RotCalMult, FrontExtent);
// MainVehicle.ApplyForce((predictedQuat * BottomExtent - curQuat * BottomExtent) * RotCalMult, BottomExtent);
// Calibrate rotation
var diff = MainVehicle.ReadQuaternion().Differentiate(Quaternion);
MainVehicle.WorldRotationVelocity = diff.ToEulerAngles()*RotCalMult;
}
private bool CreateVehicle()
@ -226,7 +220,7 @@ namespace RageCoop.Client
ID = EntityPool.RequestNewID();
MainVehicle = v;
MainVehicle.CanPretendOccupants = false;
OwnerID = Main.LocalPlayerID;
OwnerID = LocalPlayerID;
SetUpFixedData();
}
@ -242,13 +236,6 @@ namespace RageCoop.Client
IsSubmarineCar = MainVehicle.IsSubmarineCar;
IsDeluxo = MainVehicle.Model == 1483171323;
IsTrain = MainVehicle.IsTrain;
// var (rbl, ftr) = MainVehicle.Model.Dimensions;
FrontExtent = new Vector3(0, ExtentLength, 0);
RearExtent = -FrontExtent;
TopExtent = new Vector3(0, ExtentLength, 5);
BottomExtent = -TopExtent;
RightExtent = new Vector3(5, ExtentLength, 0);
LeftExtent = -RightExtent;
}
/// <summary>
@ -261,7 +248,7 @@ namespace RageCoop.Client
internal SyncedVehicle(int id)
{
ID = id;
LastSynced = Main.Ticked;
LastSynced = Ticked;
}
#endregion
@ -295,7 +282,7 @@ namespace RageCoop.Client
private void StartPedalingAnim(bool fast)
{
MainVehicle.Driver?.Task.PlayAnimation(PedalingAnimDict(), PedalingAnimName(fast), 8.0f, -8.0f, -1,
AnimationFlags.Loop | AnimationFlags.AllowRotation, 1.0f);
AnimationFlags.Loop | AnimationFlags.Secondary, 1.0f);
}
private void StopPedalingAnim(bool fast)

View File

@ -1,15 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using GTA;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using SHVDN;
namespace RageCoop.Client
{
internal class EntityPool
internal static unsafe class EntityPool
{
public static object PedsLock = new object();
#if BENCHMARK
@ -47,8 +49,8 @@ namespace RageCoop.Client
{
foreach (var ped in PedsByID.Values.ToArray())
{
if ((keepPlayer && ped.ID == Main.LocalPlayerID) ||
(keepMine && ped.OwnerID == Main.LocalPlayerID)) continue;
if ((keepPlayer && ped.ID == LocalPlayerID) ||
(keepMine && ped.OwnerID == LocalPlayerID)) continue;
RemovePed(ped.ID);
}
@ -57,7 +59,7 @@ namespace RageCoop.Client
foreach (var id in VehiclesByID.Keys.ToArray())
{
if (keepMine && VehiclesByID[id].OwnerID == Main.LocalPlayerID) continue;
if (keepMine && VehiclesByID[id].OwnerID == LocalPlayerID) continue;
RemoveVehicle(id);
}
@ -65,7 +67,7 @@ namespace RageCoop.Client
VehiclesByHandle.Clear();
foreach (var p in ProjectilesByID.Values.ToArray())
if (p.Shooter.ID != Main.LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
if (p.Shooter.ID != LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
p.MainProjectile.Delete();
ProjectilesByID.Clear();
ProjectilesByHandle.Clear();
@ -82,14 +84,10 @@ namespace RageCoop.Client
#region PEDS
public static SyncedPed GetPedByID(int id)
{
return PedsByID.TryGetValue(id, out var p) ? p : null;
}
=> PedsByID.TryGetValue(id, out var p) ? p : null;
public static SyncedPed GetPedByHandle(int handle)
{
return PedsByHandle.TryGetValue(handle, out var p) ? p : null;
}
=> PedsByHandle.TryGetValue(handle, out var p) ? p : null;
public static List<int> GetPedIDs()
{
@ -100,16 +98,16 @@ namespace RageCoop.Client
{
var p = Game.Player.Character;
// var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
// Function.Call(Hash.SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
var player = GetPedByID(Main.LocalPlayerID);
// Call(SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
var player = GetPedByID(LocalPlayerID);
if (player == null)
{
Main.Logger.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
Log.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
var c = new SyncedPed(p);
Main.LocalPlayerID = c.OwnerID = c.ID;
LocalPlayerID = c.OwnerID = c.ID;
Add(c);
Main.Logger.Debug($"Local player ID is:{c.ID}");
PlayerList.SetPlayer(c.ID, Main.Settings.Username);
Log.Debug($"Local player ID is:{c.ID}");
PlayerList.SetPlayer(c.ID, Settings.Username);
return true;
}
@ -157,7 +155,7 @@ namespace RageCoop.Client
if (p != null)
{
if (PedsByHandle.ContainsKey(p.Handle)) PedsByHandle.Remove(p.Handle);
// Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}");
// Log.Debug($"Removing ped {c.ID}. Reason:{reason}");
p.AttachedBlip?.Delete();
p.Kill();
p.Model.MarkAsNoLongerNeeded();
@ -177,14 +175,10 @@ namespace RageCoop.Client
#region VEHICLES
public static SyncedVehicle GetVehicleByID(int id)
{
return VehiclesByID.TryGetValue(id, out var v) ? v : null;
}
=> VehiclesByID.TryGetValue(id, out var v) ? v : null;
public static SyncedVehicle GetVehicleByHandle(int handle)
{
return VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
}
=> VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
public static List<int> GetVehicleIDs()
{
@ -214,7 +208,7 @@ namespace RageCoop.Client
if (veh != null)
{
if (VehiclesByHandle.ContainsKey(veh.Handle)) VehiclesByHandle.Remove(veh.Handle);
// Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
// Log.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
veh.AttachedBlip?.Delete();
veh.Model.MarkAsNoLongerNeeded();
veh.MarkAsNoLongerNeeded();
@ -231,9 +225,7 @@ namespace RageCoop.Client
#region PROJECTILES
public static SyncedProjectile GetProjectileByID(int id)
{
return ProjectilesByID.TryGetValue(id, out var p) ? p : null;
}
=> ProjectilesByID.TryGetValue(id, out var p) ? p : null;
public static void Add(SyncedProjectile p)
{
@ -263,7 +255,7 @@ namespace RageCoop.Client
if (p != null)
{
if (ProjectilesByHandle.ContainsKey(p.Handle)) ProjectilesByHandle.Remove(p.Handle);
Main.Logger.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
Log.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
p.Explode();
}
@ -288,6 +280,16 @@ namespace RageCoop.Client
#endregion
#region SERVER OBJECTS
public static SyncedProp GetPropByID(int id)
=> ServerProps.TryGetValue(id, out var p) ? p : null;
public static Blip GetBlipByID(int id)
=> ServerBlips.TryGetValue(id, out var p) ? p : null;
#endregion
private static int vehStateIndex;
private static int pedStateIndex;
private static int vehStatesPerFrame;
@ -297,24 +299,28 @@ namespace RageCoop.Client
public static void DoSync()
{
UpdateTargets();
#if BENCHMARK
PerfCounter.Restart();
Debug.TimeStamps[TimeStamp.CheckProjectiles] = PerfCounter.ElapsedTicks;
#endif
var allPeds = World.GetAllPeds();
var allVehicles = World.GetAllVehicles();
var allProjectiles = World.GetAllProjectiles();
vehStatesPerFrame = allVehicles.Length * 2 / (int)Game.FPS + 1;
pedStatesPerFrame = allPeds.Length * 2 / (int)Game.FPS + 1;
var allPeds = NativeMemory.GetPedHandles();
var allVehicles = NativeMemory.GetVehicleHandles();
var allProjectiles = NativeMemory.GetProjectileHandles();
vehStatesPerFrame = allVehicles.Length * 2 / (int)FPS + 1;
pedStatesPerFrame = allPeds.Length * 2 / (int)FPS + 1;
#if BENCHMARK
Debug.TimeStamps[TimeStamp.GetAllEntities] = PerfCounter.ElapsedTicks;
#endif
lock (ProjectilesLock)
{
foreach (var p in allProjectiles)
if (!ProjectilesByHandle.ContainsKey(p.Handle))
Add(new SyncedProjectile(p));
if (!ProjectilesByHandle.ContainsKey(p))
Add(new SyncedProjectile(Projectile.FromHandle(p)));
foreach (var p in ProjectilesByID.Values.ToArray())
// Outgoing sync
@ -338,6 +344,7 @@ namespace RageCoop.Client
}
}
i = -1;
lock (PedsLock)
@ -346,18 +353,19 @@ namespace RageCoop.Client
foreach (var p in allPeds)
{
var c = GetPedByHandle(p.Handle);
if (c == null && p != Game.Player.Character)
var c = GetPedByHandle(p);
if (c == null && p != Game.Player.Character.Handle)
{
if (allPeds.Length > Main.Settings.WorldPedSoftLimit &&
p.PopulationType == EntityPopulationType.RandomAmbient && !p.IsInVehicle())
var type = Util.GetPopulationType(p);
if (allPeds.Length > Settings.WorldPedSoftLimit &&
type == EntityPopulationType.RandomAmbient && !Call<bool>(IS_PED_IN_ANY_VEHICLE, p, 0))
{
p.Delete();
Util.DeleteEntity(p);
continue;
}
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c = new SyncedPed(p);
// Log.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c = new SyncedPed((Ped)Entity.FromHandle(p));
Add(c);
}
@ -409,32 +417,35 @@ namespace RageCoop.Client
#endif
}
var check = Main.Ticked % 100 == 0;
var check = Ticked % 100 == 0;
i = -1;
lock (VehiclesLock)
{
foreach (var veh in allVehicles)
if (!VehiclesByHandle.ContainsKey(veh.Handle))
if (!VehiclesByHandle.ContainsKey(veh))
{
if (allVehicles.Length > Main.Settings.WorldVehicleSoftLimit)
var cveh = (Vehicle)Entity.FromHandle(veh);
if (cveh == null)
continue;
if (allVehicles.Length > Settings.WorldVehicleSoftLimit)
{
var type = veh.PopulationType;
var type = cveh.PopulationType;
if (type == EntityPopulationType.RandomAmbient || type == EntityPopulationType.RandomParked)
{
foreach (var p in veh.Occupants)
foreach (var p in cveh.Occupants)
{
p.Delete();
var c = GetPedByHandle(p.Handle);
if (c != null) RemovePed(c.ID, "ThrottleTraffic");
}
veh.Delete();
cveh.Delete();
continue;
}
}
// Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
// Log.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
Add(new SyncedVehicle(veh));
Add(new SyncedVehicle(cveh));
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.AddVehicles] = PerfCounter.ElapsedTicks;
@ -480,7 +491,7 @@ namespace RageCoop.Client
{
Networking.Targets = new List<NetConnection>(PlayerList.Players.Count) { Networking.ServerConnection };
foreach (var p in PlayerList.Players.Values.ToArray())
if (p.HasDirectConnection && p.Position.DistanceTo(Main.PlayerPosition) < 500)
if (p.HasDirectConnection && p.Position.DistanceTo(PlayerPosition) < 500)
Networking.Targets.Add(p.Connection);
}
@ -514,8 +525,8 @@ namespace RageCoop.Client
private static void SetBudget(int b)
{
Function.Call(Hash.SET_PED_POPULATION_BUDGET, b); // 0 - 3
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, b); // 0 - 3
Call(SET_PED_POPULATION_BUDGET, b); // 0 - 3
Call(SET_VEHICLE_POPULATION_BUDGET, b); // 0 - 3
}
public static string DumpDebug()

View File

@ -74,7 +74,7 @@ namespace RageCoop.Client
{
return;
// p = Game.Player.Character;
// Main.Logger.Warning("Failed to find owner for bullet");
// Log.Warning("Failed to find owner for bullet");
}
var damage = (int)p.GetWeaponDamage(weaponHash);

View File

@ -5,6 +5,7 @@ namespace RageCoop.Client
{
internal static class Voice
{
private static bool _stopping = false;
private static WaveInEvent _waveIn;
private static readonly BufferedWaveProvider _waveProvider =
@ -30,7 +31,8 @@ namespace RageCoop.Client
if (_thread != null && _thread.IsAlive)
{
_thread.Abort();
_stopping = true;
_thread.Join();
_thread = null;
}
}
@ -51,9 +53,9 @@ namespace RageCoop.Client
return;
// I tried without thread but the game will lag without
_thread = new Thread(() =>
_thread = ThreadManager.CreateThread(() =>
{
while (true)
while (!_stopping && !IsUnloading)
using (var wo = new WaveOutEvent())
{
wo.Init(_waveProvider);
@ -61,8 +63,7 @@ namespace RageCoop.Client
while (wo.PlaybackState == PlaybackState.Playing) Thread.Sleep(100);
}
});
_thread.Start();
},"Voice");
}
public static void StartRecording()

View File

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

View File

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

View File

@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
using GTA;
using GTA.Math;
using RageCoop.Core;
using SHVDN;
using NativeMemory = SHVDN.NativeMemory;
namespace RageCoop.Client
{

View File

@ -93,7 +93,7 @@ namespace RageCoop.Client
var num1 = new OutputArgument();
var num2 = new OutputArgument();
if (!Function.Call<bool>(Hash.GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y,
if (!Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y,
worldCoords.Z, num1, num2))
{
screenCoords = new Vector2();
@ -106,13 +106,13 @@ namespace RageCoop.Client
public static void StayInCover(this Ped p)
{
Function.Call(Hash.TASK_STAY_IN_COVER, p);
Call(TASK_STAY_IN_COVER, p);
}
public static bool IsTurretSeat(this Vehicle veh, int seat)
{
if (Function.Call<bool>(Hash.IS_TURRET_SEAT, veh, seat)) return true;
if (!Function.Call<bool>(Hash.DOES_VEHICLE_HAVE_WEAPONS, veh.Handle)) return false;
if (Call<bool>(IS_TURRET_SEAT, veh, seat)) return true;
if (!Call<bool>(DOES_VEHICLE_HAVE_WEAPONS, veh.Handle)) return false;
switch (seat)
{
@ -177,9 +177,9 @@ namespace RageCoop.Client
var result = new byte[36];
for (byte i = 0; i < 12; i++)
{
result[i] = (byte)Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i + 12] = (byte)Function.Call<short>(Hash.GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i + 24] = (byte)Function.Call<short>(Hash.GET_PED_PALETTE_VARIATION, ped.Handle, i);
result[i] = (byte)Call<short>(GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i + 12] = (byte)Call<short>(GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i + 24] = (byte)Call<short>(GET_PED_PALETTE_VARIATION, ped.Handle, i);
}
return result;
@ -214,13 +214,13 @@ namespace RageCoop.Client
{
flags |= PedDataFlags.IsInCover;
if (ped.IsInCoverFacingLeft) flags |= PedDataFlags.IsInCover;
if (!Function.Call<bool>(Hash.IS_PED_IN_HIGH_COVER, ped)) flags |= PedDataFlags.IsInLowCover;
if (!Call<bool>(IS_PED_IN_HIGH_COVER, ped)) flags |= PedDataFlags.IsInLowCover;
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire)) flags |= PedDataFlags.IsBlindFiring;
}
if (ped.IsInvincible) flags |= PedDataFlags.IsInvincible;
if (Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped)) flags |= PedDataFlags.IsInStealthMode;
if (Call<bool>(GET_PED_STEALTH_MOVEMENT, ped)) flags |= PedDataFlags.IsInStealthMode;
return flags;
@ -309,38 +309,35 @@ namespace RageCoop.Client
case WeaponHash.MG:
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
default:
Main.Logger.Warning(
Log.Warning(
$"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
return null;
}
}
static Dictionary<string, int> _vehicleDoors=new()
{
{ "door_dside_f", -1 },
{ "door_pside_f", 0 },
{ "door_dside_r", 1 },
{ "door_pside_r", 2 }
};
public static VehicleSeat GetNearestSeat(this Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
{
var num = 99f;
var result = -2;
var dictionary = new Dictionary<string, int>();
dictionary.Add("door_dside_f", -1);
dictionary.Add("door_pside_f", 0);
dictionary.Add("door_dside_r", 1);
dictionary.Add("door_pside_r", 2);
foreach (var text in dictionary.Keys)
foreach (var text in _vehicleDoors.Keys)
{
var flag = veh.Bones[text].Position != Vector3.Zero;
if (flag)
{
var num2 = ped.Position.DistanceTo(Function.Call<Vector3>(Hash.GET_WORLD_POSITION_OF_ENTITY_BONE,
new InputArgument[]
{
veh,
veh.Bones[text].Index
}));
var num2 = ped.Position.DistanceTo(Call<Vector3>(GET_WORLD_POSITION_OF_ENTITY_BONE, veh, veh.Bones[text].Index));
var flag2 = num2 < distanceToignoreDoors && num2 < num &&
IsSeatUsableByPed(ped, veh, dictionary[text]);
IsSeatUsableByPed(ped, veh, _vehicleDoors[text]);
if (flag2)
{
num = num2;
result = dictionary[text];
result = _vehicleDoors[text];
}
}
}
@ -366,11 +363,7 @@ namespace RageCoop.Client
}
else
{
var num = Function.Call<int>(Hash.GET_RELATIONSHIP_BETWEEN_PEDS, new InputArgument[]
{
ped,
veh.GetPedOnSeat(seat)
});
var num = Call<int>(GET_RELATIONSHIP_BETWEEN_PEDS, ped, veh.GetPedOnSeat(seat));
var flag2 = num > 2;
if (flag2) result = true;
}
@ -381,7 +374,7 @@ namespace RageCoop.Client
public static bool IsTaskActive(this Ped p, TaskType task)
{
return Function.Call<bool>(Hash.GET_IS_TASK_ACTIVE, p.Handle, task);
return Call<bool>(GET_IS_TASK_ACTIVE, p.Handle, task);
}
public static Vector3 GetAimCoord(this Ped p)
@ -408,7 +401,7 @@ namespace RageCoop.Client
public static Vector3 GetLookingCoord(this Ped p)
{
if (p == Main.P && Function.Call<int>(Hash.GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
if (p == P && Call<int>(GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
return RaycastEverything(default);
EntityBone b = p.Bones[Bone.FacialForehead];
var v = b.UpVector.Normalized;
@ -417,7 +410,7 @@ namespace RageCoop.Client
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
{
return (VehicleSeat)Function.Call<int>(Hash.GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
return (VehicleSeat)Call<int>(GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
}
#endregion

View File

@ -17,7 +17,7 @@ using Font = GTA.UI.Font;
namespace RageCoop.Client
{
internal static class Util
internal static partial class Util
{
/// <summary>
/// The location of the cursor on screen between 0 and 1.
@ -67,23 +67,6 @@ namespace RageCoop.Client
return b.ForwardVector.ToEulerRotation(b.UpVector);
}
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
{
Notification.Show("~r~" + error);
}
catch
{
}
throw new Exception(error);
}
}
public static void DrawTextFromCoord(Vector3 coord, string text, float scale = 0.5f, Point offset = default)
{
Point toDraw = default;
@ -106,7 +89,7 @@ namespace RageCoop.Client
unsafe
{
var res = ResolutionMaintainRatio;
if (Function.Call<bool>(Hash.GET_SCREEN_COORD_FROM_WORLD_COORD, pos.X, pos.Y, pos.Z, &x, &y))
if (Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, pos.X, pos.Y, pos.Z, &x, &y))
{
screenPos = new Point((int)(res.Width * x), (int)(y * 1080));
return true;
@ -116,39 +99,39 @@ namespace RageCoop.Client
return false;
}
public static Settings ReadSettings(string path = null)
public static ClientSettings ReadSettings(string path = null)
{
path = path ?? SettingsPath;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
Settings settings;
ClientSettings settings;
try
{
settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));
settings = JsonDeserialize<ClientSettings>(File.ReadAllText(path));
}
catch (Exception ex)
{
Main.Logger?.Error(ex);
File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented));
Log?.Error(ex);
File.WriteAllText(path, JsonSerialize(settings = new ClientSettings()));
}
return settings;
}
public static bool SaveSettings(string path = null, Settings settings = null)
public static bool SaveSettings(string path = null, ClientSettings settings = null)
{
try
{
path = path ?? SettingsPath;
settings = settings ?? Main.Settings;
settings = settings ?? Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
File.WriteAllText(path, JsonSerialize(settings));
return true;
}
catch (Exception ex)
{
Main.Logger?.Error(ex);
Log?.Error(ex);
return false;
}
}
@ -157,28 +140,28 @@ namespace RageCoop.Client
public static Vehicle CreateVehicle(Model model, Vector3 position, float heading = 0f)
{
if (!model.IsLoaded) return null;
return (Vehicle)Entity.FromHandle(Function.Call<int>(Hash.CREATE_VEHICLE, model.Hash, position.X,
return (Vehicle)Entity.FromHandle(Call<int>(CREATE_VEHICLE, model.Hash, position.X,
position.Y, position.Z, heading, false, false));
}
public static Ped CreatePed(Model model, Vector3 position, float heading = 0f)
{
if (!model.IsLoaded) return null;
return (Ped)Entity.FromHandle(Function.Call<int>(Hash.CREATE_PED, 26, model.Hash, position.X, position.Y,
return (Ped)Entity.FromHandle(Call<int>(CREATE_PED, 26, model.Hash, position.X, position.Y,
position.Z, heading, false, false));
}
public static void SetOnFire(this Entity e, bool toggle)
{
if (toggle)
Function.Call(Hash.START_ENTITY_FIRE, e.Handle);
Call(START_ENTITY_FIRE, e.Handle);
else
Function.Call(Hash.STOP_ENTITY_FIRE, e.Handle);
Call(STOP_ENTITY_FIRE, e.Handle);
}
public static void SetFrozen(this Entity e, bool toggle)
{
Function.Call(Hash.FREEZE_ENTITY_POSITION, e, toggle);
Call(FREEZE_ENTITY_POSITION, e, toggle);
}
public static SyncedPed GetSyncEntity(this Ped p)
@ -197,28 +180,31 @@ namespace RageCoop.Client
return v;
}
public static void ApplyForce(this Entity e, int boneIndex, Vector3 direction, Vector3 rotation = default,
ForceType forceType = ForceType.MaxForceRot2)
{
Function.Call(Hash.APPLY_FORCE_TO_ENTITY, e.Handle, forceType, direction.X, direction.Y, direction.Z,
rotation.X, rotation.Y, rotation.Z, boneIndex, false, true, true, false, true);
}
public static byte GetPlayerRadioIndex()
{
return (byte)Function.Call<int>(Hash.GET_PLAYER_RADIO_STATION_INDEX);
return (byte)Call<int>(GET_PLAYER_RADIO_STATION_INDEX);
}
public static void SetPlayerRadioIndex(int index)
{
Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index);
Call(SET_RADIO_TO_STATION_INDEX, index);
}
public static EntityPopulationType GetPopulationType(int handle)
=> (EntityPopulationType)Call<int>(GET_ENTITY_POPULATION_TYPE, handle);
[DllImport("kernel32.dll")]
public static extern ulong GetTickCount64();
public static unsafe void DeleteEntity(int handle)
{
Call(SET_ENTITY_AS_MISSION_ENTITY, handle, false, true);
Call(DELETE_ENTITY, &handle);
}
[LibraryImport("kernel32.dll")]
public static partial ulong GetTickCount64();
[LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
public static partial IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string lpModuleName);
#region -- POINTER --
private static int _steeringAngleOffset { get; set; }

View File

@ -27,9 +27,9 @@ namespace RageCoop.Client
if (veh.IsDead) flags |= VehicleDataFlags.IsDead;
if (Function.Call<bool>(Hash.IS_HORN_ACTIVE, veh.Handle)) flags |= VehicleDataFlags.IsHornActive;
if (Call<bool>(IS_HORN_ACTIVE, veh.Handle)) flags |= VehicleDataFlags.IsHornActive;
if (v.IsSubmarineCar && Function.Call<bool>(Hash.IS_VEHICLE_IN_SUBMARINE_MODE, veh.Handle))
if (v.IsSubmarineCar && Call<bool>(IS_VEHICLE_IN_SUBMARINE_MODE, veh.Handle))
flags |= VehicleDataFlags.IsTransformed;
if (v.IsAircraft) flags |= VehicleDataFlags.IsAircraft;
@ -150,7 +150,7 @@ namespace RageCoop.Client
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{
Function.Call(Hash.SET_SPECIAL_FLIGHT_MODE_TARGET_RATIO, deluxo, hover ? 1f : 0f);
Call(SET_SPECIAL_FLIGHT_MODE_TARGET_RATIO, deluxo, hover ? 1f : 0f);
}
public static bool IsDeluxoHovering(this Vehicle deluxo)
@ -160,7 +160,7 @@ namespace RageCoop.Client
public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
{
Function.Call(Hash.SET_HOVER_MODE_WING_RATIO, v, ratio);
Call(SET_HOVER_MODE_WING_RATIO, v, ratio);
}
public static float GetDeluxoWingRatio(this Vehicle v)
@ -170,7 +170,7 @@ namespace RageCoop.Client
public static float GetNozzleAngel(this Vehicle plane)
{
return Function.Call<float>(Hash.GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
return Call<float>(GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
}
public static bool HasNozzle(this Vehicle v)
@ -199,7 +199,7 @@ namespace RageCoop.Client
public static void SetNozzleAngel(this Vehicle plane, float ratio)
{
Function.Call(Hash.SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
Call(SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
}
#endregion

View File

@ -6,7 +6,6 @@ using GTA.Native;
using Newtonsoft.Json;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using static RageCoop.Client.Shared;
namespace RageCoop.Client
{
@ -26,16 +25,16 @@ namespace RageCoop.Client
static WeaponUtil()
{
// Parse and load to memory
foreach (var w in JsonConvert.DeserializeObject<VehicleWeaponInfo[]>(
foreach (var w in JsonDeserialize<VehicleWeaponInfo[]>(
File.ReadAllText(VehicleWeaponDataPath))) VehicleWeapons.Add(w.Hash, w);
Weapons = JsonConvert.DeserializeObject<Dictionary<uint, WeaponInfo>>(
Weapons = JsonDeserialize<Dictionary<uint, WeaponInfo>>(
File.ReadAllText(WeaponInfoDataPath));
if (File.Exists(WeaponFixDataPath))
WeaponFix = JsonConvert.DeserializeObject<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
WeaponFix = JsonDeserialize<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
else
API.Logger.Warning("Weapon fix data not found");
Log.Warning("Weapon fix data not found");
}
public static void DumpWeaponFix(string path)
@ -47,14 +46,14 @@ namespace RageCoop.Client
var fix = new WeaponFix();
foreach (var w in Weapons)
{
Console.Info("Testing " + w.Value.Name);
Console.PrintInfo("Testing " + w.Value.Name);
if (w.Value.FireType != "PROJECTILE")
{
var asset = new WeaponAsset(w.Key);
asset.Request(1000);
World.ShootBullet(pos, pos + Vector3.WorldUp, P, asset, 0, 1000);
if (!Function.Call<bool>(Hash.IS_BULLET_IN_AREA, pos.X, pos.Y, pos.Z, 10f, true) &&
!Function.Call<bool>(Hash.IS_PROJECTILE_IN_AREA, pos.X - 10, pos.Y - 10, pos.Z - 10, pos.X + 10,
if (!Call<bool>(IS_BULLET_IN_AREA, pos.X, pos.Y, pos.Z, 10f, true) &&
!Call<bool>(IS_PROJECTILE_IN_AREA, pos.X - 10, pos.Y - 10, pos.Z - 10, pos.X + 10,
pos.Y + 10, pos.Z + 10, true))
switch (w.Value.DamageType)
{
@ -74,7 +73,7 @@ namespace RageCoop.Client
}
}
File.WriteAllText(path, JsonConvert.SerializeObject(fix, Formatting.Indented));
File.WriteAllText(path, JsonSerialize(fix));
P.IsInvincible = false;
}
@ -112,7 +111,7 @@ namespace RageCoop.Client
public static float GetWeaponDamage(this Ped P, uint hash)
{
var comp = P.Weapons.Current.Components.GetSuppressorComponent();
return Function.Call<float>(Hash.GET_WEAPON_DAMAGE, hash,
return Call<float>(GET_WEAPON_DAMAGE, hash,
comp.Active ? comp.ComponentHash : WeaponComponentHash.Invalid);
}
@ -194,7 +193,7 @@ namespace RageCoop.Client
public static WeaponGroup GetWeaponGroup(this WeaponHash hash)
{
return Function.Call<WeaponGroup>(Hash.GET_WEAPONTYPE_GROUP, hash);
return Call<WeaponGroup>(GET_WEAPONTYPE_GROUP, hash);
}
}
}

View File

@ -115,7 +115,7 @@ namespace RageCoop.Client
}
#endregion
/*
public static Image CaptureWindow(IntPtr handle)
{
var windowDC = User32.GetWindowDC(handle);
@ -134,7 +134,7 @@ namespace RageCoop.Client
User32.ReleaseDC(handle, windowDC);
return image;
}
*/
public static void ClearLastError()
{
SetLastErrorEx(0, 0);

View File

@ -12,8 +12,7 @@ namespace RageCoop.Client
/// <summary>
/// Don't use it!
/// </summary>
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false,
SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class WorldThread : Script
{
public static Script Instance;
@ -26,14 +25,22 @@ namespace RageCoop.Client
/// </summary>
public WorldThread()
{
Util.StartUpCheck();
Instance = this;
Tick += OnTick;
Aborted += (sender, e) => { ChangeTraffic(true); };
Aborted += (e) => { DoQueuedActions(); ChangeTraffic(true); };
}
private void OnTick(object sender, EventArgs e)
protected override void OnStart()
{
base.OnStart();
while(Game.IsLoading)
Yield();
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
}
protected override void OnTick()
{
base.OnTick();
if (Game.IsLoading) return;
try
@ -43,23 +50,23 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error(ex);
Log.Error(ex);
}
if (!Networking.IsOnServer) return;
Game.DisableControlThisFrame(Control.FrontendPause);
if (Main.Settings.DisableAlternatePause) Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
if (Settings.DisableAlternatePause) Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
// Sets a value that determines how aggressive the ocean waves will be.
// Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
Function.Call(Hash.SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
Call(SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
if (Main.Settings.ShowEntityOwnerName)
if (Settings.ShowEntityOwnerName)
unsafe
{
int handle;
if (Function.Call<bool>(Hash.GET_ENTITY_PLAYER_IS_FREE_AIMING_AT, 0, &handle))
if (Call<bool>(GET_ENTITY_PLAYER_IS_FREE_AIMING_AT, 0, &handle))
{
var entity = Entity.FromHandle(handle);
if (entity != null)
@ -76,13 +83,13 @@ namespace RageCoop.Client
if (!_trafficEnabled)
{
Function.Call(Hash.SET_VEHICLE_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_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Function.Call(Hash.SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Function.Call(Hash.SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
Function.Call(Hash.SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
Call(SET_PED_POPULATION_BUDGET, 0);
Call(SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
Call(SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
}
}
@ -96,35 +103,35 @@ namespace RageCoop.Client
{
if (enable)
{
Function.Call(Hash.REMOVE_SCENARIO_BLOCKING_AREAS);
Function.Call(Hash.SET_CREATE_RANDOM_COPS, true);
Function.Call(Hash.SET_RANDOM_TRAINS, true);
Function.Call(Hash.SET_RANDOM_BOATS, true);
Function.Call(Hash.SET_GARBAGE_TRUCKS, true);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3); // 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_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, -1);
Function.Call(Hash.SET_DISTANT_CARS_ENABLED, true);
Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, false);
Call(REMOVE_SCENARIO_BLOCKING_AREAS);
Call(SET_CREATE_RANDOM_COPS, true);
Call(SET_RANDOM_TRAINS, true);
Call(SET_RANDOM_BOATS, true);
Call(SET_GARBAGE_TRUCKS, true);
Call(SET_PED_POPULATION_BUDGET, 3); // 0 - 3
Call(SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
Call(SET_ALL_VEHICLE_GENERATORS_ACTIVE);
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
Call(SET_NUMBER_OF_PARKED_VEHICLES, -1);
Call(SET_DISTANT_CARS_ENABLED, true);
Call(DISABLE_VEHICLE_DISTANTLIGHTS, false);
}
else if (Networking.IsOnServer)
{
Function.Call(Hash.ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f,
Call(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_RANDOM_TRAINS, false);
Function.Call(Hash.SET_RANDOM_BOATS, false);
Function.Call(Hash.SET_GARBAGE_TRUCKS, false);
Function.Call(Hash.DELETE_ALL_TRAINS);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Function.Call(Hash.SET_FAR_DRAW_VEHICLES, false);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, 0);
Function.Call(Hash.SET_DISTANT_CARS_ENABLED, false);
Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, true);
Call(SET_CREATE_RANDOM_COPS, false);
Call(SET_RANDOM_TRAINS, false);
Call(SET_RANDOM_BOATS, false);
Call(SET_GARBAGE_TRUCKS, false);
Call(DELETE_ALL_TRAINS);
Call(SET_PED_POPULATION_BUDGET, 0);
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Call(SET_FAR_DRAW_VEHICLES, false);
Call(SET_NUMBER_OF_PARKED_VEHICLES, 0);
Call(SET_DISTANT_CARS_ENABLED, false);
Call(DISABLE_VEHICLE_DISTANTLIGHTS, true);
foreach (var ped in World.GetAllPeds())
{
if (ped == Game.Player.Character) continue;
@ -132,7 +139,7 @@ namespace RageCoop.Client
if (c == null || (c.IsLocal && ped.Handle != Game.Player.Character.Handle &&
ped.PopulationType != EntityPopulationType.Mission))
{
Main.Logger.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
Log.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
ped.CurrentVehicle?.Delete();
ped.Kill();
ped.Delete();
@ -147,7 +154,7 @@ namespace RageCoop.Client
// Don't delete player's vehicle
continue;
if (v == null || (v.IsLocal && veh.PopulationType != EntityPopulationType.Mission))
// Main.Logger.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
// Log.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
veh.Delete();
}
@ -174,7 +181,7 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error(ex);
Log.Error(ex);
QueuedActions.Remove(action);
}
}

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.CSharp" version="4.7.0" 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.Win32.Primitives" version="4.3.0" targetFramework="net48" />
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" />
<package id="NativeInvoker" version="1.0.0.1" targetFramework="net48" />
<package id="NAudio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Asio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Core" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Midi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net48" />
<package id="NETStandard.Library" version="1.6.1" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
<package id="ScriptHookVDotNet3" version="3.5.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.Buffers" version="4.5.1" 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.Console" version="4.3.0" targetFramework="net48" />
<package id="System.Data.DataSetExtensions" version="4.5.0" targetFramework="net48" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net48" />
<package id="System.Drawing.Common" version="4.5.0" targetFramework="net48" />
<package id="System.Globalization" version="4.3.0" targetFramework="net48" />
<package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net48" />
<package id="System.IO" version="4.3.0" targetFramework="net48" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net48" />
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net48" />
<package id="System.IO.FileSystem" 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.Expressions" version="4.3.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" 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.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.Reflection" 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.Resources.ResourceManager" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.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.InteropServices" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net48" />
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net481" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net481" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Text.Encoding.Extensions" 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.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.Xml.ReaderWriter" version="4.3.0" targetFramework="net48" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" />
</packages>

View File

@ -1,59 +0,0 @@
using System.IO;
using System.Text;
using GTA.Math;
namespace RageCoop.Core
{
internal class BitReader : BinaryReader
{
public BitReader(byte[] array) : base(new MemoryStream(array))
{
}
~BitReader()
{
Close();
Dispose();
}
public byte[] ReadByteArray()
{
return base.ReadBytes(ReadInt32());
}
public override string ReadString()
{
return Encoding.UTF8.GetString(ReadBytes(ReadInt32()));
}
public Vector3 ReadVector3()
{
return new Vector3
{
X = ReadSingle(),
Y = ReadSingle(),
Z = ReadSingle()
};
}
public Vector2 ReadVector2()
{
return new Vector2
{
X = ReadSingle(),
Y = ReadSingle()
};
}
public Quaternion ReadQuaternion()
{
return new Quaternion
{
X = ReadSingle(),
Y = ReadSingle(),
Z = ReadSingle(),
W = ReadSingle()
};
}
}
}

304
Core/Buffer.cs Normal file
View File

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

View File

@ -9,6 +9,7 @@ using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
@ -20,17 +21,19 @@ using RageCoop.Core.Scripting;
[assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")]
[assembly: InternalsVisibleTo("RageCoop.Client.Scripting")]
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
[assembly: InternalsVisibleTo("DataDumper")]
[assembly: InternalsVisibleTo("UnitTest")]
[assembly: InternalsVisibleTo("RageCoop.ResourceBuilder")]
namespace RageCoop.Core
{
internal static class CoreUtils
{
private static readonly Random random = new Random();
private static readonly Random random = new();
private static readonly HashSet<string> ToIgnore = new HashSet<string>
private static readonly HashSet<string> ToIgnore = new()
{
"RageCoop.Client",
"RageCoop.Client.Loader",
@ -39,7 +42,8 @@ namespace RageCoop.Core
"RageCoop.Server",
"ScriptHookVDotNet2",
"ScriptHookVDotNet3",
"ScriptHookVDotNet"
"ScriptHookVDotNet",
"ScriptHookVDotNetCore"
};
public static string FormatToSharpStyle(string input, int offset)
@ -90,29 +94,6 @@ namespace RageCoop.Core
.Select(s => s[random.Next(s.Length)]).ToArray());
}
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 =
@ -127,7 +108,20 @@ namespace RageCoop.Core
public static bool CanBeIgnored(this string name)
{
return ToIgnore.Contains(Path.GetFileNameWithoutExtension(name));
name = Path.GetFileNameWithoutExtension(name);
return ToIgnore.Contains(name) || AssemblyLoadContext.Default.Assemblies.Any(x => x.GetName().Name == name);
}
public static void ForceLoadAllAssemblies()
{
foreach (var a in AssemblyLoadContext.Default.Assemblies)
LoadAllReferencedAssemblies(a.GetName());
}
public static void LoadAllReferencedAssemblies(this AssemblyName assembly)
{
foreach (var child in Assembly.Load(assembly).GetReferencedAssemblies())
LoadAllReferencedAssemblies(child);
}
public static string ToFullPath(this string path)
@ -311,7 +305,7 @@ namespace RageCoop.Core
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return JsonConvert.DeserializeObject<IpInfo>(content);
return JsonDeserialize<IpInfo>(content);
}
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)

59
Core/JsonCoverters.cs Normal file
View File

@ -0,0 +1,59 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPAddress));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
return IPAddress.Parse((string)reader.Value);
}
}
class IPEndPointConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPEndPoint));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IPEndPoint ep = (IPEndPoint)value;
JObject jo = new()
{
{ "Address", JToken.FromObject(ep.Address, serializer) },
{ "Port", ep.Port }
};
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
JObject jo = JObject.Load(reader);
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
int port = (int)jo["Port"];
return new IPEndPoint(address, port);
}
}
}

View File

@ -46,11 +46,11 @@ namespace RageCoop.Core
public string Name = "Logger";
private bool Stopping;
public List<StreamWriter> Writers = new List<StreamWriter> { new StreamWriter(Console.OpenStandardOutput()) };
public List<StreamWriter> Writers = new() { new StreamWriter(Console.OpenStandardOutput()) };
internal Logger()
{
Name = Process.GetCurrentProcess().Id.ToString();
Name = Environment.ProcessId.ToString();
if (!FlushImmediately)
{
LoggerThread = new Thread(() =>
@ -164,20 +164,31 @@ namespace RageCoop.Core
while (_queuedLines.TryDequeue(out var line))
{
var formatted = Format(line);
Writers.ForEach(x =>
Writers?.ForEach(x =>
{
x.WriteLine(formatted);
x.Flush();
try
{
x.WriteLine(formatted);
x.Flush();
}
catch (Exception ex)
{
HandleError(ex);
}
});
OnFlush?.Invoke(line, formatted);
}
}
catch
catch (Exception ex)
{
HandleError(ex);
}
}
}
void HandleError(Exception ex)
{
Console.WriteLine($"Logger {this} flush error: {ex}");
}
public class LogLine
{
public LogLevel LogLevel;

View File

@ -128,6 +128,11 @@ namespace RageCoop.Core
return q.ToEulerAngles().ToDegree();
}
public static Quaternion Differentiate(this Quaternion p, Quaternion q)
{
return q * Quaternion.Invert(p);
}
public static Vector3 ToEulerAngles(this Quaternion q)
{
var angles = new Vector3();

View File

@ -7,15 +7,17 @@ namespace RageCoop.Core
{
internal class CoopPeer : NetPeer, IDisposable
{
private readonly Thread ListenerThread;
private readonly Logger Log;
private readonly Thread _receiver;
private bool _stopping;
public EventHandler<NetIncomingMessage> OnMessageReceived;
public CoopPeer(NetPeerConfiguration config) : base(config)
public CoopPeer(NetPeerConfiguration config,Logger logger) : base(config)
{
Log = logger;
Start();
NetIncomingMessage msg;
ListenerThread = new Thread(() =>
_receiver = new Thread(() =>
{
while (!_stopping)
{
@ -23,7 +25,7 @@ namespace RageCoop.Core
if (msg != null) OnMessageReceived?.Invoke(this, msg);
}
});
ListenerThread.Start();
_receiver.Start();
}
/// <summary>
@ -32,8 +34,18 @@ namespace RageCoop.Core
public void Dispose()
{
_stopping = true;
Shutdown("Bye!");
ListenerThread.Join();
if (Status == NetPeerStatus.Running)
{
Shutdown("Bye!");
}
if (_receiver.IsAlive)
{
Log?.Debug("Stopping message thread");
_receiver.Join();
}
Log?.Debug("Stopping network thread");
Join();
Log?.Debug("CoopPeer disposed");
}
public void SendTo(Packet p, NetConnection connection, ConnectionChannel channel = ConnectionChannel.Default,

View File

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
namespace RageCoop.Core
@ -33,8 +34,8 @@ namespace RageCoop.Core
SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var client = new WebClient();
return client.DownloadString(url);
var client = new HttpClient();
return client.GetStringAsync(url).GetAwaiter().GetResult();
}
}
}

View File

@ -8,7 +8,6 @@ namespace RageCoop.Core
[Serializable]
public class ServerInfo
{
#pragma warning disable 1591
public string address { get; set; }
public int port { get; set; }
public string name { get; set; }

View File

@ -9,7 +9,6 @@ namespace RageCoop.Core
{
internal class CustomEvent : Packet
{
public static Func<byte, NetIncomingMessage, object> ResolveHandle = null;
public CustomEventFlags Flags;
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
@ -19,79 +18,20 @@ namespace RageCoop.Core
public override PacketType Type => PacketType.CustomEvent;
public int Hash { get; set; }
public object[] Args { get; set; }
public byte[] Payload;
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);
m.Write(Payload);
}
public override void Deserialize(NetIncomingMessage m)
public unsafe override void Deserialize(NetIncomingMessage m)
{
Flags = (CustomEventFlags)m.ReadByte();
Hash = m.ReadInt32();
Args = new object[m.ReadInt32()];
for (var i = 0; i < Args.Length; i++)
{
var 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] = (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}");
Args[i] = ResolveHandle(type, m);
break;
}
}
Payload = m.ReadBytes(m.LengthBytes - m.PositionInBytes);
}
}
}

View File

@ -152,6 +152,7 @@ namespace RageCoop.Core
internal abstract class Packet : IPacket
{
public abstract PacketType Type { get; }
public virtual void Deserialize(NetIncomingMessage m)

View File

@ -1,61 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.1</AssemblyVersion>
<FileVersion>0.1</FileVersion>
<Version>0.1</Version>
<DebugType>embedded</DebugType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Configurations>Debug;Release;API</Configurations>
<OutDir>..\bin\$(Configuration)\Core</OutDir>
</PropertyGroup>
<PropertyGroup>
<ImplicitUsings>true</ImplicitUsings>
<NoWarn>1701;1702;CS1591</NoWarn>
<TargetFrameworks>net7.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.1</AssemblyVersion>
<FileVersion>0.1</FileVersion>
<Version>0.1</Version>
<DebugType>embedded</DebugType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Configurations>Debug;Release;API</Configurations>
<OutDir>..\bin\$(Configuration)\Core</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'API'">
<OutDir>..\bin\API</OutDir>
<Deterministic>true</Deterministic>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ProduceOnlyReferenceAssembly>true</ProduceOnlyReferenceAssembly>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
</ItemGroup>
<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>
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Fody" Version="6.6.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libs\Lidgren.Network\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore\ScriptHookVDotNetCore.csproj" />
</ItemGroup>
</Project>

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using System;
using Newtonsoft.Json;
using System;
using System.IO;
namespace RageCoop.Core.Scripting
@ -10,16 +11,19 @@ namespace RageCoop.Core.Scripting
/// <summary>
/// Full name with relative path of this file
/// </summary>
[JsonProperty]
public string Name { get; internal set; }
/// <summary>
/// Whether this is a directory
/// </summary>
[JsonProperty]
public bool IsDirectory { get; internal set; }
/// <summary>
/// Get a stream that can be used to read file content.
/// </summary>
[JsonIgnore]
public Func<Stream> GetStream { get; internal set; }
}
}

70
Core/Shared.cs Normal file
View File

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

View File

@ -24,9 +24,9 @@ namespace RageCoop.Core
}
Console.WriteLine("Deserializing");
var anims = JsonConvert.DeserializeObject<AnimDic[]>(File.ReadAllText(input));
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(input));
Console.WriteLine("Serializing");
File.WriteAllText(output, JsonConvert.SerializeObject(anims, Formatting.Indented));
File.WriteAllText(output, JsonSerialize(anims));
return anims;
}
}
@ -113,12 +113,11 @@ namespace RageCoop.Core
}
Console.WriteLine("Deserializing");
var infos = JsonConvert.DeserializeObject<VehicleInfo[]>(File.ReadAllText(input));
var infos = JsonDeserialize<VehicleInfo[]>(File.ReadAllText(input));
Console.WriteLine("Serializing");
File.WriteAllText(output,
JsonConvert.SerializeObject(
infos.Select(FromVehicle).Where(x => x != null),
Formatting.Indented));
JsonSerialize(
infos.Select(FromVehicle).Where(x => x != null)));
}
public static VehicleWeaponInfo FromVehicle(VehicleInfo info)

54
Core/XSpan.cs Normal file
View File

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

View File

@ -7,24 +7,30 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "Server\R
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "Client\Scripts\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{12E29AB7-74C4-4250-8975-C02D7FFC2D7B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client.CefHost", "Client\CefHost\RageCoop.Client.CefHost.csproj", "{BA750E08-5E41-4B56-8AD5-875716D2CCEA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{70A1F09D-648D-4C8B-8947-E920B1A587A3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CefTest", "Tools\CefTest\CefTest.csproj", "{874944F4-2D01-4423-B55C-C651CCBA6630}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataDumper", "Tools\DataDumper\DataDumper.csproj", "{6387D897-09AF-4464-B440-80438E3BB8D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Scripting", "Client\Scripting\RageCoop.Client.Scripting.csproj", "{FE47AFBA-0613-4378-B318-892DEB7B3D88}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "Tools\UnitTest\UnitTest.csproj", "{4FE96671-3DC5-4394-B2E3-584399E57310}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{1AF84C35-B86B-46BB-9FDB-ACB7787E582A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScriptHookVDotNetCore", "libs\ScriptHookVDotNetCore\src\ScriptHookVDotNetCore\ScriptHookVDotNetCore.csproj", "{B15EDABB-30AF-475A-823D-ACB9F75CFE13}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lidgren.Network", "libs\Lidgren.Network\Lidgren.Network\Lidgren.Network.csproj", "{02616B5A-2A68-42AA-A91E-311EF95FCF44}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeGen", "Tools\CodeGen\CodeGen.csproj", "{C4CF8A98-7393-42BD-97A1-2E850D12890A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
API|Any CPU = API|Any CPU
@ -83,42 +89,6 @@ Global
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.API|Any CPU.ActiveCfg = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.API|Any CPU.Build.0 = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.API|x64.ActiveCfg = Release|Any CPU
{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}.API|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
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.API|Any CPU.ActiveCfg = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.API|Any CPU.Build.0 = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.API|x64.ActiveCfg = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.API|x64.Build.0 = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Debug|x64.ActiveCfg = Debug|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Debug|x64.Build.0 = Debug|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Release|Any CPU.Build.0 = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Release|x64.ActiveCfg = Release|Any CPU
{BA750E08-5E41-4B56-8AD5-875716D2CCEA}.Release|x64.Build.0 = Release|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.API|Any CPU.ActiveCfg = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.API|Any CPU.Build.0 = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.API|x64.ActiveCfg = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.API|x64.Build.0 = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Debug|Any CPU.Build.0 = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Debug|x64.ActiveCfg = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Debug|x64.Build.0 = Debug|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Release|Any CPU.ActiveCfg = Release|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Release|Any CPU.Build.0 = Release|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Release|x64.ActiveCfg = Release|Any CPU
{874944F4-2D01-4423-B55C-C651CCBA6630}.Release|x64.Build.0 = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|Any CPU.ActiveCfg = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|Any CPU.Build.0 = Debug|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.API|x64.ActiveCfg = Debug|Any CPU
@ -131,6 +101,66 @@ Global
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|Any CPU.Build.0 = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|x64.ActiveCfg = Release|Any CPU
{6387D897-09AF-4464-B440-80438E3BB8D0}.Release|x64.Build.0 = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|Any CPU.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|Any CPU.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|x64.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.API|x64.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|x64.ActiveCfg = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Debug|x64.Build.0 = Debug|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|Any CPU.Build.0 = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|x64.ActiveCfg = Release|Any CPU
{FE47AFBA-0613-4378-B318-892DEB7B3D88}.Release|x64.Build.0 = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|Any CPU.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|Any CPU.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|x64.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.API|x64.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|x64.ActiveCfg = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Debug|x64.Build.0 = Debug|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|Any CPU.Build.0 = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|x64.ActiveCfg = Release|Any CPU
{4FE96671-3DC5-4394-B2E3-584399E57310}.Release|x64.Build.0 = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|Any CPU.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|Any CPU.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|x64.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.API|x64.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|x64.ActiveCfg = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Debug|x64.Build.0 = Debug|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|Any CPU.Build.0 = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|x64.ActiveCfg = Release|Any CPU
{B15EDABB-30AF-475A-823D-ACB9F75CFE13}.Release|x64.Build.0 = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|Any CPU.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|Any CPU.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|x64.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.API|x64.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|x64.ActiveCfg = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Debug|x64.Build.0 = Debug|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|Any CPU.Build.0 = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|x64.ActiveCfg = Release|Any CPU
{02616B5A-2A68-42AA-A91E-311EF95FCF44}.Release|x64.Build.0 = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|Any CPU.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|Any CPU.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|x64.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.API|x64.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|x64.ActiveCfg = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Debug|x64.Build.0 = Debug|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|Any CPU.Build.0 = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|x64.ActiveCfg = Release|Any CPU
{C4CF8A98-7393-42BD-97A1-2E850D12890A}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -138,10 +168,12 @@ Global
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}
{BA750E08-5E41-4B56-8AD5-875716D2CCEA} = {531656CF-7269-488D-B042-741BC96C3941}
{874944F4-2D01-4423-B55C-C651CCBA6630} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
{6387D897-09AF-4464-B440-80438E3BB8D0} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
{FE47AFBA-0613-4378-B318-892DEB7B3D88} = {531656CF-7269-488D-B042-741BC96C3941}
{4FE96671-3DC5-4394-B2E3-584399E57310} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
{B15EDABB-30AF-475A-823D-ACB9F75CFE13} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{02616B5A-2A68-42AA-A91E-311EF95FCF44} = {1AF84C35-B86B-46BB-9FDB-ACB7787E582A}
{C4CF8A98-7393-42BD-97A1-2E850D12890A} = {70A1F09D-648D-4C8B-8947-E920B1A587A3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}

View File

@ -198,10 +198,13 @@ public class Client
try
{
var outgoingMessage = Server.MainNetServer.CreateMessage();
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);;
new Packets.CustomEvent(flags)
{
Hash = hash,
Args = args
Payload = writer.ToByteArray(writer.Position)
}.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered,
(byte)ConnectionChannel.Event);

View File

@ -79,8 +79,7 @@ public partial class Server
if (!CanAnnounce)
{
var existing = JsonConvert
.DeserializeObject<List<ServerInfo>>(
var existing = JsonDeserialize<List<ServerInfo>>(
HttpHelper.DownloadString(Util.GetFinalRedirect(Settings.MasterServer)))
.Where(x => x.address == IpInfo.Address).FirstOrDefault();
if (existing != null)
@ -117,7 +116,7 @@ public partial class Server
publicKeyModulus = Convert.ToBase64String(pModulus),
publicKeyExponent = Convert.ToBase64String(pExpoenet)
};
var msg = JsonConvert.SerializeObject(serverInfo);
var msg = JsonSerialize(serverInfo);
var realUrl = Util.GetFinalRedirect(Settings.MasterServer);
response = httpClient.PostAsync(realUrl, new StringContent(msg, Encoding.UTF8, "application/json"))

View File

@ -15,7 +15,7 @@ using System.Resources;
[assembly: AssemblyCulture("")]
// Version information
[assembly: AssemblyVersion("1.6.0.24")]
[assembly: AssemblyFileVersion("1.6.0.24")]
[assembly: AssemblyVersion("1.6.0.55")]
[assembly: AssemblyFileVersion("1.6.0.55")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -22,6 +22,7 @@
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Configurations>Debug;Release;API</Configurations>
<OutDir>..\bin\$(Configuration)\Server</OutDir>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'API'">
@ -55,7 +56,7 @@
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<IncludeAssets>compile; runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="Fody" Version="6.6.3">
@ -72,15 +73,6 @@
<ProjectReference Include="..\Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Update="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
@ -101,8 +93,7 @@
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition=" '$(DevEnvDir)' != '*Undefined*'">
<Exec
Command="&quot;$(DevEnvDir)TextTransform.exe&quot; -a !!BuildConfiguration!$(Configuration) &quot;$(ProjectDir)Properties\AssemblyInfo.tt&quot;" />
<Exec Command="&quot;$(DevEnvDir)TextTransform.exe&quot; -a !!BuildConfiguration!$(Configuration) &quot;$(ProjectDir)Properties\AssemblyInfo.tt&quot;" />
</Target>

View File

@ -19,7 +19,7 @@ public class ServerEvents
#region INTERNAL
internal Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new();
internal Dictionary<int, List<CustomEventHandler>> CustomEventHandlers = new();
#endregion
@ -138,10 +138,25 @@ public class ServerEvents
OnPlayerDisconnected?.Invoke(this, client);
}
internal void InvokeCustomEventReceived(Packets.CustomEvent p, Client sender)
internal unsafe void InvokeCustomEventReceived(Packets.CustomEvent p, Client sender)
{
var args = new CustomEventReceivedArgs { Hash = p.Hash, Args = p.Args, Client = sender };
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers)) handlers.ForEach(x => { x.Invoke(args); });
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers))
{
fixed (byte* pData = p.Payload)
{
foreach (var handler in handlers)
{
try
{
handler.Invoke(p.Hash, pData, p.Payload.Length, sender);
}
catch (Exception ex)
{
Server.Logger?.Error("InvokeCustomEvent", ex);
}
}
}
}
}
internal void InvokePlayerUpdate(Client client)
@ -344,16 +359,18 @@ public class API
/// <param name="flags"></param>
/// <param name="eventHash">An unique identifier of the event/> to get it from a string</param>
/// <param name="args">
/// The objects conataing your data, see <see cref="Scripting.CustomEventReceivedArgs.Args" /> for
/// The objects conataing your data, see <see cref="Core.Scripting.CustomEventReceivedArgs.Args" /> for
/// supported types.
/// </param>
/// <param name="targets">The target clients to send. Leave it null to send to all clients</param>
public void SendCustomEvent(CustomEventFlags flags, List<Client> targets, CustomEventHash eventHash,
params object[] args)
{
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);
var p = new Packets.CustomEvent(flags)
{
Args = args,
Payload = writer.ToByteArray(writer.Position),
Hash = eventHash
};
if (targets == null)
@ -383,8 +400,11 @@ public class API
lock (Events.CustomEventHandlers)
{
if (!Events.CustomEventHandlers.TryGetValue(hash, out var handlers))
Events.CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
handlers.Add(handler);
Events.CustomEventHandlers.Add(hash, handlers = new());
handlers.Add(new Action<Core.Scripting.CustomEventReceivedArgs>((e) =>
{
handler.Invoke(CustomEventReceivedArgs.From(e));
}));
}
}

View File

@ -25,23 +25,26 @@ public class ChatEventArgs : EventArgs
/// <summary>
/// </summary>
public class CustomEventReceivedArgs : EventArgs
public class CustomEventReceivedArgs : Core.Scripting.CustomEventReceivedArgs
{
/// <summary>
/// The <see cref="RageCoop.Server.Client" /> that triggered this event
/// </summary>
public Client Client { get; set; }
public Client Client
{
get => Tag as Client;
set => Tag = value;
}
/// <summary>
/// The event hash
/// </summary>
public int Hash { get; set; }
/// <summary>
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion, Vector2
/// <see cref="ServerObject.Handle" />
/// </summary>
public object[] Args { get; set; }
public static CustomEventReceivedArgs From(Core.Scripting.CustomEventReceivedArgs e)
{
return new CustomEventReceivedArgs
{
Args = e.Args,
Tag = e.Tag,
Hash = e.Hash,
};
}
}
/// <summary>

View File

@ -22,6 +22,11 @@ internal class Resources
Logger = server.Logger;
}
static Resources()
{
CoreUtils.ForceLoadAllAssemblies();
}
public bool IsLoaded { get; private set; }
public void LoadAll()
@ -98,7 +103,6 @@ internal class Resources
zip.AddDirectory(dir[(resourceFolder.Length + 1)..]);
foreach (var file in Directory.GetFiles(resourceFolder, "*", SearchOption.AllDirectories))
{
if (Path.GetFileName(file).CanBeIgnored()) continue;
zip.Add(file, file[(resourceFolder.Length + 1)..]);
}
@ -263,7 +267,7 @@ internal class Resources
}
if (Server.GetResponse<Packets.FileTransferResponse>(client, new Packets.AllResourcesSent(),
ConnectionChannel.RequestResponse, 30000)?.Response == FileResponse.Loaded)
ConnectionChannel.File, 30000)?.Response == FileResponse.Loaded)
{
client.IsReady = true;
Server.API.Events.InvokePlayerReady(client);

View File

@ -6,6 +6,7 @@ using GTA.Math;
using GTA.Native;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using static RageCoop.Core.Scripting.CustomEvents;
namespace RageCoop.Server.Scripting;
@ -78,11 +79,11 @@ public abstract class ServerObject
switch (this)
{
case ServerProp _:
return 50;
return T_ID_PROP;
case ServerPed _:
return 51;
return T_ID_PED;
case ServerVehicle _:
return 52;
return T_ID_VEH;
default:
throw new NotImplementedException();
}
@ -280,7 +281,7 @@ public class ServerBlip
/// <summary>
/// Pass this as an argument in CustomEvent or NativeCall to convert this object to handle at client side.
/// </summary>
public Tuple<byte, byte[]> Handle => new(60, BitConverter.GetBytes(ID));
public Tuple<byte, byte[]> Handle => new(T_ID_BLIP, BitConverter.GetBytes(ID));
/// <summary>
/// Network ID (not handle!)

View File

@ -47,6 +47,10 @@ public class ServerResource : PluginLoader
internal static ServerResource LoadFrom(string resDir, string dataFolder, Logger logger = null)
{
string mainAssemblyPath = Path.Combine(resDir, Path.GetFileName(resDir) + ".dll");
if (!File.Exists(mainAssemblyPath))
throw new FileNotFoundException($"Main assemby not found: {mainAssemblyPath}");
var runtimeLibs = Path.Combine(resDir, "RuntimeLibs", CoreUtils.GetInvariantRID());
if (Directory.Exists(runtimeLibs))
{
@ -61,16 +65,18 @@ public class ServerResource : PluginLoader
CoreUtils.CopyFilesRecursively(new DirectoryInfo(runtimeLibs), new DirectoryInfo(resDir));
}
var conf = new PluginConfig(Path.GetFullPath(Path.Combine(resDir, Path.GetFileName(resDir) + ".dll")))
var conf = new PluginConfig(Path.GetFullPath(mainAssemblyPath))
{
PreferSharedTypes = true,
EnableHotReload = false,
IsUnloadable = false,
LoadInMemory = true
};
ServerResource r = new(conf);
r.Logger = logger;
r.Name = Path.GetFileName(resDir);
ServerResource r = new(conf)
{
Logger = logger,
Name = Path.GetFileName(resDir)
};
if (!File.Exists(conf.MainAssemblyPath))
{
r.Dispose();

9
Server/Shared.cs Normal file
View File

@ -0,0 +1,9 @@
global using static RageCoop.Core.Shared;
global using static RageCoop.Server.Shared;
global using RageCoop.Core;
namespace RageCoop.Server
{
internal class Shared
{
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<OutDir>$(SolutionDir)bin\Tools\CodeGen</OutDir>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Client\Scripts\RageCoop.Client.csproj" />
</ItemGroup>
</Project>

82
Tools/CodeGen/Program.cs Normal file
View File

@ -0,0 +1,82 @@
using RageCoop.Client.Scripting;
using System.Linq;
using System.Reflection;
using System.Text;
namespace CodeGen
{
internal static class Program
{
static void Main(string[] args)
{
Directory.SetCurrentDirectory(@"..\..\..\");
var props = new StringBuilder();
var config = new StringBuilder();
var funcs = new StringBuilder();
foreach (var prop in typeof(API.Config).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
config.AppendLine($"public static {prop.PropertyType.ToTypeName()} {prop.Name} => GetConfig<{prop.PropertyType.ToTypeName()}>(\"{prop.Name}\");");
}
foreach (var prop in typeof(API).GetProperties(BindingFlags.Public | BindingFlags.Static))
{
props.AppendLine($"public static {prop.PropertyType.ToTypeName()} {prop.Name} => GetProperty<{prop.PropertyType.ToTypeName()}>(\"{prop.Name}\");");
}
foreach (var f in
typeof(API).GetMethods(BindingFlags.Public | BindingFlags.Static).
Where(x =>
{
var attri = x.GetCustomAttribute<RemotingAttribute>();
if (attri ==null) return false;
return attri.GenBridge;
}))
{
var ret = f.ReturnType.ToTypeName();
var gReturn = $"<{ret}>";
if (ret == "System.Void") { ret = "void"; gReturn = string.Empty; }
var ps = f.GetParameters();
var paras = string.Join(',', ps.Select(x => $"{x.ParameterType.ToTypeName()} {x.Name}"));
var parasNoType = string.Join(',', ps.Select(x => x.Name));
if (ps.Length > 0)
{
parasNoType = "," + parasNoType;
}
funcs.AppendLine($"public static {ret} {f.Name}({paras}) => InvokeCommand{gReturn}(\"{f.Name}\"{parasNoType});");
}
var code = $@"namespace RageCoop.Client.Scripting
{{
public static unsafe partial class APIBridge
{{
public static class Config
{{
{config}
}}
#region PROPERTIES
{props}
#endregion
#region FUNCTIONS
{funcs}
#endregion
}}
}}
";
File.WriteAllText(@"Client\Scripting\APIBridge.Generated.cs", code);
}
static string ToTypeName(this Type type)
{
var name = type.ToString();
if (type.GenericTypeArguments.Length > 0)
{
name = $"{name.Substring(0, name.IndexOf('`'))}<{string.Join(',', type.GenericTypeArguments.Select(ToTypeName))}>";
}
name = name.Replace("RageCoop.Client.Player", "RageCoop.Client.Scripting.PlayerInfo");
return name;
}
}
}

View File

@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoAotCompile>true</NoAotCompile>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<OutDir>..\..\bin\Tools\DataDumper</OutDir>
<OutDir>$(SolutionDir)bin\Tools\DataDumper</OutDir>
</PropertyGroup>
<ItemGroup>

92
Tools/UnitTest/Program.cs Normal file
View File

@ -0,0 +1,92 @@
using GTA.Math;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Security.Cryptography;
namespace UnitTest
{
class TestElement
{
static SHA256 sha = SHA256.Create();
static int blah = new Random().Next();
public TestElement(int i)
{
num = (i + blah) * 1024;
vec2 = new(num * 10, num * 20);
vec3 = new(num * 10, num * 20, num * 30);
quat = new(num * 10, num * 20, num * 30, num * 40);
str = sha.ComputeHash(BitConverter.GetBytes(num)).Dump();
}
public int num;
public Vector2 vec2;
public Vector3 vec3;
public Quaternion quat;
public string str;
}
internal unsafe class Program
{
static void Main(string[] args)
{
TestElement[] test = new TestElement[1024];
Console.WriteLine("Testing buffers");
var buf = new BufferWriter(1024);
for (int i = 0; i < 1024; i++)
{
var e = test[i] = new TestElement(i);
buf.WriteVal(e.num);
buf.Write(ref e.vec2);
buf.Write(ref e.vec3);
buf.Write(ref e.quat);
buf.Write(e.str);
}
Console.WriteLine($"Buffer size: {buf.Size}");
Console.WriteLine($"Current position: {buf.Position}");
Console.WriteLine("Validating data");
var reader = new BufferReader(buf.Address, buf.Size);
for (int i = 0; i < 1024; i++)
{
var e = test[i];
reader.Read(out int num);
reader.Read(out Vector2 vec2);
reader.Read(out Vector3 vec3);
reader.Read(out Quaternion quat);
reader.Read(out string str);
if (num != e.num)
throw new Exception("POCO fail");
if (vec2 != e.vec2)
throw new Exception("vec2 fail");
if (vec3 != e.vec3)
throw new Exception("vec3 fail");
if (quat != e.quat)
throw new Exception("quat fail");
if (str != e.str)
throw new Exception("str fail");
}
Console.WriteLine("Buffers OK");
Console.WriteLine("Testing CustomEvents");
var objs = new object[] { (byte)236, (short)82, (ushort)322,
"test", 123, 123U, 456UL, 345L, 5F, new Vector2(15, 54), new Vector3(22, 45, 25), new Quaternion(2, 3, 222, 5) };
buf.Reset();
CustomEvents.WriteObjects(buf, objs);
var payload = buf.ToByteArray(buf.Position);
fixed(byte* p = payload)
{
reader.Initialise(p, payload.Length);
}
if (!CustomEvents.ReadObjects(reader).SequenceEqual(objs))
throw new Exception("CustomEvents fail");
Console.WriteLine("CustomEvents OK");
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -1,8 +1,9 @@
cd "%~dp0"
git submodule update --init
if exist "bin" rmdir /s /q "bin"
dotnet build -c Release
dotnet build -c API Core\RageCoop.Core.csproj
dotnet build -c API Server\RageCoop.Server.csproj
cd %~dp0
copy .\Client\Scripts\obj\Release\ref\RageCoop.Client.dll .\bin\API\RageCoop.Client.dll /y
dotnet build -c API Client\RageCoop.Client.Scripting.csproj
del .\bin\API\RageCoop.Server.exe
rmdir .\bin\Release\Client\SubProcess /s /q
if exist .\bin\Release\Client\SubProcess rmdir .\bin\Release\Client\SubProcess /s /q

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