Initial commit

This commit is contained in:
EntenKoeniq
2021-07-07 13:36:25 +02:00
commit c332b89bf7
39 changed files with 3743 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ['https://spenden.pp-h.eu/68454276-da3c-47c7-b95a-7fa443706a44']

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
**/bin
**/obj
**/packages
.vs/*

143
Client/Chat.cs Normal file
View 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
View 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>

View File

@ -0,0 +1,7 @@
namespace CoopClient.Entities
{
public class EntitiesNpc : EntitiesPed
{
public int LastUpdateReceived { get; set; }
}
}

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

View File

@ -0,0 +1,8 @@
namespace CoopClient.Entities
{
public class EntitiesPlayer : EntitiesPed
{
public string SocialClubName { get; set; }
public string Username { get; set; } = "Player";
}
}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

21
LICENSE Normal file
View 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
MasterServer/blocked.txt Normal file
View File

86
MasterServer/index.mjs Normal file
View 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
View 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
View 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
View 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
View 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
View 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>

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

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