Initial commit
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
custom: ['https://spenden.pp-h.eu/68454276-da3c-47c7-b95a-7fa443706a44']
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
**/bin
|
||||||
|
**/obj
|
||||||
|
**/packages
|
||||||
|
.vs/*
|
143
Client/Chat.cs
Normal file
143
Client/Chat.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Native;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class Chat
|
||||||
|
{
|
||||||
|
private readonly Scaleform MainScaleForm;
|
||||||
|
|
||||||
|
public string CurrentInput { get; set; }
|
||||||
|
|
||||||
|
private bool CurrentFocused { get; set; }
|
||||||
|
public bool Focused
|
||||||
|
{
|
||||||
|
get { return CurrentFocused; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("SET_FOCUS", value ? 2 : 1, 2, "ALL");
|
||||||
|
|
||||||
|
CurrentFocused = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chat()
|
||||||
|
{
|
||||||
|
MainScaleForm = new Scaleform("multiplayer_chat");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL");
|
||||||
|
MainScaleForm.CallFunction("SET_FOCUS", 1, 2, "ALL");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("RESET");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
MainScaleForm.Render2D();
|
||||||
|
|
||||||
|
if (!CurrentFocused)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMessage(string sender, string msg)
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("ADD_MESSAGE", sender + ":", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnKeyDown(Keys key)
|
||||||
|
{
|
||||||
|
if (key == Keys.Escape)
|
||||||
|
{
|
||||||
|
Focused = false;
|
||||||
|
CurrentInput = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == Keys.PageUp)
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("PAGE_UP");
|
||||||
|
}
|
||||||
|
else if (key == Keys.PageDown)
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("PAGE_DOWN");
|
||||||
|
}
|
||||||
|
|
||||||
|
string keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
|
||||||
|
|
||||||
|
if (keyChar.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (keyChar[0])
|
||||||
|
{
|
||||||
|
case (char)8:
|
||||||
|
if (CurrentInput.Length > 0)
|
||||||
|
{
|
||||||
|
MainScaleForm.CallFunction("SET_FOCUS", 1, 2, "ALL");
|
||||||
|
MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL");
|
||||||
|
|
||||||
|
CurrentInput = CurrentInput.Substring(0, CurrentInput.Length - 1);
|
||||||
|
MainScaleForm.CallFunction("ADD_TEXT", CurrentInput);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case (char)13:
|
||||||
|
MainScaleForm.CallFunction("ADD_TEXT", "ENTER");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(CurrentInput))
|
||||||
|
{
|
||||||
|
Main.MainNetworking.SendChatMessage(CurrentInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
Focused = false;
|
||||||
|
CurrentInput = "";
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
CurrentInput += keyChar;
|
||||||
|
MainScaleForm.CallFunction("ADD_TEXT", keyChar);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern int ToUnicodeEx(uint virtualKeyCode, uint scanCode, byte[] keyboardState,
|
||||||
|
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
|
||||||
|
StringBuilder receivingBuffer,
|
||||||
|
int bufferSize, uint flags, IntPtr kblayout);
|
||||||
|
|
||||||
|
public static string GetCharFromKey(Keys key, bool shift, bool altGr)
|
||||||
|
{
|
||||||
|
StringBuilder buf = new StringBuilder(256);
|
||||||
|
byte[] keyboardState = new byte[256];
|
||||||
|
|
||||||
|
if (shift)
|
||||||
|
{
|
||||||
|
keyboardState[(int)Keys.ShiftKey] = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altGr)
|
||||||
|
{
|
||||||
|
keyboardState[(int)Keys.ControlKey] = 0xff;
|
||||||
|
keyboardState[(int)Keys.Menu] = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, InputLanguage.CurrentInputLanguage.Handle);
|
||||||
|
return buf.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
Client/CoopClient.csproj
Normal file
85
Client/CoopClient.csproj
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>CoopClient</RootNamespace>
|
||||||
|
<AssemblyName>CoopClient</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="LemonUI.SHVDN3">
|
||||||
|
<HintPath>..\Libs\Release\LemonUI.SHVDN3.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lidgren.Network">
|
||||||
|
<HintPath>..\Libs\Release\Lidgren.Network.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="protobuf-net, Version=2.4.0.0, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\protobuf-net.2.4.6\lib\net40\protobuf-net.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ScriptHookVDotNet3, Version=3.1.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>..\Libs\Release\ScriptHookVDotNet3.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Configuration" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Numerics" />
|
||||||
|
<Reference Include="System.Runtime.Serialization" />
|
||||||
|
<Reference Include="System.ServiceModel" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Chat.cs" />
|
||||||
|
<Compile Include="Entities\EntitiesNpc.cs" />
|
||||||
|
<Compile Include="Entities\EntitiesPed.cs" />
|
||||||
|
<Compile Include="Entities\EntitiesPlayer.cs" />
|
||||||
|
<Compile Include="Entities\EntitiesThread.cs" />
|
||||||
|
<Compile Include="Main.cs" />
|
||||||
|
<Compile Include="Networking.cs" />
|
||||||
|
<Compile Include="Packets.cs" />
|
||||||
|
<Compile Include="PlayerList.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="WorldThread.cs" />
|
||||||
|
<Compile Include="Settings.cs" />
|
||||||
|
<Compile Include="Util.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
7
Client/Entities/EntitiesNPC.cs
Normal file
7
Client/Entities/EntitiesNPC.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace CoopClient.Entities
|
||||||
|
{
|
||||||
|
public class EntitiesNpc : EntitiesPed
|
||||||
|
{
|
||||||
|
public int LastUpdateReceived { get; set; }
|
||||||
|
}
|
||||||
|
}
|
354
Client/Entities/EntitiesPed.cs
Normal file
354
Client/Entities/EntitiesPed.cs
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Native;
|
||||||
|
using GTA.Math;
|
||||||
|
|
||||||
|
using LemonUI.Elements;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class EntitiesPed
|
||||||
|
{
|
||||||
|
private bool AllDataAvailable = false;
|
||||||
|
public bool LastSyncWasFull { get; set; } = false;
|
||||||
|
|
||||||
|
public Ped Character { get; set; }
|
||||||
|
public int Health { get; set; }
|
||||||
|
private int LastModelHash = 0;
|
||||||
|
public int ModelHash { get; set; }
|
||||||
|
private Dictionary<int, int> LastProps = new Dictionary<int, int>();
|
||||||
|
public Dictionary<int, int> Props { get; set; }
|
||||||
|
public Vector3 Position { get; set; }
|
||||||
|
public Vector3 Rotation { get; set; }
|
||||||
|
public Vector3 Velocity { get; set; }
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
private bool LastIsJumping = false;
|
||||||
|
public bool IsJumping { get; set; }
|
||||||
|
public bool IsRagdoll { get; set; }
|
||||||
|
public bool IsOnFire { get; set; }
|
||||||
|
public Vector3 AimCoords { get; set; }
|
||||||
|
public bool IsAiming { get; set; }
|
||||||
|
public bool IsShooting { get; set; }
|
||||||
|
public bool IsReloading { get; set; }
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
private Blip PedBlip;
|
||||||
|
|
||||||
|
public void DisplayLocally(string username)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* username: string
|
||||||
|
* string: null
|
||||||
|
* ped: npc
|
||||||
|
* string: value
|
||||||
|
* ped: player
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check beforehand whether ped has all the required data
|
||||||
|
if (!AllDataAvailable)
|
||||||
|
{
|
||||||
|
if (!LastSyncWasFull)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllDataAvailable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region NOT_IN_RANGE
|
||||||
|
if (!Game.Player.Character.IsInRange(Position, 250f))
|
||||||
|
{
|
||||||
|
if (Character != null && Character.Exists())
|
||||||
|
{
|
||||||
|
Character.Kill();
|
||||||
|
Character.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username != null)
|
||||||
|
{
|
||||||
|
if (PedBlip == null || !PedBlip.Exists())
|
||||||
|
{
|
||||||
|
PedBlip = World.CreateBlip(Position);
|
||||||
|
PedBlip.Color = BlipColor.White;
|
||||||
|
PedBlip.Scale = 0.8f;
|
||||||
|
PedBlip.Name = username;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PedBlip.Position = Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IS_IN_RANGE
|
||||||
|
if (PedBlip != null && PedBlip.Exists())
|
||||||
|
{
|
||||||
|
PedBlip.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool characterExist = Character != null && Character.Exists();
|
||||||
|
|
||||||
|
if (!characterExist)
|
||||||
|
{
|
||||||
|
CreateCharacter(username);
|
||||||
|
}
|
||||||
|
else if (LastSyncWasFull)
|
||||||
|
{
|
||||||
|
if (ModelHash != LastModelHash)
|
||||||
|
{
|
||||||
|
if (characterExist)
|
||||||
|
{
|
||||||
|
Character.Kill();
|
||||||
|
Character.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateCharacter(username);
|
||||||
|
}
|
||||||
|
else if (Props != LastProps)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<int, int> prop in Props)
|
||||||
|
{
|
||||||
|
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, Character.Handle, prop.Key, prop.Value, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
LastProps = Props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username != null && Character.IsInRange(Game.Player.Character.Position, 20f))
|
||||||
|
{
|
||||||
|
float sizeOffset;
|
||||||
|
if (GameplayCamera.IsFirstPersonAimCamActive)
|
||||||
|
{
|
||||||
|
Vector3 targetPos = Character.Bones[Bone.IKHead].Position + new Vector3(0, 0, 0.10f) + (Character.Velocity / Game.FPS);
|
||||||
|
|
||||||
|
Function.Call(Hash.SET_DRAW_ORIGIN, targetPos.X, targetPos.Y, targetPos.Z, 0);
|
||||||
|
|
||||||
|
sizeOffset = Math.Max(1f - ((GameplayCamera.Position - Character.Position).Length() / 30f), 0.30f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector3 targetPos = Character.Bones[Bone.IKHead].Position + new Vector3(0, 0, 0.35f) + (Character.Velocity / Game.FPS);
|
||||||
|
|
||||||
|
Function.Call(Hash.SET_DRAW_ORIGIN, targetPos.X, targetPos.Y, targetPos.Z, 0);
|
||||||
|
|
||||||
|
sizeOffset = Math.Max(1f - ((GameplayCamera.Position - Character.Position).Length() / 25f), 0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
new ScaledText(new PointF(0, 0), username, 0.4f * sizeOffset, GTA.UI.Font.ChaletLondon)
|
||||||
|
{
|
||||||
|
Outline = true,
|
||||||
|
Alignment = GTA.UI.Alignment.Center
|
||||||
|
}.Draw();
|
||||||
|
|
||||||
|
Function.Call(Hash.CLEAR_DRAW_ORIGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsOnFire && !Character.IsOnFire)
|
||||||
|
{
|
||||||
|
Character.IsInvincible = false;
|
||||||
|
|
||||||
|
Function.Call(Hash.START_ENTITY_FIRE, Character);
|
||||||
|
}
|
||||||
|
else if (!IsOnFire && Character.IsOnFire)
|
||||||
|
{
|
||||||
|
Function.Call(Hash.STOP_ENTITY_FIRE, Character);
|
||||||
|
|
||||||
|
Character.IsInvincible = true;
|
||||||
|
|
||||||
|
if (Character.IsDead)
|
||||||
|
{
|
||||||
|
Character.Resurrect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Character.IsDead)
|
||||||
|
{
|
||||||
|
if (Health <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Character.IsInvincible = true;
|
||||||
|
Character.Resurrect();
|
||||||
|
}
|
||||||
|
else if (Character.Health != Health)
|
||||||
|
{
|
||||||
|
Character.Health = Health;
|
||||||
|
|
||||||
|
if (Health <= 0 && !Character.IsDead)
|
||||||
|
{
|
||||||
|
Character.IsInvincible = false;
|
||||||
|
Character.Kill();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsJumping && !LastIsJumping)
|
||||||
|
{
|
||||||
|
Character.Task.Jump();
|
||||||
|
}
|
||||||
|
|
||||||
|
LastIsJumping = IsJumping;
|
||||||
|
|
||||||
|
if (IsRagdoll && !Character.IsRagdoll)
|
||||||
|
{
|
||||||
|
Character.CanRagdoll = true;
|
||||||
|
Character.Ragdoll();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!IsRagdoll && Character.IsRagdoll)
|
||||||
|
{
|
||||||
|
Character.CancelRagdoll();
|
||||||
|
Character.CanRagdoll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsJumping || IsOnFire)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsReloading && !Character.IsReloading)
|
||||||
|
{
|
||||||
|
Character.Task.ClearAll();
|
||||||
|
Character.Task.ReloadWeapon();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsReloading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Character.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash)
|
||||||
|
{
|
||||||
|
Character.Weapons.RemoveAll();
|
||||||
|
Character.Weapons.Give((WeaponHash)CurrentWeaponHash, -1, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsShooting)
|
||||||
|
{
|
||||||
|
Function.Call(Hash.SET_PED_INFINITE_AMMO_CLIP, Character, true);
|
||||||
|
|
||||||
|
if (!Character.IsInRange(Position, 0.5f))
|
||||||
|
{
|
||||||
|
Function.Call(Hash.TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, Character, Position.X, Position.Y,
|
||||||
|
Position.Z, AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, true, 2f, 2f, false, 0, false,
|
||||||
|
unchecked((int)FiringPattern.FullAuto));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Function.Call(Hash.TASK_SHOOT_AT_COORD, Character, AimCoords.X, AimCoords.Y, AimCoords.Z, 1500, (uint)FiringPattern.FullAuto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsAiming)
|
||||||
|
{
|
||||||
|
if (!Character.IsInRange(Position, 0.5f))
|
||||||
|
{
|
||||||
|
Function.Call(Hash.TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, Character, Position.X, Position.Y,
|
||||||
|
Position.Z, AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 2f, 2f, false, 512, false,
|
||||||
|
unchecked((int)FiringPattern.FullAuto));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Character.Task.AimAt(AimCoords, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WalkTo();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateCharacter(string username)
|
||||||
|
{
|
||||||
|
LastModelHash = ModelHash;
|
||||||
|
LastProps = Props;
|
||||||
|
|
||||||
|
Character = World.CreatePed(new Model(ModelHash), Position, Rotation.Z);
|
||||||
|
Character.RelationshipGroup = Main.RelationshipGroup;
|
||||||
|
Character.BlockPermanentEvents = true;
|
||||||
|
Character.CanRagdoll = false;
|
||||||
|
Character.IsInvincible = true;
|
||||||
|
Character.Health = Health;
|
||||||
|
|
||||||
|
if (username != null)
|
||||||
|
{
|
||||||
|
// Add a new blip for the ped
|
||||||
|
Character.AddBlip();
|
||||||
|
Character.AttachedBlip.Color = BlipColor.White;
|
||||||
|
Character.AttachedBlip.Scale = 0.8f;
|
||||||
|
Character.AttachedBlip.Name = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (KeyValuePair<int, int> prop in Props)
|
||||||
|
{
|
||||||
|
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, Character.Handle, prop.Key, prop.Value, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LastMoving;
|
||||||
|
private void WalkTo()
|
||||||
|
{
|
||||||
|
if (!Character.IsInRange(Position, 7.0f) && (LastMoving = true))
|
||||||
|
{
|
||||||
|
Character.Position = Position;
|
||||||
|
Character.Rotation = Rotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector3 predictPosition = Position + (Position - Character.Position) + Velocity;
|
||||||
|
float range = predictPosition.DistanceToSquared(Character.Position);
|
||||||
|
|
||||||
|
switch (Speed)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
if ((!Character.IsWalking || range > 0.25f) && (LastMoving = true))
|
||||||
|
{
|
||||||
|
float nrange = range * 2;
|
||||||
|
if (nrange > 1.0f)
|
||||||
|
{
|
||||||
|
nrange = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Character.Task.GoStraightTo(predictPosition);
|
||||||
|
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, Character, nrange);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if ((!Character.IsRunning || range > 0.50f) && (LastMoving = true))
|
||||||
|
{
|
||||||
|
Character.Task.RunTo(predictPosition, true);
|
||||||
|
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, Character, 1.0f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if ((!Character.IsSprinting || range > 0.75f) && (LastMoving = true))
|
||||||
|
{
|
||||||
|
Function.Call(Hash.TASK_GO_STRAIGHT_TO_COORD, Character, predictPosition.X, predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
|
||||||
|
Function.Call(Hash.SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, Character, 1.49f);
|
||||||
|
Function.Call(Hash.SET_PED_DESIRED_MOVE_BLEND_RATIO, Character, 1.0f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!Character.IsInRange(Position, 0.5f))
|
||||||
|
{
|
||||||
|
Character.Task.RunTo(Position, true, 500);
|
||||||
|
}
|
||||||
|
else if (LastMoving && (LastMoving = false))
|
||||||
|
{
|
||||||
|
Character.Task.StandStill(2000);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
Client/Entities/EntitiesPlayer.cs
Normal file
8
Client/Entities/EntitiesPlayer.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace CoopClient.Entities
|
||||||
|
{
|
||||||
|
public class EntitiesPlayer : EntitiesPed
|
||||||
|
{
|
||||||
|
public string SocialClubName { get; set; }
|
||||||
|
public string Username { get; set; } = "Player";
|
||||||
|
}
|
||||||
|
}
|
62
Client/Entities/EntitiesThread.cs
Normal file
62
Client/Entities/EntitiesThread.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
|
||||||
|
namespace CoopClient.Entities
|
||||||
|
{
|
||||||
|
public class EntitiesThread : Script
|
||||||
|
{
|
||||||
|
const int npcThreshold = 2500; // 2.5 seconds timeout
|
||||||
|
|
||||||
|
public EntitiesThread()
|
||||||
|
{
|
||||||
|
Tick += OnTick;
|
||||||
|
Interval = 1000 / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (Game.IsLoading || !Main.MainNetworking.IsOnServer() || !Main.NpcsAllowed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (Main.Npcs)
|
||||||
|
{
|
||||||
|
// Remove all NPCs with a last update older than npcThreshold or display this npc
|
||||||
|
foreach (KeyValuePair<string, EntitiesNpc> npc in new Dictionary<string, EntitiesNpc>(Main.Npcs))
|
||||||
|
{
|
||||||
|
if ((Environment.TickCount - npc.Value.LastUpdateReceived) > npcThreshold)
|
||||||
|
{
|
||||||
|
if (npc.Value.Character != null && npc.Value.Character.Exists() && npc.Value.Health > 0)
|
||||||
|
{
|
||||||
|
npc.Value.Character.Kill();
|
||||||
|
npc.Value.Character.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.Npcs.Remove(npc.Key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
npc.Value.DisplayLocally(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only if that player wants to share his NPCs with others
|
||||||
|
if (Main.ShareNpcsWithPlayers)
|
||||||
|
{
|
||||||
|
// Send all npcs from the current player
|
||||||
|
foreach (Ped ped in World.GetNearbyPeds(Game.Player.Character.Position, 150f)
|
||||||
|
.Where(p => p.Handle != Game.Player.Character.Handle && !p.IsDead && p.RelationshipGroup != Main.RelationshipGroup)
|
||||||
|
.OrderBy(p => (p.Position - Game.Player.Character.Position).Length())
|
||||||
|
.Take(10)) // only 10 for now
|
||||||
|
{
|
||||||
|
Main.MainNetworking.SendNpcData(ped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
362
Client/Main.cs
Normal file
362
Client/Main.cs
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using CoopClient.Entities;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Native;
|
||||||
|
|
||||||
|
using LemonUI;
|
||||||
|
using LemonUI.Menus;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class Main : Script
|
||||||
|
{
|
||||||
|
public static RelationshipGroup RelationshipGroup;
|
||||||
|
|
||||||
|
private bool GameLoaded = false;
|
||||||
|
|
||||||
|
public static readonly string CurrentModVersion = Enum.GetValues(typeof(ModVersion)).Cast<ModVersion>().Last().ToString();
|
||||||
|
|
||||||
|
public static bool ShareNpcsWithPlayers = false;
|
||||||
|
public static bool NpcsAllowed = false;
|
||||||
|
|
||||||
|
public static Settings MainSettings = Util.ReadSettings();
|
||||||
|
public static ObjectPool MainMenuPool = new ObjectPool();
|
||||||
|
public static NativeMenu MainMenu = new NativeMenu("GTACoop:R", CurrentModVersion.Replace("_", "."))
|
||||||
|
{
|
||||||
|
UseMouse = false,
|
||||||
|
Alignment = MainSettings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||||
|
};
|
||||||
|
public static NativeMenu MainSettingsMenu = new NativeMenu("GTACoop:R", "Settings", "Go to the settings")
|
||||||
|
{
|
||||||
|
UseMouse = false,
|
||||||
|
Alignment = MainSettings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||||
|
};
|
||||||
|
public static Chat MainChat = new Chat();
|
||||||
|
public static PlayerList MainPlayerList = new PlayerList();
|
||||||
|
|
||||||
|
public static Networking MainNetworking = new Networking();
|
||||||
|
|
||||||
|
public static string LocalPlayerID = null;
|
||||||
|
public static readonly Dictionary<string, EntitiesPlayer> Players = new Dictionary<string, EntitiesPlayer>();
|
||||||
|
public static readonly Dictionary<string, EntitiesNpc> Npcs = new Dictionary<string, EntitiesNpc>();
|
||||||
|
|
||||||
|
public Main()
|
||||||
|
{
|
||||||
|
Function.Call((Hash)0x0888C3502DBBEEF5); // _LOAD_MP_DLC_MAPS
|
||||||
|
Function.Call((Hash)0x9BAE5AD2508DF078, true); // _ENABLE_MP_DLC_MAPS
|
||||||
|
|
||||||
|
NativeItem usernameItem = new NativeItem("Username")
|
||||||
|
{
|
||||||
|
AltTitle = MainSettings.Username
|
||||||
|
};
|
||||||
|
usernameItem.Activated += (menu, item) =>
|
||||||
|
{
|
||||||
|
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, usernameItem.AltTitle, 20);
|
||||||
|
if (!string.IsNullOrWhiteSpace(newUsername))
|
||||||
|
{
|
||||||
|
MainSettings.Username = newUsername;
|
||||||
|
Util.SaveSettings();
|
||||||
|
|
||||||
|
usernameItem.AltTitle = newUsername;
|
||||||
|
MainMenuPool.RefreshAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NativeItem serverIpItem = new NativeItem("Server IP")
|
||||||
|
{
|
||||||
|
AltTitle = MainSettings.LastServerAddress
|
||||||
|
};
|
||||||
|
serverIpItem.Activated += (menu, item) =>
|
||||||
|
{
|
||||||
|
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, serverIpItem.AltTitle, 60);
|
||||||
|
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
|
||||||
|
{
|
||||||
|
MainSettings.LastServerAddress = newServerIp;
|
||||||
|
Util.SaveSettings();
|
||||||
|
|
||||||
|
serverIpItem.AltTitle = newServerIp;
|
||||||
|
MainMenuPool.RefreshAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
NativeItem serverConnectItem = new NativeItem("Connect");
|
||||||
|
serverConnectItem.Activated += (sender, item) =>
|
||||||
|
{
|
||||||
|
MainNetworking.DisConnectFromServer(MainSettings.LastServerAddress);
|
||||||
|
};
|
||||||
|
|
||||||
|
NativeCheckboxItem shareNpcsItem = new NativeCheckboxItem("Share Npcs", ShareNpcsWithPlayers);
|
||||||
|
shareNpcsItem.CheckboxChanged += (item, check) =>
|
||||||
|
{
|
||||||
|
ShareNpcsWithPlayers = shareNpcsItem.Checked;
|
||||||
|
};
|
||||||
|
shareNpcsItem.Enabled = false;
|
||||||
|
|
||||||
|
NativeCheckboxItem flipMenuItem = new NativeCheckboxItem("Flip menu", MainSettings.FlipMenu);
|
||||||
|
flipMenuItem.CheckboxChanged += (item, check) =>
|
||||||
|
{
|
||||||
|
MainMenu.Alignment = flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
|
||||||
|
MainSettingsMenu.Alignment = flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
|
||||||
|
|
||||||
|
MainSettings.FlipMenu = flipMenuItem.Checked;
|
||||||
|
Util.SaveSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
NativeItem aboutItem = new NativeItem("About", "~g~GTACoop~s~:~b~R ~s~by ~o~EntenKoeniq")
|
||||||
|
{
|
||||||
|
LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star")
|
||||||
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
NativeCheckboxItem useDebugItem = new NativeCheckboxItem("Debug", UseDebug);
|
||||||
|
useDebugItem.CheckboxChanged += (item, check) =>
|
||||||
|
{
|
||||||
|
UseDebug = useDebugItem.Checked;
|
||||||
|
|
||||||
|
if (!useDebugItem.Checked && DebugSyncPed != null)
|
||||||
|
{
|
||||||
|
if (DebugSyncPed.Character.Exists())
|
||||||
|
{
|
||||||
|
DebugSyncPed.Character.Kill();
|
||||||
|
DebugSyncPed.Character.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugSyncPed = null;
|
||||||
|
FullDebugSync = true;
|
||||||
|
Players.Remove("DebugKey");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MainMenu.Add(usernameItem);
|
||||||
|
MainMenu.Add(serverIpItem);
|
||||||
|
MainMenu.Add(serverConnectItem);
|
||||||
|
MainMenu.AddSubMenu(MainSettingsMenu);
|
||||||
|
MainSettingsMenu.Add(shareNpcsItem);
|
||||||
|
MainSettingsMenu.Add(flipMenuItem);
|
||||||
|
#if DEBUG
|
||||||
|
MainSettingsMenu.Add(useDebugItem);
|
||||||
|
#endif
|
||||||
|
MainMenu.Add(aboutItem);
|
||||||
|
|
||||||
|
MainMenuPool.Add(MainMenu);
|
||||||
|
MainMenuPool.Add(MainSettingsMenu);
|
||||||
|
|
||||||
|
Tick += OnTick;
|
||||||
|
KeyDown += OnKeyDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int LastDataSend;
|
||||||
|
private void OnTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (Game.IsLoading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!GameLoaded && (GameLoaded = true))
|
||||||
|
{
|
||||||
|
RelationshipGroup = World.AddRelationshipGroup("SYNCPED");
|
||||||
|
Game.Player.Character.RelationshipGroup = RelationshipGroup;
|
||||||
|
|
||||||
|
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, Game.Player.Character, true, true);
|
||||||
|
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainMenuPool.Process();
|
||||||
|
|
||||||
|
MainNetworking.ReceiveMessages();
|
||||||
|
|
||||||
|
if (!MainNetworking.IsOnServer())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.Player.Character.IsGettingIntoVehicle)
|
||||||
|
{
|
||||||
|
GTA.UI.Notification.Show("~y~Vehicles are not sync yet!", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainChat.Tick();
|
||||||
|
if (!MainChat.Focused && !MainMenuPool.AreAnyVisible)
|
||||||
|
{
|
||||||
|
MainPlayerList.Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display all players
|
||||||
|
foreach (KeyValuePair<string, EntitiesPlayer> player in Players)
|
||||||
|
{
|
||||||
|
player.Value.DisplayLocally(player.Value.Username);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UseDebug)
|
||||||
|
{
|
||||||
|
Debug();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Environment.TickCount - LastDataSend) >= (1000 / 60))
|
||||||
|
{
|
||||||
|
MainNetworking.SendPlayerData();
|
||||||
|
|
||||||
|
LastDataSend = Environment.TickCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (MainChat.Focused)
|
||||||
|
{
|
||||||
|
MainChat.OnKeyDown(e.KeyCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e.KeyCode)
|
||||||
|
{
|
||||||
|
case Keys.F9:
|
||||||
|
if (MainMenuPool.AreAnyVisible)
|
||||||
|
{
|
||||||
|
MainMenu.Visible = false;
|
||||||
|
MainSettingsMenu.Visible = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainMenu.Visible = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Keys.T:
|
||||||
|
if (MainNetworking.IsOnServer())
|
||||||
|
{
|
||||||
|
MainChat.Focused = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Keys.Y:
|
||||||
|
if (MainNetworking.IsOnServer())
|
||||||
|
{
|
||||||
|
int time = Environment.TickCount;
|
||||||
|
|
||||||
|
MainPlayerList.Pressed = (time - MainPlayerList.Pressed) < 5000 ? (time - 6000) : time;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime ArtificialLagCounter = DateTime.MinValue;
|
||||||
|
private EntitiesPlayer DebugSyncPed;
|
||||||
|
private bool FullDebugSync = true;
|
||||||
|
private bool UseDebug = false;
|
||||||
|
|
||||||
|
private void Debug()
|
||||||
|
{
|
||||||
|
var player = Game.Player.Character;
|
||||||
|
if (!Players.ContainsKey("DebugKey"))
|
||||||
|
{
|
||||||
|
Players.Add("DebugKey", new EntitiesPlayer() { SocialClubName = "DEBUG", Username = "DebugPlayer" });
|
||||||
|
DebugSyncPed = Players["DebugKey"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.IsInVehicle() && DateTime.Now.Subtract(ArtificialLagCounter).TotalMilliseconds >= 300)
|
||||||
|
{
|
||||||
|
ArtificialLagCounter = DateTime.Now;
|
||||||
|
|
||||||
|
#region SPEED
|
||||||
|
byte speed = 0;
|
||||||
|
if (Game.Player.Character.IsWalking)
|
||||||
|
{
|
||||||
|
speed = 1;
|
||||||
|
}
|
||||||
|
else if (Game.Player.Character.IsRunning)
|
||||||
|
{
|
||||||
|
speed = 2;
|
||||||
|
}
|
||||||
|
else if (Game.Player.Character.IsSprinting || Game.IsControlPressed(GTA.Control.Sprint))
|
||||||
|
{
|
||||||
|
speed = 3;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SHOOTING - AIMING
|
||||||
|
bool aiming = player.IsAiming;
|
||||||
|
bool shooting = player.IsShooting && player.Weapons.Current?.AmmoInClip != 0;
|
||||||
|
|
||||||
|
GTA.Math.Vector3 aimCoord = new GTA.Math.Vector3();
|
||||||
|
if (aiming || shooting)
|
||||||
|
{
|
||||||
|
aimCoord = Util.RaycastEverything(new GTA.Math.Vector2(0, 0));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region FLAG
|
||||||
|
byte? flags = 0;
|
||||||
|
|
||||||
|
if (FullDebugSync)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.LastSyncWasFull;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiming)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsAiming;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shooting)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsShooting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsReloading)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsReloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsJumping)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsJumping;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsRagdoll)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsRagdoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsOnFire)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsOnFire;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
if (FullDebugSync)
|
||||||
|
{
|
||||||
|
DebugSyncPed.ModelHash = player.Model.Hash;
|
||||||
|
DebugSyncPed.Props = Util.GetPedProps(player);
|
||||||
|
}
|
||||||
|
DebugSyncPed.Health = player.Health;
|
||||||
|
DebugSyncPed.Position = player.Position;
|
||||||
|
DebugSyncPed.Rotation = player.Rotation;
|
||||||
|
DebugSyncPed.Velocity = player.Velocity;
|
||||||
|
DebugSyncPed.Speed = speed;
|
||||||
|
DebugSyncPed.AimCoords = aimCoord;
|
||||||
|
DebugSyncPed.CurrentWeaponHash = (int)player.Weapons.Current.Hash;
|
||||||
|
DebugSyncPed.LastSyncWasFull = (flags.Value & (byte)PedDataFlags.LastSyncWasFull) > 0;
|
||||||
|
DebugSyncPed.IsAiming = (flags.Value & (byte)PedDataFlags.IsAiming) > 0;
|
||||||
|
DebugSyncPed.IsShooting = (flags.Value & (byte)PedDataFlags.IsShooting) > 0;
|
||||||
|
DebugSyncPed.IsReloading = (flags.Value & (byte)PedDataFlags.IsReloading) > 0;
|
||||||
|
DebugSyncPed.IsJumping = (flags.Value & (byte)PedDataFlags.IsJumping) > 0;
|
||||||
|
DebugSyncPed.IsRagdoll = (flags.Value & (byte)PedDataFlags.IsRagdoll) > 0;
|
||||||
|
DebugSyncPed.IsOnFire = (flags.Value & (byte)PedDataFlags.IsOnFire) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DebugSyncPed.Character != null && DebugSyncPed.Character.Exists())
|
||||||
|
{
|
||||||
|
Function.Call(Hash.SET_ENTITY_NO_COLLISION_ENTITY, DebugSyncPed.Character.Handle, player.Handle, false);
|
||||||
|
Function.Call(Hash.SET_ENTITY_NO_COLLISION_ENTITY, player.Handle, DebugSyncPed.Character.Handle, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
FullDebugSync = !FullDebugSync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
556
Client/Networking.cs
Normal file
556
Client/Networking.cs
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using CoopClient.Entities;
|
||||||
|
|
||||||
|
using Lidgren.Network;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Math;
|
||||||
|
using GTA.Native;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class Networking
|
||||||
|
{
|
||||||
|
public NetClient Client;
|
||||||
|
|
||||||
|
public void DisConnectFromServer(string address)
|
||||||
|
{
|
||||||
|
if (IsOnServer())
|
||||||
|
{
|
||||||
|
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||||
|
new PlayerDisconnectPacket() { Player = Main.LocalPlayerID }.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
|
||||||
|
Client.FlushSendQueue();
|
||||||
|
|
||||||
|
Client.Disconnect("Disconnected");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 6d4ec318f1c43bd62fe13d5a7ab28650 = GTACOOP:R
|
||||||
|
NetPeerConfiguration config = new NetPeerConfiguration("6d4ec318f1c43bd62fe13d5a7ab28650")
|
||||||
|
{
|
||||||
|
AutoFlushSendQueue = false
|
||||||
|
};
|
||||||
|
|
||||||
|
Client = new NetClient(config);
|
||||||
|
|
||||||
|
Client.Start();
|
||||||
|
|
||||||
|
string[] ip = address.Split(':');
|
||||||
|
|
||||||
|
// Send HandshakePacket
|
||||||
|
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||||
|
new HandshakePacket()
|
||||||
|
{
|
||||||
|
ID = string.Empty,
|
||||||
|
SocialClubName = Game.Player.Name,
|
||||||
|
Username = Main.MainSettings.Username,
|
||||||
|
ModVersion = Main.CurrentModVersion,
|
||||||
|
NpcsAllowed = false
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
|
||||||
|
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOnServer()
|
||||||
|
{
|
||||||
|
return Client?.ConnectionStatus == NetConnectionStatus.Connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveMessages()
|
||||||
|
{
|
||||||
|
if (Client == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetIncomingMessage message;
|
||||||
|
|
||||||
|
while ((message = Client.ReadMessage()) != null)
|
||||||
|
{
|
||||||
|
switch (message.MessageType)
|
||||||
|
{
|
||||||
|
case NetIncomingMessageType.StatusChanged:
|
||||||
|
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
|
||||||
|
|
||||||
|
string reason = message.ReadString();
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case NetConnectionStatus.InitiatedConnect:
|
||||||
|
Main.MainMenu.Items[0].Enabled = false;
|
||||||
|
Main.MainMenu.Items[1].Enabled = false;
|
||||||
|
Main.MainMenu.Items[2].Enabled = false;
|
||||||
|
GTA.UI.Notification.Show("~y~Trying to connect...");
|
||||||
|
break;
|
||||||
|
case NetConnectionStatus.Connected:
|
||||||
|
if (message.SenderConnection.RemoteHailMessage.ReadByte() != (byte)PacketTypes.HandshakePacket)
|
||||||
|
{
|
||||||
|
Client.Disconnect("Wrong packet!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Packet remoteHailMessagePacket;
|
||||||
|
remoteHailMessagePacket = new HandshakePacket();
|
||||||
|
remoteHailMessagePacket.NetIncomingMessageToPacket(message.SenderConnection.RemoteHailMessage);
|
||||||
|
|
||||||
|
HandshakePacket handshakePacket = (HandshakePacket)remoteHailMessagePacket;
|
||||||
|
Main.LocalPlayerID = handshakePacket.ID;
|
||||||
|
Main.NpcsAllowed = handshakePacket.NpcsAllowed;
|
||||||
|
|
||||||
|
foreach (Ped entity in World.GetAllPeds())
|
||||||
|
{
|
||||||
|
if (entity.Handle != Game.Player.Character.Handle)
|
||||||
|
{
|
||||||
|
entity.Kill();
|
||||||
|
entity.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Vehicle vehicle in World.GetAllVehicles())
|
||||||
|
{
|
||||||
|
if (Game.Player.Character.CurrentVehicle?.Handle != vehicle.Handle)
|
||||||
|
{
|
||||||
|
vehicle.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Function.Call(Hash.SET_GARBAGE_TRUCKS, 0);
|
||||||
|
Function.Call(Hash.SET_RANDOM_BOATS, 0);
|
||||||
|
Function.Call(Hash.SET_RANDOM_TRAINS, 0);
|
||||||
|
|
||||||
|
Main.MainMenu.Items[2].Enabled = true;
|
||||||
|
Main.MainMenu.Items[2].Title = "Disconnect";
|
||||||
|
Main.MainSettingsMenu.Items[0].Enabled = Main.NpcsAllowed;
|
||||||
|
|
||||||
|
Main.MainChat.Init();
|
||||||
|
Main.MainPlayerList.Init(Main.MainSettings.Username);
|
||||||
|
|
||||||
|
// Send player connect packet
|
||||||
|
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||||
|
new PlayerConnectPacket()
|
||||||
|
{
|
||||||
|
Player = Main.LocalPlayerID,
|
||||||
|
SocialClubName = string.Empty,
|
||||||
|
Username = string.Empty
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
|
||||||
|
Client.FlushSendQueue();
|
||||||
|
|
||||||
|
GTA.UI.Notification.Show("~g~Connected!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetConnectionStatus.Disconnected:
|
||||||
|
GTA.UI.Notification.Show("~r~" + reason);
|
||||||
|
|
||||||
|
// Reset all values
|
||||||
|
FullPlayerSync = true;
|
||||||
|
|
||||||
|
Main.NpcsAllowed = false;
|
||||||
|
|
||||||
|
if (Main.MainChat.Focused)
|
||||||
|
{
|
||||||
|
Main.MainChat.Focused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.MainChat.Clear();
|
||||||
|
|
||||||
|
Main.MainMenu.Items[0].Enabled = true;
|
||||||
|
Main.MainMenu.Items[1].Enabled = true;
|
||||||
|
Main.MainMenu.Items[2].Enabled = true;
|
||||||
|
Main.MainMenu.Items[2].Title = "Connect";
|
||||||
|
Main.MainSettingsMenu.Items[0].Enabled = false;
|
||||||
|
|
||||||
|
Main.Players.Clear();
|
||||||
|
Main.Npcs.Clear();
|
||||||
|
|
||||||
|
Vector3 pos = Game.Player.Character.Position;
|
||||||
|
Function.Call(Hash.CLEAR_AREA_OF_PEDS, pos.X, pos.Y, pos.Z, 300.0f, 0);
|
||||||
|
Function.Call(Hash.CLEAR_AREA_OF_VEHICLES, pos.X, pos.Y, pos.Z, 300.0f, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.Data:
|
||||||
|
byte packetType = message.ReadByte();
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
switch (packetType)
|
||||||
|
{
|
||||||
|
case (byte)PacketTypes.PlayerConnectPacket:
|
||||||
|
packet = new PlayerConnectPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
PlayerConnect((PlayerConnectPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.PlayerDisconnectPacket:
|
||||||
|
packet = new PlayerDisconnectPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
PlayerDisconnect((PlayerDisconnectPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.FullSyncPlayerPacket:
|
||||||
|
packet = new FullSyncPlayerPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
FullSyncPlayer((FullSyncPlayerPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.FullSyncNpcPacket:
|
||||||
|
packet = new FullSyncNpcPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
FullSyncNpc((FullSyncNpcPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.LightSyncPlayerPacket:
|
||||||
|
packet = new LightSyncPlayerPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
LightSyncPlayer((LightSyncPlayerPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.ChatMessagePacket:
|
||||||
|
packet = new ChatMessagePacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
|
||||||
|
ChatMessagePacket chatMessagePacket = (ChatMessagePacket)packet;
|
||||||
|
Main.MainChat.AddMessage(chatMessagePacket.Username, chatMessagePacket.Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.DebugMessage:
|
||||||
|
case NetIncomingMessageType.ErrorMessage:
|
||||||
|
case NetIncomingMessageType.WarningMessage:
|
||||||
|
case NetIncomingMessageType.VerboseDebugMessage:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.Recycle(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region GET
|
||||||
|
private void PlayerConnect(PlayerConnectPacket packet)
|
||||||
|
{
|
||||||
|
EntitiesPlayer player = new EntitiesPlayer()
|
||||||
|
{
|
||||||
|
SocialClubName = packet.SocialClubName,
|
||||||
|
Username = packet.Username
|
||||||
|
};
|
||||||
|
|
||||||
|
Main.Players.Add(packet.Player, player);
|
||||||
|
|
||||||
|
Main.MainPlayerList.Update(Main.Players, Main.MainSettings.Username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerDisconnect(PlayerDisconnectPacket packet)
|
||||||
|
{
|
||||||
|
if (Main.Players.ContainsKey(packet.Player))
|
||||||
|
{
|
||||||
|
Main.Players.Remove(packet.Player);
|
||||||
|
|
||||||
|
Main.MainPlayerList.Update(Main.Players, Main.MainSettings.Username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FullSyncPlayer(FullSyncPlayerPacket packet)
|
||||||
|
{
|
||||||
|
if (Main.Players.ContainsKey(packet.Player))
|
||||||
|
{
|
||||||
|
EntitiesPlayer player = Main.Players[packet.Player];
|
||||||
|
player.ModelHash = packet.ModelHash;
|
||||||
|
player.Props = packet.Props;
|
||||||
|
player.Health = packet.Health;
|
||||||
|
player.Position = packet.Position.ToVector();
|
||||||
|
player.Rotation = packet.Rotation.ToVector();
|
||||||
|
player.Velocity = packet.Velocity.ToVector();
|
||||||
|
player.Speed = packet.Speed;
|
||||||
|
player.AimCoords = packet.AimCoords.ToVector();
|
||||||
|
player.LastSyncWasFull = (packet.Flag.Value & (byte)PedDataFlags.LastSyncWasFull) > 0;
|
||||||
|
player.IsAiming = (packet.Flag.Value & (byte)PedDataFlags.IsAiming) > 0;
|
||||||
|
player.IsShooting = (packet.Flag.Value & (byte)PedDataFlags.IsShooting) > 0;
|
||||||
|
player.IsReloading = (packet.Flag.Value & (byte)PedDataFlags.IsReloading) > 0;
|
||||||
|
player.IsJumping = (packet.Flag.Value & (byte)PedDataFlags.IsJumping) > 0;
|
||||||
|
player.IsRagdoll = (packet.Flag.Value & (byte)PedDataFlags.IsRagdoll) > 0;
|
||||||
|
player.IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FullSyncNpc(FullSyncNpcPacket packet)
|
||||||
|
{
|
||||||
|
if (Main.Npcs.ContainsKey(packet.ID))
|
||||||
|
{
|
||||||
|
EntitiesNpc npc = Main.Npcs[packet.ID];
|
||||||
|
npc.LastUpdateReceived = Environment.TickCount;
|
||||||
|
npc.ModelHash = packet.ModelHash;
|
||||||
|
npc.Props = packet.Props;
|
||||||
|
npc.Health = packet.Health;
|
||||||
|
npc.Position = packet.Position.ToVector();
|
||||||
|
npc.Rotation = packet.Rotation.ToVector();
|
||||||
|
npc.Velocity = packet.Velocity.ToVector();
|
||||||
|
npc.Speed = packet.Speed;
|
||||||
|
npc.AimCoords = packet.AimCoords.ToVector();
|
||||||
|
npc.LastSyncWasFull = (packet.Flag.Value & (byte)PedDataFlags.LastSyncWasFull) > 0;
|
||||||
|
npc.IsAiming = (packet.Flag.Value & (byte)PedDataFlags.IsAiming) > 0;
|
||||||
|
npc.IsShooting = (packet.Flag.Value & (byte)PedDataFlags.IsShooting) > 0;
|
||||||
|
npc.IsReloading = (packet.Flag.Value & (byte)PedDataFlags.IsReloading) > 0;
|
||||||
|
npc.IsJumping = (packet.Flag.Value & (byte)PedDataFlags.IsJumping) > 0;
|
||||||
|
npc.IsRagdoll = (packet.Flag.Value & (byte)PedDataFlags.IsRagdoll) > 0;
|
||||||
|
npc.IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Main.Npcs.Add(packet.ID, new EntitiesNpc()
|
||||||
|
{
|
||||||
|
LastUpdateReceived = Environment.TickCount,
|
||||||
|
ModelHash = packet.ModelHash,
|
||||||
|
Props = packet.Props,
|
||||||
|
Health = packet.Health,
|
||||||
|
Position = packet.Position.ToVector(),
|
||||||
|
Rotation = packet.Rotation.ToVector(),
|
||||||
|
Velocity = packet.Velocity.ToVector(),
|
||||||
|
Speed = packet.Speed,
|
||||||
|
AimCoords = packet.AimCoords.ToVector(),
|
||||||
|
LastSyncWasFull = (packet.Flag.Value & (byte)PedDataFlags.LastSyncWasFull) > 0,
|
||||||
|
IsAiming = (packet.Flag.Value & (byte)PedDataFlags.IsAiming) > 0,
|
||||||
|
IsShooting = (packet.Flag.Value & (byte)PedDataFlags.IsShooting) > 0,
|
||||||
|
IsReloading = (packet.Flag.Value & (byte)PedDataFlags.IsReloading) > 0,
|
||||||
|
IsJumping = (packet.Flag.Value & (byte)PedDataFlags.IsJumping) > 0,
|
||||||
|
IsRagdoll = (packet.Flag.Value & (byte)PedDataFlags.IsRagdoll) > 0,
|
||||||
|
IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LightSyncPlayer(LightSyncPlayerPacket packet)
|
||||||
|
{
|
||||||
|
if (Main.Players.ContainsKey(packet.Player))
|
||||||
|
{
|
||||||
|
EntitiesPlayer player = Main.Players[packet.Player];
|
||||||
|
player.Health = packet.Health;
|
||||||
|
player.Position = packet.Position.ToVector();
|
||||||
|
player.Rotation = packet.Rotation.ToVector();
|
||||||
|
player.Velocity = packet.Velocity.ToVector();
|
||||||
|
player.Speed = packet.Speed;
|
||||||
|
player.LastSyncWasFull = (packet.Flag.Value & (byte)PedDataFlags.LastSyncWasFull) > 0;
|
||||||
|
player.IsAiming = (packet.Flag.Value & (byte)PedDataFlags.IsAiming) > 0;
|
||||||
|
player.IsShooting = (packet.Flag.Value & (byte)PedDataFlags.IsShooting) > 0;
|
||||||
|
player.IsReloading = (packet.Flag.Value & (byte)PedDataFlags.IsReloading) > 0;
|
||||||
|
player.IsJumping = (packet.Flag.Value & (byte)PedDataFlags.IsJumping) > 0;
|
||||||
|
player.IsRagdoll = (packet.Flag.Value & (byte)PedDataFlags.IsRagdoll) > 0;
|
||||||
|
player.IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SEND
|
||||||
|
private bool FullPlayerSync = true;
|
||||||
|
public void SendPlayerData()
|
||||||
|
{
|
||||||
|
Ped player = Game.Player.Character;
|
||||||
|
|
||||||
|
#region SPEED
|
||||||
|
byte speed = 0;
|
||||||
|
if (Game.Player.Character.IsWalking)
|
||||||
|
{
|
||||||
|
speed = 1;
|
||||||
|
}
|
||||||
|
else if (Game.Player.Character.IsRunning)
|
||||||
|
{
|
||||||
|
speed = 2;
|
||||||
|
}
|
||||||
|
else if (Game.Player.Character.IsSprinting)
|
||||||
|
{
|
||||||
|
speed = 3;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SHOOTING - AIMING
|
||||||
|
bool aiming = player.IsAiming;
|
||||||
|
bool shooting = player.IsShooting && player.Weapons.Current?.AmmoInClip != 0;
|
||||||
|
|
||||||
|
Vector3 aimCoord = new Vector3();
|
||||||
|
if (aiming || shooting)
|
||||||
|
{
|
||||||
|
aimCoord = Util.RaycastEverything(new Vector2(0, 0));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Flags
|
||||||
|
byte? flags = 0;
|
||||||
|
|
||||||
|
if (FullPlayerSync)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.LastSyncWasFull;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiming)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsAiming;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shooting)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsShooting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsReloading)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsReloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsJumping)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsJumping;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsRagdoll)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsRagdoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.IsOnFire)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsOnFire;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||||
|
|
||||||
|
if (FullPlayerSync)
|
||||||
|
{
|
||||||
|
new FullSyncPlayerPacket()
|
||||||
|
{
|
||||||
|
Player = Main.LocalPlayerID,
|
||||||
|
ModelHash = player.Model.Hash,
|
||||||
|
Props = Util.GetPedProps(player),
|
||||||
|
Health = player.Health,
|
||||||
|
Position = player.Position.ToLVector(),
|
||||||
|
Rotation = player.Rotation.ToLVector(),
|
||||||
|
Velocity = player.Velocity.ToLVector(),
|
||||||
|
Speed = speed,
|
||||||
|
AimCoords = aimCoord.ToLVector(),
|
||||||
|
CurrentWeaponHash = (int)player.Weapons.Current.Hash,
|
||||||
|
Flag = flags
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new LightSyncPlayerPacket()
|
||||||
|
{
|
||||||
|
Player = Main.LocalPlayerID,
|
||||||
|
Health = player.Health,
|
||||||
|
Position = player.Position.ToLVector(),
|
||||||
|
Rotation = player.Rotation.ToLVector(),
|
||||||
|
Velocity = player.Velocity.ToLVector(),
|
||||||
|
Speed = speed,
|
||||||
|
AimCoords = aimCoord.ToLVector(),
|
||||||
|
CurrentWeaponHash = (int)player.Weapons.Current.Hash,
|
||||||
|
Flag = flags
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
|
||||||
|
Client.FlushSendQueue();
|
||||||
|
|
||||||
|
FullPlayerSync = !FullPlayerSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendNpcData(Ped npc)
|
||||||
|
{
|
||||||
|
#region SPEED
|
||||||
|
byte speed = 0;
|
||||||
|
if (npc.IsWalking)
|
||||||
|
{
|
||||||
|
speed = 1;
|
||||||
|
}
|
||||||
|
else if (npc.IsRunning)
|
||||||
|
{
|
||||||
|
speed = 2;
|
||||||
|
}
|
||||||
|
else if (npc.IsSprinting)
|
||||||
|
{
|
||||||
|
speed = 3;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SHOOTING - AIMING
|
||||||
|
bool aiming = npc.IsAiming;
|
||||||
|
bool shooting = npc.IsShooting && npc.Weapons.Current?.AmmoInClip != 0;
|
||||||
|
|
||||||
|
Vector3 aimCoord = new Vector3();
|
||||||
|
if (aiming || shooting)
|
||||||
|
{
|
||||||
|
aimCoord = Util.GetLastWeaponImpact(npc);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Flags
|
||||||
|
byte? flags = 0;
|
||||||
|
|
||||||
|
// FullSync = true
|
||||||
|
flags |= (byte)PedDataFlags.LastSyncWasFull;
|
||||||
|
|
||||||
|
if (shooting)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsShooting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiming)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsAiming;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npc.IsReloading)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsReloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npc.IsJumping)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsJumping;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npc.IsRagdoll)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsRagdoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npc.IsOnFire)
|
||||||
|
{
|
||||||
|
flags |= (byte)PedDataFlags.IsOnFire;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||||
|
|
||||||
|
new FullSyncNpcPacket()
|
||||||
|
{
|
||||||
|
ID = Main.LocalPlayerID + npc.Handle,
|
||||||
|
ModelHash = npc.Model.Hash,
|
||||||
|
Props = Util.GetPedProps(npc),
|
||||||
|
Health = npc.Health,
|
||||||
|
Position = npc.Position.ToLVector(),
|
||||||
|
Rotation = npc.Rotation.ToLVector(),
|
||||||
|
Velocity = npc.Velocity.ToLVector(),
|
||||||
|
Speed = speed,
|
||||||
|
AimCoords = aimCoord.ToLVector(),
|
||||||
|
CurrentWeaponHash = (int)npc.Weapons.Current.Hash,
|
||||||
|
Flag = flags
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
|
||||||
|
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
|
||||||
|
Client.FlushSendQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendChatMessage(string message)
|
||||||
|
{
|
||||||
|
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||||
|
new ChatMessagePacket()
|
||||||
|
{
|
||||||
|
Username = Main.MainSettings.Username,
|
||||||
|
Message = message
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
|
||||||
|
Client.FlushSendQueue();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
478
Client/Packets.cs
Normal file
478
Client/Packets.cs
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Lidgren.Network;
|
||||||
|
using ProtoBuf;
|
||||||
|
|
||||||
|
using GTA.Math;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
#region CLIENT-ONLY
|
||||||
|
public static class VectorExtensions
|
||||||
|
{
|
||||||
|
public static LVector3 ToLVector(this Vector3 vec)
|
||||||
|
{
|
||||||
|
return new LVector3()
|
||||||
|
{
|
||||||
|
X = vec.X,
|
||||||
|
Y = vec.Y,
|
||||||
|
Z = vec.Z,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public struct LVector3
|
||||||
|
{
|
||||||
|
#region CLIENT-ONLY
|
||||||
|
public Vector3 ToVector()
|
||||||
|
{
|
||||||
|
return new Vector3(X, Y, Z);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public LVector3(float X, float Y, float Z)
|
||||||
|
{
|
||||||
|
this.X = X;
|
||||||
|
this.Y = Y;
|
||||||
|
this.Z = Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public float X { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public float Y { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public float Z { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ModVersion
|
||||||
|
{
|
||||||
|
V0_1_0
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PacketTypes
|
||||||
|
{
|
||||||
|
HandshakePacket,
|
||||||
|
PlayerConnectPacket,
|
||||||
|
PlayerDisconnectPacket,
|
||||||
|
FullSyncPlayerPacket,
|
||||||
|
FullSyncNpcPacket,
|
||||||
|
LightSyncPlayerPacket,
|
||||||
|
ChatMessagePacket
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PedDataFlags
|
||||||
|
{
|
||||||
|
LastSyncWasFull = 1 << 0,
|
||||||
|
IsAiming = 1 << 1,
|
||||||
|
IsShooting = 1 << 2,
|
||||||
|
IsReloading = 1 << 3,
|
||||||
|
IsJumping = 1 << 4,
|
||||||
|
IsRagdoll = 1 << 5,
|
||||||
|
IsOnFire = 1 << 6
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPacket
|
||||||
|
{
|
||||||
|
void PacketToNetOutGoingMessage(NetOutgoingMessage message);
|
||||||
|
void NetIncomingMessageToPacket(NetIncomingMessage message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class Packet : IPacket
|
||||||
|
{
|
||||||
|
public abstract void PacketToNetOutGoingMessage(NetOutgoingMessage message);
|
||||||
|
public abstract void NetIncomingMessageToPacket(NetIncomingMessage message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class HandshakePacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string ID { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string SocialClubName { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public string ModVersion { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public bool NpcsAllowed { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.HandshakePacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
HandshakePacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<HandshakePacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID = data.ID;
|
||||||
|
SocialClubName = data.SocialClubName;
|
||||||
|
Username = data.Username;
|
||||||
|
ModVersion = data.ModVersion;
|
||||||
|
NpcsAllowed = data.NpcsAllowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class PlayerConnectPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string SocialClubName { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.PlayerConnectPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
PlayerConnectPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<PlayerConnectPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
SocialClubName = data.SocialClubName;
|
||||||
|
Username = data.Username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class PlayerDisconnectPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.PlayerDisconnectPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
PlayerDisconnectPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<PlayerDisconnectPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class FullSyncPlayerPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public int ModelHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public Dictionary<int, int> Props { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public int Health { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public LVector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public LVector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
public LVector3 AimCoords { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(10)]
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(11)]
|
||||||
|
public byte? Flag { get; set; } = 0;
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.FullSyncPlayerPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
FullSyncPlayerPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<FullSyncPlayerPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
ModelHash = data.ModelHash;
|
||||||
|
Props = data.Props;
|
||||||
|
Health = data.Health;
|
||||||
|
Position = data.Position;
|
||||||
|
Rotation = data.Rotation;
|
||||||
|
Velocity = data.Velocity;
|
||||||
|
Speed = data.Speed;
|
||||||
|
AimCoords = data.AimCoords;
|
||||||
|
CurrentWeaponHash = data.CurrentWeaponHash;
|
||||||
|
Flag = data.Flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class FullSyncNpcPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string ID { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public int ModelHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public Dictionary<int, int> Props { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public int Health { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public LVector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public LVector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
public LVector3 AimCoords { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(10)]
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(11)]
|
||||||
|
public byte? Flag { get; set; } = 0;
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.FullSyncNpcPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
FullSyncNpcPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<FullSyncNpcPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID = data.ID;
|
||||||
|
ModelHash = data.ModelHash;
|
||||||
|
Props = data.Props;
|
||||||
|
Health = data.Health;
|
||||||
|
Position = data.Position;
|
||||||
|
Rotation = data.Rotation;
|
||||||
|
Velocity = data.Velocity;
|
||||||
|
Speed = data.Speed;
|
||||||
|
AimCoords = data.AimCoords;
|
||||||
|
CurrentWeaponHash = data.CurrentWeaponHash;
|
||||||
|
Flag = data.Flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class LightSyncPlayerPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public int Health { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public LVector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public LVector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public LVector3 AimCoords { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
public byte? Flag { get; set; } = 0;
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.LightSyncPlayerPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
LightSyncPlayerPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<LightSyncPlayerPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
Health = data.Health;
|
||||||
|
Position = data.Position;
|
||||||
|
Rotation = data.Rotation;
|
||||||
|
Velocity = data.Velocity;
|
||||||
|
Speed = data.Speed;
|
||||||
|
AimCoords = data.AimCoords;
|
||||||
|
CurrentWeaponHash = data.CurrentWeaponHash;
|
||||||
|
Flag = data.Flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class ChatMessagePacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.ChatMessagePacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
ChatMessagePacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<ChatMessagePacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Username = data.Username;
|
||||||
|
Message = data.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
Client/PlayerList.cs
Normal file
47
Client/PlayerList.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using CoopClient.Entities;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Native;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class PlayerList
|
||||||
|
{
|
||||||
|
private readonly Scaleform MainScaleform = new Scaleform("mp_mm_card_freemode");
|
||||||
|
public int Pressed { get; set; }
|
||||||
|
|
||||||
|
public void Init(string localUsername)
|
||||||
|
{
|
||||||
|
MainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
|
||||||
|
MainScaleform.CallFunction("SET_DATA_SLOT", 0, "", localUsername, 116, 0, 0, "", "", 2, "", "", ' ');
|
||||||
|
MainScaleform.CallFunction("SET_TITLE", "Player list", "1 players");
|
||||||
|
MainScaleform.CallFunction("DISPLAY_VIEW");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Dictionary<string, EntitiesPlayer> players, string LocalUsername)
|
||||||
|
{
|
||||||
|
MainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
|
||||||
|
MainScaleform.CallFunction("SET_DATA_SLOT", 0, "", LocalUsername, 116, 0, 0, "", "", 2, "", "", ' ');
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
foreach (KeyValuePair<string, EntitiesPlayer> player in players)
|
||||||
|
{
|
||||||
|
MainScaleform.CallFunction("SET_DATA_SLOT", i++, "", player.Value.Username, 116, 0, i - 1, "", "", 2, "", "", ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
MainScaleform.CallFunction("SET_TITLE", "Player list", (players.Count + 1) + " players");
|
||||||
|
MainScaleform.CallFunction("DISPLAY_VIEW");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
if ((Environment.TickCount - Pressed) < 5000)
|
||||||
|
{
|
||||||
|
Function.Call(Hash.DRAW_SCALEFORM_MOVIE, MainScaleform.Handle, 0.122f, 0.3f, 0.28f, 0.6f, 255, 255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
Client/Properties/AssemblyInfo.cs
Normal file
36
Client/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("Client")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Client")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2021")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("ef56d109-1f22-43e0-9dff-cfcfb94e0681")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
9
Client/Settings.cs
Normal file
9
Client/Settings.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class Settings
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = "Player";
|
||||||
|
public string LastServerAddress { get; set; } = "127.0.0.1:4499";
|
||||||
|
public bool FlipMenu { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
189
Client/Util.cs
Normal file
189
Client/Util.cs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Native;
|
||||||
|
using GTA.Math;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
class Util
|
||||||
|
{
|
||||||
|
public static Dictionary<int, int> GetPedProps(Ped ped)
|
||||||
|
{
|
||||||
|
Dictionary<int, int> result = new Dictionary<int, int>();
|
||||||
|
for (int i = 0; i < 11; i++)
|
||||||
|
{
|
||||||
|
int mod = Function.Call<int>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
|
||||||
|
result.Add(i, mod);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Settings ReadSettings()
|
||||||
|
{
|
||||||
|
XmlSerializer ser = new XmlSerializer(typeof(Settings));
|
||||||
|
|
||||||
|
string path = Directory.GetCurrentDirectory() + "\\scripts\\CoopSettings.xml";
|
||||||
|
Settings settings = null;
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
using (FileStream stream = File.OpenRead(path))
|
||||||
|
{
|
||||||
|
settings = (Settings)ser.Deserialize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
{
|
||||||
|
ser.Serialize(stream, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (FileStream stream = File.OpenWrite(path))
|
||||||
|
{
|
||||||
|
ser.Serialize(stream, settings = new Settings());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string path = Directory.GetCurrentDirectory() + "\\scripts\\CoopSettings.xml";
|
||||||
|
|
||||||
|
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
{
|
||||||
|
XmlSerializer ser = new XmlSerializer(typeof(Settings));
|
||||||
|
ser.Serialize(stream, Main.MainSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
GTA.UI.Notification.Show("Error saving player settings: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 GetLastWeaponImpact(Ped ped)
|
||||||
|
{
|
||||||
|
OutputArgument coord = new OutputArgument();
|
||||||
|
if (!Function.Call<bool>(Hash.GET_PED_LAST_WEAPON_IMPACT_COORD, ped.Handle, coord))
|
||||||
|
{
|
||||||
|
return new Vector3();
|
||||||
|
}
|
||||||
|
|
||||||
|
return coord.GetResult<Vector3>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double DegToRad(double deg)
|
||||||
|
{
|
||||||
|
return deg * Math.PI / 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 RotationToDirection(Vector3 rotation)
|
||||||
|
{
|
||||||
|
double z = DegToRad(rotation.Z);
|
||||||
|
double x = DegToRad(rotation.X);
|
||||||
|
double num = Math.Abs(Math.Cos(x));
|
||||||
|
|
||||||
|
return new Vector3
|
||||||
|
{
|
||||||
|
X = (float)(-Math.Sin(z) * num),
|
||||||
|
Y = (float)(Math.Cos(z) * num),
|
||||||
|
Z = (float)Math.Sin(x)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool WorldToScreenRel(Vector3 worldCoords, out Vector2 screenCoords)
|
||||||
|
{
|
||||||
|
OutputArgument num1 = new OutputArgument();
|
||||||
|
OutputArgument num2 = new OutputArgument();
|
||||||
|
|
||||||
|
if (!Function.Call<bool>(Hash.GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y, worldCoords.Z, num1, num2))
|
||||||
|
{
|
||||||
|
screenCoords = new Vector2();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
screenCoords = new Vector2((num1.GetResult<float>() - 0.5f) * 2, (num2.GetResult<float>() - 0.5f) * 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 ScreenRelToWorld(Vector3 camPos, Vector3 camRot, Vector2 coord)
|
||||||
|
{
|
||||||
|
Vector3 camForward = RotationToDirection(camRot);
|
||||||
|
Vector3 rotUp = camRot + new Vector3(10, 0, 0);
|
||||||
|
Vector3 rotDown = camRot + new Vector3(-10, 0, 0);
|
||||||
|
Vector3 rotLeft = camRot + new Vector3(0, 0, -10);
|
||||||
|
Vector3 rotRight = camRot + new Vector3(0, 0, 10);
|
||||||
|
|
||||||
|
Vector3 camRight = RotationToDirection(rotRight) - RotationToDirection(rotLeft);
|
||||||
|
Vector3 camUp = RotationToDirection(rotUp) - RotationToDirection(rotDown);
|
||||||
|
|
||||||
|
double rollRad = -DegToRad(camRot.Y);
|
||||||
|
|
||||||
|
Vector3 camRightRoll = camRight * (float)Math.Cos(rollRad) - camUp * (float)Math.Sin(rollRad);
|
||||||
|
Vector3 camUpRoll = camRight * (float)Math.Sin(rollRad) + camUp * (float)Math.Cos(rollRad);
|
||||||
|
|
||||||
|
Vector3 point3D = camPos + camForward * 10.0f + camRightRoll + camUpRoll;
|
||||||
|
if (!WorldToScreenRel(point3D, out Vector2 point2D))
|
||||||
|
{
|
||||||
|
return camPos + camForward * 10.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 point3DZero = camPos + camForward * 10.0f;
|
||||||
|
if (!WorldToScreenRel(point3DZero, out Vector2 point2DZero))
|
||||||
|
{
|
||||||
|
return camPos + camForward * 10.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double eps = 0.001;
|
||||||
|
if (Math.Abs(point2D.X - point2DZero.X) < eps || Math.Abs(point2D.Y - point2DZero.Y) < eps)
|
||||||
|
{
|
||||||
|
return camPos + camForward * 10.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float scaleX = (coord.X - point2DZero.X) / (point2D.X - point2DZero.X);
|
||||||
|
float scaleY = (coord.Y - point2DZero.Y) / (point2D.Y - point2DZero.Y);
|
||||||
|
|
||||||
|
return camPos + camForward * 10.0f + camRightRoll * scaleX + camUpRoll * scaleY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 RaycastEverything(Vector2 screenCoord)
|
||||||
|
{
|
||||||
|
Vector3 camPos = GameplayCamera.Position;
|
||||||
|
Vector3 camRot = GameplayCamera.Rotation;
|
||||||
|
const float raycastToDist = 100.0f;
|
||||||
|
const float raycastFromDist = 1f;
|
||||||
|
|
||||||
|
Vector3 target3D = ScreenRelToWorld(camPos, camRot, screenCoord);
|
||||||
|
Vector3 source3D = camPos;
|
||||||
|
|
||||||
|
Entity ignoreEntity = Game.Player.Character;
|
||||||
|
if (Game.Player.Character.IsInVehicle())
|
||||||
|
{
|
||||||
|
ignoreEntity = Game.Player.Character.CurrentVehicle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 dir = target3D - source3D;
|
||||||
|
dir.Normalize();
|
||||||
|
RaycastResult raycastResults = World.Raycast(source3D + dir * raycastFromDist,
|
||||||
|
source3D + dir * raycastToDist,
|
||||||
|
(IntersectFlags)(1 | 16 | 256 | 2 | 4 | 8), // | peds + vehicles
|
||||||
|
ignoreEntity);
|
||||||
|
|
||||||
|
if (raycastResults.DidHit)
|
||||||
|
{
|
||||||
|
return raycastResults.HitPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return camPos + dir * raycastToDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Client/WorldThread.cs
Normal file
26
Client/WorldThread.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
using GTA;
|
||||||
|
using GTA.Native;
|
||||||
|
|
||||||
|
namespace CoopClient
|
||||||
|
{
|
||||||
|
public class WorldThread : Script
|
||||||
|
{
|
||||||
|
public WorldThread()
|
||||||
|
{
|
||||||
|
Tick += OnTick;
|
||||||
|
Interval = 1000 / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (Game.IsLoading)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function.Call((Hash)0xB96B00E976BE977F, 0.0f); // _SET_WAVES_INTENSITY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Client/packages.config
Normal file
4
Client/packages.config
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="protobuf-net" version="2.4.6" targetFramework="net48" />
|
||||||
|
</packages>
|
31
GTACoop.sln
Normal file
31
GTACoop.sln
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31423.177
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoopServer", "Server\CoopServer.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoopClient", "Client\CoopClient.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {6CC7EA75-E4FF-4534-8EB6-0AEECF2620B7}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
BIN
Images/LOGO.png
Normal file
BIN
Images/LOGO.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Nick-I. A. (EntenKoeniq)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
BIN
Libs/Release/LemonUI.SHVDN3.dll
Normal file
BIN
Libs/Release/LemonUI.SHVDN3.dll
Normal file
Binary file not shown.
BIN
Libs/Release/Lidgren.Network.dll
Normal file
BIN
Libs/Release/Lidgren.Network.dll
Normal file
Binary file not shown.
BIN
Libs/Release/ScriptHookVDotNet3.dll
Normal file
BIN
Libs/Release/ScriptHookVDotNet3.dll
Normal file
Binary file not shown.
0
MasterServer/blocked.txt
Normal file
0
MasterServer/blocked.txt
Normal file
86
MasterServer/index.mjs
Normal file
86
MasterServer/index.mjs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { createServer } from 'net';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
var serverList = [];
|
||||||
|
const blockedIps = readFileSync('blocked.txt', 'utf-8').split('\n');
|
||||||
|
|
||||||
|
const server = createServer();
|
||||||
|
|
||||||
|
server.on('connection', (socket) =>
|
||||||
|
{
|
||||||
|
if (blockedIps.includes(socket.remoteAddress))
|
||||||
|
{
|
||||||
|
console.log(`IP '${socket.remoteAddress}' blocked`);
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastData = 0;
|
||||||
|
|
||||||
|
const remoteAddress = socket.remoteAddress + ":" + socket.remotePort;
|
||||||
|
|
||||||
|
socket.on('data', async (data) =>
|
||||||
|
{
|
||||||
|
// NOT SPAM!
|
||||||
|
if (lastData !== 0 && (Date.now() - lastData) < 14500)
|
||||||
|
{
|
||||||
|
console.log("[WARNING] Spam from %s", remoteAddress);
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastData = Date.now();
|
||||||
|
|
||||||
|
var incomingMessage;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
incomingMessage = await JSON.parse(data.toString());
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
socket.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingMessage.method)
|
||||||
|
{
|
||||||
|
if (incomingMessage.method === 'POST' && incomingMessage.data)
|
||||||
|
{
|
||||||
|
// Check if the server is already in the serverList
|
||||||
|
const alreadyExist = serverList.some((val) =>
|
||||||
|
{
|
||||||
|
const found = val.remoteAddress === remoteAddress;
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
// Replace old data with new data
|
||||||
|
val.data = { ...val.data, ...incomingMessage.data };
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server doesn't exist in serverList so add the server
|
||||||
|
if (!alreadyExist)
|
||||||
|
{
|
||||||
|
serverList.push({ remoteAddress: remoteAddress, data: incomingMessage.data });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (incomingMessage.method === 'GET')
|
||||||
|
{
|
||||||
|
socket.write(JSON.stringify(serverList));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// method or data does not exist or method is not POST or GET
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('close', () => serverList = serverList.filter(val => val.remoteAddress !== remoteAddress));
|
||||||
|
|
||||||
|
socket.on('error', (e) => { /*console.error(e)*/ });
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(11000, () => console.log("MasterServer started!"));
|
11
MasterServer/package.json
Normal file
11
MasterServer/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "masterserver",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "EntenKoeniq",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
45
README.md
Normal file
45
README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="Images/LOGO.png?raw=true" alt="GTACoop:R Image"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# 🌐 GTACoop:R
|
||||||
|
[![Contributors][contributors-shield]][contributors-url]
|
||||||
|
[![Forks][forks-shield]][forks-url]
|
||||||
|
[![Stargazers][stars-shield]][stars-url]
|
||||||
|
[![Issues][issues-shield]][issues-url]
|
||||||
|
|
||||||
|
| :warning: The original GTACoop can be found [HERE](https://gtacoop.com/) |
|
||||||
|
| --- |
|
||||||
|
|
||||||
|
This modification was completely rewritten and NOT revised
|
||||||
|
|
||||||
|
# 📋 Requirements
|
||||||
|
- Visual Studio 2022
|
||||||
|
- Untested on other development environments
|
||||||
|
- .NET 6.0 & Framework 4.8
|
||||||
|
|
||||||
|
# 📚 Libraries
|
||||||
|
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet/tree/0333095099a20a266c4f17dc52d21c608d1082de)
|
||||||
|
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI/tree/a29f73120fc4f473cdfd14104aaef77f1a1b76e5)
|
||||||
|
- [Lidgren Network](https://github.com/lidgren/lidgren-network-gen3/tree/f99b006d9af8a9a230ba7c5ce0320fc727ebae0c)
|
||||||
|
- [Protobuf-net](https://www.nuget.org/packages/protobuf-net/2.4.6)
|
||||||
|
|
||||||
|
# ♻️ Synchronization
|
||||||
|
- Player & Npc
|
||||||
|
- Model
|
||||||
|
- Props
|
||||||
|
- Health
|
||||||
|
- Movement (Not finished yet)
|
||||||
|
- Weapons (Not finished yet)
|
||||||
|
|
||||||
|
# 📝 License
|
||||||
|
This project is licensed under [MIT license](https://github.com/EntenKoeniq/GTACoop-R/blob/main/LICENSE)
|
||||||
|
|
||||||
|
[contributors-shield]: https://img.shields.io/github/contributors/EntenKoeniq/GTACoop-R.svg?style=for-the-badge
|
||||||
|
[contributors-url]: https://github.com/EntenKoeniq/GTACoop-R/graphs/contributors
|
||||||
|
[forks-shield]: https://img.shields.io/github/forks/EntenKoeniq/GTACoop-R.svg?style=for-the-badge
|
||||||
|
[forks-url]: https://github.com/EntenKoeniq/GTACoop-R/network/members
|
||||||
|
[stars-shield]: https://img.shields.io/github/stars/EntenKoeniq/GTACoop-R.svg?style=for-the-badge
|
||||||
|
[stars-url]: https://github.com/EntenKoeniq/GTACoop-R/stargazers
|
||||||
|
[issues-shield]: https://img.shields.io/github/issues/EntenKoeniq/GTACoop-R.svg?style=for-the-badge
|
||||||
|
[issues-url]: https://github.com/EntenKoeniq/GTACoop-R/issues
|
9
Server/Allowlist.cs
Normal file
9
Server/Allowlist.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
public class Allowlist
|
||||||
|
{
|
||||||
|
public List<string> SocialClubName { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
11
Server/Blocklist.cs
Normal file
11
Server/Blocklist.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
public class Blocklist
|
||||||
|
{
|
||||||
|
public List<string> SocialClubName { get; set; } = new();
|
||||||
|
public List<string> Username { get; set; } = new();
|
||||||
|
public List<string> IP { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
18
Server/CoopServer.csproj
Normal file
18
Server/CoopServer.csproj
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="protobuf-net" Version="2.4.6" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Lidgren.Network">
|
||||||
|
<HintPath>..\Libs\Release\Lidgren.Network.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
12
Server/Entities/EntitiesPed.cs
Normal file
12
Server/Entities/EntitiesPed.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace CoopServer.Entities
|
||||||
|
{
|
||||||
|
struct EntitiesPed
|
||||||
|
{
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
public bool IsInRangeOf(LVector3 position, float distance)
|
||||||
|
{
|
||||||
|
return LVector3.Subtract(Position, position).Length() < distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Server/Entities/EntitiesPlayer.cs
Normal file
9
Server/Entities/EntitiesPlayer.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace CoopServer.Entities
|
||||||
|
{
|
||||||
|
class EntitiesPlayer
|
||||||
|
{
|
||||||
|
public string SocialClubName { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public EntitiesPed Ped = new();
|
||||||
|
}
|
||||||
|
}
|
79
Server/Logging.cs
Normal file
79
Server/Logging.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
class Logging
|
||||||
|
{
|
||||||
|
private static readonly object _lock = new();
|
||||||
|
|
||||||
|
public static void Info(string message)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
string msg = string.Format("[{0}] [INFO] {1}", Date(), message);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
using StreamWriter sw = new("log.txt", true);
|
||||||
|
sw.WriteLine(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warning(string message)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
string msg = string.Format("[{0}] [WARNING] {1}", Date(), message);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
using StreamWriter sw = new("log.txt", true);
|
||||||
|
sw.WriteLine(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(string message)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
string msg = string.Format("[{0}] [ERROR] {1}", Date(), message);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
using StreamWriter sw = new("log.txt", true);
|
||||||
|
sw.WriteLine(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Debug(string message)
|
||||||
|
{
|
||||||
|
if (!Server.MainSettings.DebugMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
string msg = string.Format("[{0}] [DEBUG] {1}", Date(), message);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Blue;
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
using StreamWriter sw = new("log.txt", true);
|
||||||
|
sw.WriteLine(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static string Date()
|
||||||
|
{
|
||||||
|
return DateTime.Now.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
459
Server/Packets.cs
Normal file
459
Server/Packets.cs
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Lidgren.Network;
|
||||||
|
using ProtoBuf;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
[ProtoContract]
|
||||||
|
public struct LVector3
|
||||||
|
{
|
||||||
|
public LVector3(float X, float Y, float Z)
|
||||||
|
{
|
||||||
|
this.X = X;
|
||||||
|
this.Y = Y;
|
||||||
|
this.Z = Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public float X { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public float Y { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public float Z { get; set; }
|
||||||
|
|
||||||
|
#region SERVER-ONLY
|
||||||
|
public float Length() => (float)Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
|
||||||
|
public static LVector3 Subtract(LVector3 pos1, LVector3 pos2) => new(pos1.X - pos2.X, pos1.Y - pos2.Y, pos1.Z - pos2.Z);
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ModVersion
|
||||||
|
{
|
||||||
|
V0_1_0
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PacketTypes
|
||||||
|
{
|
||||||
|
HandshakePacket,
|
||||||
|
PlayerConnectPacket,
|
||||||
|
PlayerDisconnectPacket,
|
||||||
|
FullSyncPlayerPacket,
|
||||||
|
FullSyncNpcPacket,
|
||||||
|
LightSyncPlayerPacket,
|
||||||
|
ChatMessagePacket
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum PedDataFlags
|
||||||
|
{
|
||||||
|
LastSyncWasFull = 1 << 0,
|
||||||
|
IsAiming = 1 << 1,
|
||||||
|
IsShooting = 1 << 2,
|
||||||
|
IsReloading = 1 << 3,
|
||||||
|
IsJumping = 1 << 4,
|
||||||
|
IsRagdoll = 1 << 5,
|
||||||
|
IsOnFire = 1 << 6
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPacket
|
||||||
|
{
|
||||||
|
void PacketToNetOutGoingMessage(NetOutgoingMessage message);
|
||||||
|
void NetIncomingMessageToPacket(NetIncomingMessage message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class Packet : IPacket
|
||||||
|
{
|
||||||
|
public abstract void PacketToNetOutGoingMessage(NetOutgoingMessage message);
|
||||||
|
public abstract void NetIncomingMessageToPacket(NetIncomingMessage message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class HandshakePacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string ID { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string SocialClubName { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public string ModVersion { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public bool NpcsAllowed { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.HandshakePacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
HandshakePacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<HandshakePacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID = data.ID;
|
||||||
|
SocialClubName = data.SocialClubName;
|
||||||
|
Username = data.Username;
|
||||||
|
ModVersion = data.ModVersion;
|
||||||
|
NpcsAllowed = data.NpcsAllowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class PlayerConnectPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string SocialClubName { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.PlayerConnectPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
PlayerConnectPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<PlayerConnectPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
SocialClubName = data.SocialClubName;
|
||||||
|
Username = data.Username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class PlayerDisconnectPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.PlayerDisconnectPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
PlayerDisconnectPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<PlayerDisconnectPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class FullSyncPlayerPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public int ModelHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public Dictionary<int, int> Props { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public int Health { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public LVector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public LVector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
public LVector3 AimCoords { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(10)]
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(11)]
|
||||||
|
public byte? Flag { get; set; } = 0;
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.FullSyncPlayerPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
FullSyncPlayerPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<FullSyncPlayerPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
ModelHash = data.ModelHash;
|
||||||
|
Props = data.Props;
|
||||||
|
Health = data.Health;
|
||||||
|
Position = data.Position;
|
||||||
|
Rotation = data.Rotation;
|
||||||
|
Velocity = data.Velocity;
|
||||||
|
Speed = data.Speed;
|
||||||
|
AimCoords = data.AimCoords;
|
||||||
|
CurrentWeaponHash = data.CurrentWeaponHash;
|
||||||
|
Flag = data.Flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class FullSyncNpcPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string ID { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public int ModelHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public Dictionary<int, int> Props { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public int Health { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public LVector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public LVector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
public LVector3 AimCoords { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(10)]
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(11)]
|
||||||
|
public byte? Flag { get; set; } = 0;
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.FullSyncNpcPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
FullSyncNpcPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<FullSyncNpcPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
ID = data.ID;
|
||||||
|
ModelHash = data.ModelHash;
|
||||||
|
Props = data.Props;
|
||||||
|
Health = data.Health;
|
||||||
|
Position = data.Position;
|
||||||
|
Rotation = data.Rotation;
|
||||||
|
Velocity = data.Velocity;
|
||||||
|
Speed = data.Speed;
|
||||||
|
AimCoords = data.AimCoords;
|
||||||
|
CurrentWeaponHash = data.CurrentWeaponHash;
|
||||||
|
Flag = data.Flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class LightSyncPlayerPacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Player { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public int Health { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public LVector3 Position { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public LVector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public LVector3 Velocity { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public byte Speed { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public LVector3 AimCoords { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
public int CurrentWeaponHash { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
public byte? Flag { get; set; } = 0;
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.LightSyncPlayerPacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
LightSyncPlayerPacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<LightSyncPlayerPacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Player = data.Player;
|
||||||
|
Health = data.Health;
|
||||||
|
Position = data.Position;
|
||||||
|
Rotation = data.Rotation;
|
||||||
|
Velocity = data.Velocity;
|
||||||
|
Speed = data.Speed;
|
||||||
|
AimCoords = data.AimCoords;
|
||||||
|
CurrentWeaponHash = data.CurrentWeaponHash;
|
||||||
|
Flag = data.Flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public class ChatMessagePacket : Packet
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public override void PacketToNetOutGoingMessage(NetOutgoingMessage message)
|
||||||
|
{
|
||||||
|
message.Write((byte)PacketTypes.ChatMessagePacket);
|
||||||
|
|
||||||
|
byte[] result;
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Serializer.Serialize(stream, this);
|
||||||
|
result = stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Write(result.Length);
|
||||||
|
message.Write(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void NetIncomingMessageToPacket(NetIncomingMessage message)
|
||||||
|
{
|
||||||
|
int len = message.ReadInt32();
|
||||||
|
|
||||||
|
ChatMessagePacket data;
|
||||||
|
using (MemoryStream stream = new MemoryStream(message.ReadBytes(len)))
|
||||||
|
{
|
||||||
|
data = Serializer.Deserialize<ChatMessagePacket>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Username = data.Username;
|
||||||
|
Message = data.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Server/Program.cs
Normal file
28
Server/Program.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.Title = "GTACoop:R Server";
|
||||||
|
|
||||||
|
if (File.Exists("log.txt"))
|
||||||
|
{
|
||||||
|
File.WriteAllText("log.txt", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = new Server();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error(e.ToString());
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
500
Server/Server.cs
Normal file
500
Server/Server.cs
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Lidgren.Network;
|
||||||
|
|
||||||
|
using CoopServer.Entities;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
class MasterServer
|
||||||
|
{
|
||||||
|
private Thread MainThread;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
MainThread = new Thread(Listen);
|
||||||
|
MainThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Listen()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IPHostEntry host = Dns.GetHostEntry(Server.MainSettings.MasterServer);
|
||||||
|
IPAddress ipAddress = host.AddressList[0];
|
||||||
|
IPEndPoint remoteEP = new(ipAddress, 11000);
|
||||||
|
|
||||||
|
// Create a TCP/IP socket
|
||||||
|
Socket sender = new(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||||
|
|
||||||
|
sender.Connect(remoteEP);
|
||||||
|
|
||||||
|
Logging.Info("Server connected to MasterServer");
|
||||||
|
|
||||||
|
while (sender.Connected)
|
||||||
|
{
|
||||||
|
// Encode the data string into a byte array
|
||||||
|
byte[] msg = Encoding.ASCII.GetBytes(
|
||||||
|
"{ \"method\": \"POST\", \"data\": { " +
|
||||||
|
"\"Port\": \"" + Server.MainSettings.ServerPort + "\", " +
|
||||||
|
"\"Name\": \"" + Server.MainSettings.ServerName + "\", " +
|
||||||
|
"\"Version\": \"" + Server.CurrentModVersion.Replace("_", ".") + "\", " +
|
||||||
|
"\"Players\": " + Server.MainNetServer.ConnectionsCount + ", " +
|
||||||
|
"\"MaxPlayers\": " + Server.MainSettings.MaxPlayers + ", " +
|
||||||
|
"\"NpcsAllowed\": \"" + Server.MainSettings.NpcsAllowed + "\" } }");
|
||||||
|
|
||||||
|
// Send the data
|
||||||
|
sender.Send(msg);
|
||||||
|
|
||||||
|
// Sleep for 15 seconds
|
||||||
|
Thread.Sleep(15000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException se)
|
||||||
|
{
|
||||||
|
Logging.Error(se.Message);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Error(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Server
|
||||||
|
{
|
||||||
|
public static readonly string CurrentModVersion = Enum.GetValues(typeof(ModVersion)).Cast<ModVersion>().Last().ToString();
|
||||||
|
|
||||||
|
public static readonly Settings MainSettings = Util.Read<Settings>("CoopSettings.xml");
|
||||||
|
private readonly Blocklist MainBlocklist = Util.Read<Blocklist>("Blocklist.xml");
|
||||||
|
private readonly Allowlist MainAllowlist = Util.Read<Allowlist>("Allowlist.xml");
|
||||||
|
|
||||||
|
public static NetServer MainNetServer;
|
||||||
|
|
||||||
|
private readonly MasterServer MainMasterServer = new();
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, EntitiesPlayer> Players = new();
|
||||||
|
|
||||||
|
public Server()
|
||||||
|
{
|
||||||
|
// 6d4ec318f1c43bd62fe13d5a7ab28650 = GTACOOP:R
|
||||||
|
NetPeerConfiguration config = new("6d4ec318f1c43bd62fe13d5a7ab28650")
|
||||||
|
{
|
||||||
|
MaximumConnections = MainSettings.MaxPlayers,
|
||||||
|
Port = MainSettings.ServerPort
|
||||||
|
};
|
||||||
|
|
||||||
|
config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
|
||||||
|
|
||||||
|
MainNetServer = new NetServer(config);
|
||||||
|
MainNetServer.Start();
|
||||||
|
|
||||||
|
Logging.Info(string.Format("Server listening on {0}:{1}", config.LocalAddress.ToString(), config.Port));
|
||||||
|
|
||||||
|
if (MainSettings.AnnounceSelf)
|
||||||
|
{
|
||||||
|
MainMasterServer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Listen()
|
||||||
|
{
|
||||||
|
Logging.Info("Listening for clients");
|
||||||
|
|
||||||
|
while (!Console.KeyAvailable || Console.ReadKey().Key != ConsoleKey.Escape)
|
||||||
|
{
|
||||||
|
// 16 milliseconds to sleep to reduce CPU usage
|
||||||
|
Thread.Sleep(1000 / 60);
|
||||||
|
|
||||||
|
NetIncomingMessage message;
|
||||||
|
|
||||||
|
while ((message = MainNetServer.ReadMessage()) != null)
|
||||||
|
{
|
||||||
|
switch (message.MessageType)
|
||||||
|
{
|
||||||
|
case NetIncomingMessageType.ConnectionApproval:
|
||||||
|
Logging.Info("New incoming connection from: " + message.SenderConnection.RemoteEndPoint.ToString());
|
||||||
|
if (message.ReadByte() != (byte)PacketTypes.HandshakePacket)
|
||||||
|
{
|
||||||
|
message.SenderConnection.Deny("Wrong packet!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Packet approvalPacket;
|
||||||
|
approvalPacket = new HandshakePacket();
|
||||||
|
approvalPacket.NetIncomingMessageToPacket(message);
|
||||||
|
GetHandshake(message.SenderConnection, (HandshakePacket)approvalPacket);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.StatusChanged:
|
||||||
|
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
|
||||||
|
|
||||||
|
string reason = message.ReadString();
|
||||||
|
string player = NetUtility.ToHexString(message.SenderConnection.RemoteUniqueIdentifier);
|
||||||
|
//Logging.Debug(NetUtility.ToHexString(message.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case NetConnectionStatus.Connected:
|
||||||
|
//Logging.Info("New incoming connection from: " + message.SenderConnection.RemoteEndPoint.ToString());
|
||||||
|
break;
|
||||||
|
case NetConnectionStatus.Disconnected:
|
||||||
|
if (Players.ContainsKey(player))
|
||||||
|
{
|
||||||
|
SendPlayerDisconnectPacket(new PlayerDisconnectPacket() { Player = player }, reason);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.Data:
|
||||||
|
// Get packet type
|
||||||
|
byte type = message.ReadByte();
|
||||||
|
|
||||||
|
// Create packet
|
||||||
|
Packet packet;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case (byte)PacketTypes.PlayerConnectPacket:
|
||||||
|
packet = new PlayerConnectPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
SendPlayerConnectPacket(message.SenderConnection, (PlayerConnectPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.PlayerDisconnectPacket:
|
||||||
|
packet = new PlayerDisconnectPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
SendPlayerDisconnectPacket((PlayerDisconnectPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.FullSyncPlayerPacket:
|
||||||
|
packet = new FullSyncPlayerPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
FullSyncPlayer((FullSyncPlayerPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.FullSyncNpcPacket:
|
||||||
|
if (MainSettings.NpcsAllowed)
|
||||||
|
{
|
||||||
|
packet = new FullSyncNpcPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
FullSyncNpc(message.SenderConnection, (FullSyncNpcPacket)packet);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.Warning(Players[NetUtility.ToHexString(message.SenderConnection.RemoteUniqueIdentifier)].Username + " tries to send Npcs!");
|
||||||
|
message.SenderConnection.Disconnect("Npcs are not allowed!");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.LightSyncPlayerPacket:
|
||||||
|
packet = new LightSyncPlayerPacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
LightSyncPlayer((LightSyncPlayerPacket)packet);
|
||||||
|
break;
|
||||||
|
case (byte)PacketTypes.ChatMessagePacket:
|
||||||
|
packet = new ChatMessagePacket();
|
||||||
|
packet.NetIncomingMessageToPacket(message);
|
||||||
|
SendChatMessage((ChatMessagePacket)packet);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logging.Error("Unhandled Data / Packet type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.ErrorMessage:
|
||||||
|
Logging.Error(message.ReadString());
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.WarningMessage:
|
||||||
|
Logging.Warning(message.ReadString());
|
||||||
|
break;
|
||||||
|
case NetIncomingMessageType.DebugMessage:
|
||||||
|
case NetIncomingMessageType.VerboseDebugMessage:
|
||||||
|
Logging.Debug(message.ReadString());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logging.Error(string.Format("Unhandled type: {0} {1} bytes {2} | {3}", message.MessageType, message.LengthBytes, message.DeliveryMethod, message.SequenceChannel));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
MainNetServer.Recycle(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a list of all connections but not the local connection
|
||||||
|
private static List<NetConnection> FilterAllLocal(string local)
|
||||||
|
{
|
||||||
|
return new List<NetConnection>(MainNetServer.Connections.FindAll(e => !NetUtility.ToHexString(e.RemoteUniqueIdentifier).Equals(local)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all players in range of ...
|
||||||
|
private static List<NetConnection> GetAllInRange(LVector3 position, float range, string local = null)
|
||||||
|
{
|
||||||
|
if (local == null)
|
||||||
|
{
|
||||||
|
return new List<NetConnection>(MainNetServer.Connections.FindAll(e => Players[NetUtility.ToHexString(e.RemoteUniqueIdentifier)].Ped.IsInRangeOf(position, range)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new List<NetConnection>(MainNetServer.Connections.FindAll(e =>
|
||||||
|
{
|
||||||
|
string target = NetUtility.ToHexString(e.RemoteUniqueIdentifier);
|
||||||
|
return target != local && Players[target].Ped.IsInRangeOf(position, range);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before we approve the connection, we must shake hands
|
||||||
|
private void GetHandshake(NetConnection local, HandshakePacket packet)
|
||||||
|
{
|
||||||
|
string localPlayerID = NetUtility.ToHexString(local.RemoteUniqueIdentifier);
|
||||||
|
|
||||||
|
Logging.Debug("New handshake from: [" + packet.SocialClubName + " | " + packet.Username + "]");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(packet.Username))
|
||||||
|
{
|
||||||
|
local.Deny("Username is empty or contains spaces!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (packet.Username.Any(p => !char.IsLetterOrDigit(p)))
|
||||||
|
{
|
||||||
|
local.Deny("Username contains special chars!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainSettings.Allowlist)
|
||||||
|
{
|
||||||
|
if (!MainAllowlist.SocialClubName.Contains(packet.SocialClubName))
|
||||||
|
{
|
||||||
|
local.Deny("This Social Club name is not on the allow list!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.ModVersion != CurrentModVersion)
|
||||||
|
{
|
||||||
|
local.Deny("Please update GTACoop:R to " + CurrentModVersion.Replace("_", "."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainBlocklist.SocialClubName.Contains(packet.SocialClubName))
|
||||||
|
{
|
||||||
|
local.Deny("This Social Club name has been blocked by this server!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (MainBlocklist.Username.Contains(packet.Username))
|
||||||
|
{
|
||||||
|
local.Deny("This Username has been blocked by this server!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (MainBlocklist.IP.Contains(local.RemoteEndPoint.ToString().Split(":")[0]))
|
||||||
|
{
|
||||||
|
local.Deny("This IP was blocked by this server!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, EntitiesPlayer> player in Players)
|
||||||
|
{
|
||||||
|
if (player.Value.SocialClubName == packet.SocialClubName)
|
||||||
|
{
|
||||||
|
local.Deny("The name of the Social Club is already taken!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (player.Value.Username == packet.Username)
|
||||||
|
{
|
||||||
|
local.Deny("Username is already taken!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the player to Players
|
||||||
|
Players.Add(localPlayerID,
|
||||||
|
new EntitiesPlayer()
|
||||||
|
{
|
||||||
|
SocialClubName = packet.SocialClubName,
|
||||||
|
Username = packet.Username
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
|
||||||
|
// Create a new handshake packet
|
||||||
|
new HandshakePacket()
|
||||||
|
{
|
||||||
|
ID = localPlayerID,
|
||||||
|
SocialClubName = string.Empty,
|
||||||
|
Username = string.Empty,
|
||||||
|
ModVersion = string.Empty,
|
||||||
|
NpcsAllowed = MainSettings.NpcsAllowed
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
|
||||||
|
// Accept the connection and send back a new handshake packet with the connection ID
|
||||||
|
local.Approve(outgoingMessage);
|
||||||
|
|
||||||
|
Logging.Info("New player [" + packet.SocialClubName + " | " + packet.Username + "] connected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The connection has been approved, now we need to send all other players to the new player and the new player to all players
|
||||||
|
private static void SendPlayerConnectPacket(NetConnection local, PlayerConnectPacket packet)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage))
|
||||||
|
{
|
||||||
|
SendChatMessage(new ChatMessagePacket() { Username = "Server", Message = MainSettings.WelcomeMessage }, new List<NetConnection>() { local });
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NetConnection> playerList = FilterAllLocal(packet.Player);
|
||||||
|
if (playerList.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all players to local
|
||||||
|
playerList.ForEach(targetPlayer =>
|
||||||
|
{
|
||||||
|
string targetPlayerID = NetUtility.ToHexString(targetPlayer.RemoteUniqueIdentifier);
|
||||||
|
|
||||||
|
EntitiesPlayer targetEntity = Players[targetPlayerID];
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
new PlayerConnectPacket()
|
||||||
|
{
|
||||||
|
Player = targetPlayerID,
|
||||||
|
SocialClubName = targetEntity.SocialClubName,
|
||||||
|
Username = targetEntity.Username
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, local, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send local to all players
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
new PlayerConnectPacket()
|
||||||
|
{
|
||||||
|
Player = packet.Player,
|
||||||
|
SocialClubName = Players[packet.Player].SocialClubName,
|
||||||
|
Username = Players[packet.Player].Username
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all players a message that someone has left the server
|
||||||
|
private static void SendPlayerDisconnectPacket(PlayerDisconnectPacket packet, string reason = "Disconnected")
|
||||||
|
{
|
||||||
|
List<NetConnection> playerList = FilterAllLocal(packet.Player);
|
||||||
|
|
||||||
|
if (playerList.Count != 0)
|
||||||
|
{
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
packet.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.Info(Players[packet.Player].Username + " left the server, reason: " + reason);
|
||||||
|
Players.Remove(packet.Player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FullSyncPlayer(FullSyncPlayerPacket packet)
|
||||||
|
{
|
||||||
|
Players[packet.Player].Ped.Position = packet.Position;
|
||||||
|
|
||||||
|
List<NetConnection> playerList = FilterAllLocal(packet.Player);
|
||||||
|
|
||||||
|
if (playerList.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
new FullSyncPlayerPacket()
|
||||||
|
{
|
||||||
|
Player = packet.Player,
|
||||||
|
ModelHash = packet.ModelHash,
|
||||||
|
Props = packet.Props,
|
||||||
|
Health = packet.Health,
|
||||||
|
Position = packet.Position,
|
||||||
|
Rotation = packet.Rotation,
|
||||||
|
Velocity = packet.Velocity,
|
||||||
|
Speed = packet.Speed,
|
||||||
|
AimCoords = packet.AimCoords,
|
||||||
|
CurrentWeaponHash = packet.CurrentWeaponHash,
|
||||||
|
Flag = packet.Flag
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FullSyncNpc(NetConnection local, FullSyncNpcPacket packet)
|
||||||
|
{
|
||||||
|
List<NetConnection> playerList = GetAllInRange(packet.Position, 300f, NetUtility.ToHexString(local.RemoteUniqueIdentifier));
|
||||||
|
|
||||||
|
// No connection found in this area
|
||||||
|
if (playerList.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
new FullSyncNpcPacket()
|
||||||
|
{
|
||||||
|
ID = packet.ID,
|
||||||
|
ModelHash = packet.ModelHash,
|
||||||
|
Props = packet.Props,
|
||||||
|
Health = packet.Health,
|
||||||
|
Position = packet.Position,
|
||||||
|
Rotation = packet.Rotation,
|
||||||
|
Velocity = packet.Velocity,
|
||||||
|
Speed = packet.Speed,
|
||||||
|
AimCoords = packet.AimCoords,
|
||||||
|
CurrentWeaponHash = packet.CurrentWeaponHash,
|
||||||
|
Flag = packet.Flag
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LightSyncPlayer(LightSyncPlayerPacket packet)
|
||||||
|
{
|
||||||
|
Players[packet.Player].Ped.Position = packet.Position;
|
||||||
|
|
||||||
|
List<NetConnection> playerList = FilterAllLocal(packet.Player);
|
||||||
|
|
||||||
|
if (playerList.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
new FullSyncPlayerPacket()
|
||||||
|
{
|
||||||
|
Player = packet.Player,
|
||||||
|
Health = packet.Health,
|
||||||
|
Position = packet.Position,
|
||||||
|
Rotation = packet.Rotation,
|
||||||
|
Velocity = packet.Velocity,
|
||||||
|
Speed = packet.Speed,
|
||||||
|
AimCoords = packet.AimCoords,
|
||||||
|
CurrentWeaponHash = packet.CurrentWeaponHash,
|
||||||
|
Flag = packet.Flag
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message to targets or all players
|
||||||
|
private static void SendChatMessage(ChatMessagePacket packet, List<NetConnection> targets = null)
|
||||||
|
{
|
||||||
|
string filteredMessage = packet.Message.Replace("~", "");
|
||||||
|
|
||||||
|
Logging.Info(packet.Username + ": " + filteredMessage);
|
||||||
|
|
||||||
|
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
|
||||||
|
new ChatMessagePacket()
|
||||||
|
{
|
||||||
|
Username = packet.Username,
|
||||||
|
Message = filteredMessage
|
||||||
|
}.PacketToNetOutGoingMessage(outgoingMessage);
|
||||||
|
MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
Server/Settings.cs
Normal file
15
Server/Settings.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
public class Settings
|
||||||
|
{
|
||||||
|
public int ServerPort { get; set; } = 4499;
|
||||||
|
public int MaxPlayers { get; set; } = 16;
|
||||||
|
public string ServerName { get; set; } = "GTACoop:R server";
|
||||||
|
public string WelcomeMessage { get; set; } = "Welcome on this server :)";
|
||||||
|
public bool Allowlist { get; set; } = false;
|
||||||
|
public bool NpcsAllowed { get; set; } = true;
|
||||||
|
public string MasterServer { get; set; } = "localhost";
|
||||||
|
public bool AnnounceSelf { get; set; } = true;
|
||||||
|
public bool DebugMode { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
36
Server/Util.cs
Normal file
36
Server/Util.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace CoopServer
|
||||||
|
{
|
||||||
|
class Util
|
||||||
|
{
|
||||||
|
public static T Read<T>(string file) where T : new()
|
||||||
|
{
|
||||||
|
XmlSerializer ser = new(typeof(T));
|
||||||
|
|
||||||
|
string path = Directory.GetCurrentDirectory() + "\\" + file;
|
||||||
|
T settings;
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
using (FileStream stream = File.OpenRead(path))
|
||||||
|
{
|
||||||
|
settings = (T)ser.Deserialize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream stream = new(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
{
|
||||||
|
ser.Serialize(stream, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using FileStream stream = File.OpenWrite(path);
|
||||||
|
ser.Serialize(stream, settings = new T());
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user