diff --git a/.gitignore b/.gitignore index 08f611d..7a09b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ **/bin **/obj **/packages -.vs/* \ No newline at end of file +**/.vs \ No newline at end of file diff --git a/Client/Entities/EntitiesPed.cs b/Client/Entities/EntitiesPed.cs index 59a8404..1310052 100644 --- a/Client/Entities/EntitiesPed.cs +++ b/Client/Entities/EntitiesPed.cs @@ -143,19 +143,22 @@ namespace CoopClient if (!characterExist) { - CreateCharacter(username); + if (!CreateCharacter(username)) + { + return; + } } else if (LastSyncWasFull) { if (ModelHash != LastModelHash) { - if (characterExist) - { - Character.Kill(); - Character.Delete(); - } + Character.Kill(); + Character.Delete(); - CreateCharacter(username); + if (!CreateCharacter(username)) + { + return; + } } else if (Props != LastProps) { @@ -232,30 +235,45 @@ namespace CoopClient private void DisplayInVehicle() { - if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model.Hash != VehicleModelHash) + try { - List vehs = World.GetNearbyVehicles(Character, 7f, new Model[] { VehicleModelHash }).OrderBy(v => (v.Position - Character.Position).Length()).Take(3).ToList(); - - bool vehFound = false; - - foreach (Vehicle veh in vehs) + if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model.Hash != VehicleModelHash) { - if (veh.IsSeatFree((VehicleSeat)VehicleSeatIndex)) + bool vehFound = false; + + List vehs = World.GetNearbyVehicles(Character, 7f, new Model[] { VehicleModelHash }).OrderBy(v => (v.Position - Character.Position).Length()).Take(3).ToList(); + + foreach (Vehicle veh in vehs) { - MainVehicle = veh; - vehFound = true; - break; + if (veh.IsSeatFree((VehicleSeat)VehicleSeatIndex)) + { + MainVehicle = veh; + vehFound = true; + break; + } + } + + if (!vehFound) + { + Model vehicleModel = Util.ModelRequest(VehicleModelHash); + if (vehicleModel == null) + { + GTA.UI.Notification.Show($"~r~Model ({VehicleModelHash}) cannot be loaded!"); + return; + } + + MainVehicle = World.CreateVehicle(vehicleModel, VehiclePosition); + MainVehicle.Quaternion = VehicleRotation; } } - - if (!vehFound) - { - MainVehicle = World.CreateVehicle(new Model(VehicleModelHash), VehiclePosition); - MainVehicle.Quaternion = VehicleRotation; - } + } + catch (Exception e) + { + GTA.UI.Notification.Show("~r~" + e.Message); + return; } - if (!Character.IsInVehicle() || (int)Character.SeatIndex != VehicleSeatIndex || Character.CurrentVehicle.Model.Hash != VehicleModelHash) + if (!Character.IsInVehicle() || (int)Character.SeatIndex != VehicleSeatIndex || Character.CurrentVehicle?.Model.Hash != VehicleModelHash) { if (VehicleSeatIndex == -1 && Game.Player.Character.IsInVehicle() && @@ -573,12 +591,20 @@ namespace CoopClient } } - private void CreateCharacter(string username) + private bool CreateCharacter(string username) { LastModelHash = ModelHash; LastProps = Props; - Character = World.CreatePed(new Model(ModelHash), Position, Rotation.Z); + Model characterModel = Util.ModelRequest(ModelHash); + + if (characterModel == null) + { + GTA.UI.Notification.Show($"~r~Model ({ModelHash}) cannot be loaded!"); + return false; + } + + Character = World.CreatePed(characterModel, Position, Rotation.Z); Character.RelationshipGroup = Main.RelationshipGroup; if (IsInVehicle) { @@ -606,6 +632,8 @@ namespace CoopClient { Function.Call(Hash.SET_PED_COMPONENT_VARIATION, Character.Handle, prop.Key, prop.Value, 0, 0); } + + return true; } private bool LastMoving; diff --git a/Client/Main.cs b/Client/Main.cs index 189a021..66cfec9 100644 --- a/Client/Main.cs +++ b/Client/Main.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Windows.Forms; using System.Collections.Generic; +using System.Drawing; using CoopClient.Entities; using CoopClient.Menus; @@ -20,16 +21,18 @@ namespace CoopClient public static readonly string CurrentModVersion = "V0_5_0"; public static bool ShareNpcsWithPlayers = false; + public static bool DisableTraffic = false; public static bool NpcsAllowed = false; private static bool IsGoingToCar = false; public static Settings MainSettings = Util.ReadSettings(); - public static MenusMain MainMenu = new MenusMain(); - public static Chat MainChat = new Chat(); - public static Networking MainNetworking = new Networking(); + public static MenusMain MainMenu = new MenusMain(); + + public static Chat MainChat = new Chat(); + public static long LocalPlayerID = 0; public static readonly Dictionary Players = new Dictionary(); public static readonly Dictionary Npcs = new Dictionary(); @@ -73,6 +76,15 @@ namespace CoopClient return; } +#if DEBUG + if (MainNetworking.ShowNetworkInfo) + { + new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0), $"L: {MainNetworking.Latency * 1000:N0}ms", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw(); + new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {MainNetworking.BytesReceived} bytes", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw(); + new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {MainNetworking.BytesSend} bytes", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw(); + } +#endif + MainChat.Tick(); // Display all players @@ -81,10 +93,12 @@ namespace CoopClient player.Value.DisplayLocally(player.Value.Username); } +#if DEBUG if (UseDebug) { Debug(); } +#endif if ((Environment.TickCount - LastDataSend) >= (1000 / 60)) { @@ -156,25 +170,13 @@ namespace CoopClient private void OnAbort(object sender, EventArgs e) { - foreach (KeyValuePair player in Players) - { - player.Value.Character?.AttachedBlip?.Delete(); - player.Value.Character?.CurrentVehicle?.Delete(); - player.Value.Character?.Kill(); - player.Value.Character?.Delete(); - player.Value.PedBlip?.Delete(); - } - - foreach (KeyValuePair Npc in Npcs) - { - Npc.Value.Character?.CurrentVehicle?.Delete(); - Npc.Value.Character?.Kill(); - Npc.Value.Character?.Delete(); - } + CleanUp(); } public static void CleanUp() { + MainChat.Clear(); + foreach (KeyValuePair player in Players) { player.Value.Character?.AttachedBlip?.Delete(); @@ -193,33 +195,15 @@ namespace CoopClient } Npcs.Clear(); - foreach (Ped entity in World.GetAllPeds()) + foreach (Ped entity in World.GetAllPeds().Where(p => p.Handle != Game.Player.Character.Handle)) { - if (entity.Handle != Game.Player.Character.Handle) - { - entity.Kill(); - entity.Delete(); - } + entity.Kill(); + entity.Delete(); } - if (!Game.Player.Character.IsInVehicle()) + foreach (Vehicle veh in World.GetAllVehicles().Where(v => v.Handle != Game.Player.Character.Handle)) { - foreach (Vehicle vehicle in World.GetAllVehicles()) - { - vehicle.Delete(); - } - } - else - { - int? playerVehicleHandle = Game.Player.Character.CurrentVehicle?.Handle; - - foreach (Vehicle vehicle in World.GetAllVehicles()) - { - if (playerVehicleHandle != vehicle.Handle) - { - vehicle.Delete(); - } - } + veh.Delete(); } } diff --git a/Client/Menus/Sub/Settings.cs b/Client/Menus/Sub/Settings.cs index 80f8a3b..6d86c2f 100644 --- a/Client/Menus/Sub/Settings.cs +++ b/Client/Menus/Sub/Settings.cs @@ -9,26 +9,32 @@ namespace CoopClient.Menus.Sub UseMouse = false, Alignment = Main.MainSettings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left }; - + + private readonly NativeCheckboxItem DisableTraffic = new NativeCheckboxItem("Disable Traffic", Main.DisableTraffic); private readonly NativeCheckboxItem ShareNpcsItem = new NativeCheckboxItem("Share Npcs", Main.ShareNpcsWithPlayers) { Enabled = false }; private readonly NativeSliderItem StreamedNpcsItem = new NativeSliderItem(string.Format("Streamed Npcs ({0})", Main.MainSettings.StreamedNpc), 20, Main.MainSettings.StreamedNpc); private readonly NativeCheckboxItem FlipMenuItem = new NativeCheckboxItem("Flip menu", Main.MainSettings.FlipMenu); private readonly NativeCheckboxItem UseDebugItem = new NativeCheckboxItem("Debug", Main.UseDebug); + private readonly NativeCheckboxItem ShowNetworkInfo = new NativeCheckboxItem("Show Network Info", Main.MainNetworking.ShowNetworkInfo); public Settings() { + DisableTraffic.CheckboxChanged += DisableTrafficCheckboxChanged; ShareNpcsItem.CheckboxChanged += (item, check) => { Main.ShareNpcsWithPlayers = ShareNpcsItem.Checked; }; StreamedNpcsItem.ValueChanged += StreamedNpcsValueChanged; FlipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged; #if DEBUG UseDebugItem.CheckboxChanged += UseDebugCheckboxChanged; + ShowNetworkInfo.CheckboxChanged += ShowNetworkInfoCheckboxChanged; #endif + MainMenu.Add(DisableTraffic); MainMenu.Add(ShareNpcsItem); MainMenu.Add(StreamedNpcsItem); MainMenu.Add(FlipMenuItem); #if DEBUG MainMenu.Add(UseDebugItem); + MainMenu.Add(ShowNetworkInfo); #endif } @@ -65,5 +71,35 @@ namespace CoopClient.Menus.Sub Main.Players.Remove(0); } } + + public void ShowNetworkInfoCheckboxChanged(object a, System.EventArgs b) + { + Main.MainNetworking.ShowNetworkInfo = ShowNetworkInfo.Checked; + + if (!Main.MainNetworking.ShowNetworkInfo) + { + Main.MainNetworking.BytesReceived = 0; + Main.MainNetworking.BytesSend = 0; + } + } + + public void DisableTrafficCheckboxChanged(object a, System.EventArgs b) + { + Main.DisableTraffic = DisableTraffic.Checked; + + if (DisableTraffic.Checked) + { + if (ShareNpcsItem.Checked) + { + ShareNpcsItem.Checked = false; + } + + ShareNpcsItem.Enabled = false; + } + else if (Main.NpcsAllowed && !ShareNpcsItem.Enabled) + { + ShareNpcsItem.Enabled = true; + } + } } } diff --git a/Client/Networking.cs b/Client/Networking.cs index 9be1189..adfd7ec 100644 --- a/Client/Networking.cs +++ b/Client/Networking.cs @@ -14,6 +14,11 @@ namespace CoopClient public NetClient Client; public float Latency; + public bool ShowNetworkInfo = false; + + public int BytesReceived = 0; + public int BytesSend = 0; + public void DisConnectFromServer(string address) { if (IsOnServer()) @@ -72,6 +77,8 @@ namespace CoopClient while ((message = Client.ReadMessage()) != null) { + BytesReceived += message.LengthBytes; + switch (message.MessageType) { case NetIncomingMessageType.StatusChanged: @@ -125,7 +132,7 @@ namespace CoopClient Main.MainMenu.MainMenu.Items[2].Enabled = true; Main.MainMenu.MainMenu.Items[2].Title = "Disconnect"; - Main.MainMenu.SubSettings.MainMenu.Items[0].Enabled = Main.NpcsAllowed; + Main.MainMenu.SubSettings.MainMenu.Items[1].Enabled = !Main.DisableTraffic && Main.NpcsAllowed; Main.MainMenu.MainMenu.Visible = false; Main.MainMenu.MenuPool.RefreshAll(); @@ -144,15 +151,13 @@ namespace CoopClient Main.MainChat.Focused = false; } - Main.MainChat.Clear(); - Main.CleanUp(); Main.MainMenu.MainMenu.Items[0].Enabled = true; Main.MainMenu.MainMenu.Items[1].Enabled = true; Main.MainMenu.MainMenu.Items[2].Enabled = true; Main.MainMenu.MainMenu.Items[2].Title = "Connect"; - Main.MainMenu.SubSettings.MainMenu.Items[0].Enabled = false; + Main.MainMenu.SubSettings.MainMenu.Items[1].Enabled = false; Main.MainMenu.MenuPool.RefreshAll(); break; @@ -654,6 +659,13 @@ namespace CoopClient Client.SendMessage(outgoingMessage, messageType); Client.FlushSendQueue(); + +#if DEBUG + if (ShowNetworkInfo) + { + BytesSend += outgoingMessage.LengthBytes; + } +#endif } public void SendNpcData(Ped npc) @@ -710,6 +722,13 @@ namespace CoopClient Client.SendMessage(outgoingMessage, NetDeliveryMethod.Unreliable); Client.FlushSendQueue(); + +#if DEBUG + if (ShowNetworkInfo) + { + BytesSend += outgoingMessage.LengthBytes; + } +#endif } public void SendChatMessage(string message) @@ -722,6 +741,13 @@ namespace CoopClient }.PacketToNetOutGoingMessage(outgoingMessage); Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered); Client.FlushSendQueue(); + +#if DEBUG + if (ShowNetworkInfo) + { + BytesSend += outgoingMessage.LengthBytes; + } +#endif } #endregion } diff --git a/Client/Util.cs b/Client/Util.cs index e029b90..7e6f965 100644 --- a/Client/Util.cs +++ b/Client/Util.cs @@ -79,6 +79,26 @@ namespace CoopClient } #endregion + public static Model ModelRequest(int hash) + { + Model model = new Model(hash); + short counter = 0; + + while (counter++ < 1000) + { + model.Request(); + + Script.Yield(); + + if (model.IsLoaded) + { + return model; + } + } + + return null; + } + public static bool IsBetween(this T item, T start, T end) { return Comparer.Default.Compare(item, start) >= 0 && Comparer.Default.Compare(item, end) <= 0; @@ -438,7 +458,7 @@ namespace CoopClient dir.Normalize(); RaycastResult raycastResults = World.Raycast(source3D + dir * raycastFromDist, source3D + dir * raycastToDist, - (IntersectFlags)(1 | 16 | 256 | 2 | 4 | 8), // | peds + vehicles + IntersectFlags.Everything, ignoreEntity); if (raycastResults.DidHit) diff --git a/Client/WorldThread.cs b/Client/WorldThread.cs index fd561f0..eabbebf 100644 --- a/Client/WorldThread.cs +++ b/Client/WorldThread.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using GTA; using GTA.Native; @@ -7,10 +8,19 @@ namespace CoopClient { public class WorldThread : Script { + private static bool LastDisableTraffic = false; + public WorldThread() { Tick += OnTick; Interval = 1000 / 60; + Aborted += (sender, e) => + { + if (LastDisableTraffic) + { + Traffic(true); + } + }; } public static void OnTick(object sender, EventArgs e) @@ -24,6 +34,75 @@ namespace CoopClient Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, Game.Player.Character.Handle, true, false); Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, Game.Player.Character.Handle, true); + + if (Main.DisableTraffic) + { + if (!LastDisableTraffic) + { + Traffic(false); + } + + Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0); + Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0); + Function.Call(Hash.SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f); + Function.Call(Hash.SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f); + Function.Call(Hash.SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f); + Function.Call((Hash)0x2F9A292AD0A3BD89); + Function.Call((Hash)0x5F3B7749C112D552); + } + else if (LastDisableTraffic) + { + Traffic(true); + } + + LastDisableTraffic = Main.DisableTraffic; + } + + private static void Traffic(bool enable) + { + if (enable) + { + Function.Call(Hash.REMOVE_SCENARIO_BLOCKING_AREAS); + Function.Call(Hash.SET_CREATE_RANDOM_COPS, true); + Function.Call(Hash.SET_RANDOM_TRAINS, true); + Function.Call(Hash.SET_RANDOM_BOATS, true); + Function.Call(Hash.SET_GARBAGE_TRUCKS, true); + Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3); // 0 - 3 + Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3 + Function.Call(Hash.SET_ALL_VEHICLE_GENERATORS_ACTIVE); + Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true); + Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, -1); + Function.Call((Hash)0xF796359A959DF65D, true); // Display distant vehicles + Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, false); + } + else + { + Function.Call(Hash.ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f, 1000.0f, 0, 1, 1, 1); + Function.Call(Hash.SET_CREATE_RANDOM_COPS, false); + Function.Call(Hash.SET_RANDOM_TRAINS, false); + Function.Call(Hash.SET_RANDOM_BOATS, false); + Function.Call(Hash.SET_GARBAGE_TRUCKS, false); + Function.Call(Hash.DELETE_ALL_TRAINS); + Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0); + Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0); + Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false); + Function.Call(Hash.SET_FAR_DRAW_VEHICLES, false); + Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, 0); + Function.Call((Hash)0xF796359A959DF65D, false); //Display distant vehicles + Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, true); + + foreach (Ped ped in World.GetAllPeds().Where(p => p.RelationshipGroup != "SYNCPED")) + { + ped.CurrentVehicle?.Delete(); + ped.Kill(); + ped.Delete(); + } + + foreach (Vehicle veh in World.GetAllVehicles().Where(v => v.IsSeatFree(VehicleSeat.Driver) && v.PassengerCount == 0)) + { + veh.Delete(); + } + } } } } diff --git a/FirstGameMod/FirstGameMode.sln b/FirstGameMod/FirstGameMode.sln new file mode 100644 index 0000000..ce30111 --- /dev/null +++ b/FirstGameMod/FirstGameMode.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31605.320 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FirstGameMode", "FirstGameMode\FirstGameMode.csproj", "{212B1A61-0C03-4B0E-A53C-2CC6B667E0DA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {212B1A61-0C03-4B0E-A53C-2CC6B667E0DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {212B1A61-0C03-4B0E-A53C-2CC6B667E0DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {212B1A61-0C03-4B0E-A53C-2CC6B667E0DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {212B1A61-0C03-4B0E-A53C-2CC6B667E0DA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3C329584-BE48-469B-85D8-FD24F47BD033} + EndGlobalSection +EndGlobal diff --git a/FirstGameMod/FirstGameMode/Commands.cs b/FirstGameMod/FirstGameMode/Commands.cs new file mode 100644 index 0000000..97c28bf --- /dev/null +++ b/FirstGameMod/FirstGameMode/Commands.cs @@ -0,0 +1,46 @@ +using System.Linq; + +using CoopServer; + +namespace FirstGameMode +{ + class Commands + { + [Command("hello")] + public static void HelloCommand(CommandContext ctx) + { + ServerScript.SendChatMessageToPlayer(ctx.Player.Username, "Hello " + ctx.Player.Username + " :)"); + } + + [Command("inrange")] + public static void InRangeCommand(CommandContext ctx) + { + if (ctx.Player.Ped.IsInRangeOf(new LVector3(0f, 0f, 75f), 7f)) + { + ServerScript.SendChatMessageToPlayer(ctx.Player.Username, "You are in range! :)"); + } + else + { + ServerScript.SendChatMessageToPlayer(ctx.Player.Username, "You are not in range! :("); + } + } + + [Command("online")] + public static void OnlineCommand(CommandContext ctx) + { + ServerScript.SendChatMessageToPlayer(ctx.Player.Username, ServerScript.GetAllPlayersCount() + " player online!"); + } + + [Command("kick")] + public static void KickCommand(CommandContext ctx) + { + if (ctx.Args.Length < 2) + { + ServerScript.SendChatMessageToPlayer(ctx.Player.Username, "Please use \"/kick \""); + return; + } + + ServerScript.KickPlayerByUsername(ctx.Args[0], ctx.Args.Skip(1).ToArray()); + } + } +} diff --git a/FirstGameMod/FirstGameMode/FirstGameMode.csproj b/FirstGameMod/FirstGameMode/FirstGameMode.csproj new file mode 100644 index 0000000..bbfaf21 --- /dev/null +++ b/FirstGameMod/FirstGameMode/FirstGameMode.csproj @@ -0,0 +1,13 @@ + + + + net5.0 + + + + + ..\..\Server\bin\Debug\net5.0\CoopServer.dll + + + + diff --git a/FirstGameMod/FirstGameMode/Main.cs b/FirstGameMod/FirstGameMode/Main.cs new file mode 100644 index 0000000..882aa0c --- /dev/null +++ b/FirstGameMod/FirstGameMode/Main.cs @@ -0,0 +1,47 @@ +using CoopServer; +using CoopServer.Entities; +using System.Timers; + +namespace FirstGameMode +{ + public class Main : ServerScript + { + private static readonly Timer RunningSinceTimer = new() { Interval = 1000 }; + private static int RunningSince = 0; + + public override void Start() + { + RunningSinceTimer.Start(); + RunningSinceTimer.Elapsed += new ElapsedEventHandler((sender, e) => RunningSince += 1); + + RegisterCommand("running", RunningCommand); + RegisterCommands(); + } + + public static void RunningCommand(CommandContext ctx) + { + SendChatMessageToPlayer(ctx.Player.Username, "Server has been running for: " + RunningSince + " seconds!"); + } + + public override void OnPlayerConnect(EntitiesPlayer client) + { + SendChatMessageToAll("Player " + client.Username + " connected!"); + } + + public override void OnPlayerDisconnect(EntitiesPlayer player, string reason) + { + SendChatMessageToAll(player.Username + " left the server, reason: " + reason); + } + + public override bool OnChatMessage(string username, string message) + { + if (message.StartsWith("EASTEREGG")) + { + SendChatMessageToPlayer(username, "You found the EASTEREGG! *-*"); + return true; + } + + return false; + } + } +} diff --git a/Server/Server.cs b/Server/Server.cs index d3e5a79..032c563 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -32,6 +32,7 @@ namespace CoopServer public static readonly Dictionary Players = new(); private static ServerScript GameMode; + public static readonly Dictionary> Commands = new Dictionary>(); public Server() { @@ -127,12 +128,7 @@ namespace CoopServer #endregion } - if (MainSettings.DefaultGameMode) - { - GameMode = new DefaultScript(); - GameMode.Start(); - } - else if (!string.IsNullOrEmpty(MainSettings.GameMode)) + if (!string.IsNullOrEmpty(MainSettings.GameMode)) { try { @@ -381,27 +377,6 @@ namespace CoopServer } } - // Return a list of all connections but not the local connection - private static List FilterAllLocal(NetConnection local) - { - return new(MainNetServer.Connections.Where(e => e != local)); - } - private static List FilterAllLocal(long local) - { - return new(MainNetServer.Connections.Where(e => e.RemoteUniqueIdentifier != local)); - } - - // Return a list of players within range of ... - private static List GetAllInRange(LVector3 position, float range) - { - return new(MainNetServer.Connections.FindAll(e => Players[e.RemoteUniqueIdentifier].Ped.IsInRangeOf(position, range))); - } - // Return a list of players within range of ... but not the local one - private static List GetAllInRange(LVector3 position, float range, NetConnection local) - { - return new(MainNetServer.Connections.Where(e => e != local && Players[e.RemoteUniqueIdentifier].Ped.IsInRangeOf(position, range))); - } - #region -- PLAYER -- // Before we approve the connection, we must shake hands private void GetHandshake(NetConnection local, HandshakePacket packet) @@ -504,7 +479,7 @@ namespace CoopServer GameMode.OnPlayerConnect(Players[packet.Player]); } - List playerList = FilterAllLocal(local); + List playerList = Util.FilterAllLocal(local); if (playerList.Count == 0) { return; @@ -546,7 +521,7 @@ namespace CoopServer GameMode.OnPlayerDisconnect(Players[packet.Player], reason); } - List playerList = FilterAllLocal(packet.Player); + List playerList = Util.FilterAllLocal(packet.Player); if (playerList.Count != 0) { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); @@ -563,7 +538,7 @@ namespace CoopServer player.Ped.Position = packet.Extra.Position; - List playerList = FilterAllLocal(packet.Extra.Player); + List playerList = Util.FilterAllLocal(packet.Extra.Player); if (playerList.Count == 0) { return; @@ -585,7 +560,7 @@ namespace CoopServer player.Ped.Position = packet.Extra.Position; - List playerList = FilterAllLocal(packet.Extra.Player); + List playerList = Util.FilterAllLocal(packet.Extra.Player); if (playerList.Count == 0) { return; @@ -607,7 +582,7 @@ namespace CoopServer player.Ped.Position = packet.Extra.Position; - List playerList = FilterAllLocal(packet.Extra.Player); + List playerList = Util.FilterAllLocal(packet.Extra.Player); if (playerList.Count == 0) { return; @@ -629,7 +604,7 @@ namespace CoopServer player.Ped.Position = packet.Extra.Position; - List playerList = FilterAllLocal(packet.Extra.Player); + List playerList = Util.FilterAllLocal(packet.Extra.Player); if (playerList.Count == 0) { return; @@ -648,25 +623,62 @@ namespace CoopServer // Send a message to targets or all players private static void SendChatMessage(ChatMessagePacket packet, List targets = null) { - if (GameMode != null && GameMode.OnChatMessage(packet.Username, packet.Message)) + NetOutgoingMessage outgoingMessage; + + if (GameMode != null) { - return; + if (packet.Message.StartsWith("/")) + { + string[] cmdArgs = packet.Message.Split(" "); + string cmdName = cmdArgs[0].Remove(0, 1); + if (Commands.Any(x => x.Key.Name == cmdName)) + { + CommandContext ctx = new() + { + Player = Players.First(x => x.Value.Username == packet.Username).Value, + Args = cmdArgs.Skip(1).ToArray() + }; + + KeyValuePair> command = Commands.First(x => x.Key.Name == cmdName); + command.Value.Invoke(ctx); + } + else + { + string username = packet.Username; + + packet = new() + { + Username = "Server", + Message = "Command not found!" + }; + + outgoingMessage = MainNetServer.CreateMessage(); + packet.PacketToNetOutGoingMessage(outgoingMessage); + MainNetServer.SendMessage(outgoingMessage, MainNetServer.Connections.Find(con => con.RemoteUniqueIdentifier == Players.First(x => x.Value.Username == username).Key), NetDeliveryMethod.ReliableOrdered, 0); + } + + return; + } + else if (GameMode.OnChatMessage(packet.Username, packet.Message)) + { + return; + } } packet.Message = packet.Message.Replace("~", ""); - Logging.Info(packet.Username + ": " + packet.Message); - - NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); + outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0); + + Logging.Info(packet.Username + ": " + packet.Message); } #endregion #region -- NPC -- private static void FullSyncNpc(NetConnection local, FullSyncNpcPacket packet) { - List playerList = GetAllInRange(packet.Position, 300f, local); + List playerList = Util.GetAllInRange(packet.Position, 300f, local); if (playerList.Count == 0) { return; @@ -679,7 +691,7 @@ namespace CoopServer private static void FullSyncNpcVeh(NetConnection local, FullSyncNpcVehPacket packet) { - List playerList = GetAllInRange(packet.Position, 300f, local); + List playerList = Util.GetAllInRange(packet.Position, 300f, local); if (playerList.Count == 0) { return; @@ -690,5 +702,29 @@ namespace CoopServer MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.UnreliableSequenced, 0); } #endregion + + public static void RegisterCommand(string name, Action callback) + { + Command command = new() { Name = name }; + + if (Commands.ContainsKey(command)) + { + throw new Exception("Command \"" + command.Name + "\" was already been registered!"); + } + + Commands.Add(command, callback); + } + + public static void RegisterCommands() + { + IEnumerable commands = typeof(T).GetMethods().Where(method => method.GetCustomAttributes(typeof(CommandAttribute), false).Any()); + + foreach (MethodInfo method in commands) + { + CommandAttribute attribute = method.GetCustomAttribute(true); + + RegisterCommand(attribute.Name, (Action)Delegate.CreateDelegate(typeof(Action), method)); + } + } } } diff --git a/Server/ServerScript.cs b/Server/ServerScript.cs index 0ed0bde..ab41f48 100644 --- a/Server/ServerScript.cs +++ b/Server/ServerScript.cs @@ -5,100 +5,9 @@ using Lidgren.Network; namespace CoopServer { - class DefaultScript : ServerScript - { - private List Admins = new(); - private string AdminPassword = "test123"; - - public override void OnPlayerConnect(Entities.EntitiesPlayer client) - { - SendChatMessageToAll("Say hello to " + client.Username); - } - - public override void OnPlayerDisconnect(Entities.EntitiesPlayer player, string reason) - { - Logging.Info(player.Username + " left the server, reason: " + reason); - - if (Admins.Contains(player.Username)) - { - Admins.Remove(player.Username); - } - } - - public override bool OnChatMessage(string username, string message) - { - if (!message.StartsWith("/")) - { - return false; - } - - string[] messageSplitted = message.Split(" "); - int messageSplittedLength = messageSplitted.Length; - - if (messageSplittedLength == 0) - { - return true; - } - - if (!Admins.Contains(username)) - { - if (messageSplitted[0] != "/rcon") - { - SendChatMessageToPlayer(username, "Please login with \"/rcon \"!"); - return true; - } - - if (messageSplitted.Length < 2) - { - SendChatMessageToPlayer(username, "Password missing!"); - return true; - } - - if (messageSplitted[1] != AdminPassword) - { - SendChatMessageToPlayer(username, "Wrong password!"); - Logging.Warning("Player [" + username + "] tried to login rcon with [" + messageSplitted[1] + "]"); - return true; - } - - Admins.Add(username); - - SendChatMessageToPlayer(username, "Login successfully!"); - Logging.Info("Login successfully! [RCON][" + username + "]"); - return true; - } - - if (messageSplitted[0] == "/kick") - { - if (messageSplittedLength < 3) - { - SendChatMessageToPlayer(username, "Please use \"/kick \""); - return true; - } - - try - { - KickPlayerByUsername(messageSplitted[1], messageSplittedLength >= 3 ? messageSplitted[2] : "Kicked by " + username + "!"); - SendChatMessageToPlayer(username, "Player [" + messageSplitted[1] + "] kicked!"); - } - catch (Exception e) - { - SendChatMessageToPlayer(username, e.Message); - } - return true; - } - - SendChatMessageToPlayer(username, "Command \"" + messageSplitted[0] + "\" not found!"); - return true; - } - } - public class ServerScript { - public virtual void Start() - { - Logging.Info("Gamemode loaded successfully!"); - } + public virtual void Start() { } public virtual void OnPlayerConnect(Entities.EntitiesPlayer player) { @@ -110,78 +19,139 @@ namespace CoopServer Logging.Info(player.Username + " left the server, reason: " + reason); } - public virtual bool OnChatMessage(string username, string message) { return false; } - - protected static List GetAllConnections() + public virtual bool OnChatMessage(string username, string message) { - List result = new(); + return false; + } + + public static List GetAllConnections() + { + List result = new(); lock (Server.MainNetServer.Connections) { - Server.MainNetServer.Connections.ForEach(con => result.Add(NetUtility.ToHexString(con.RemoteUniqueIdentifier))); + Server.MainNetServer.Connections.ForEach(x => result.Add(x.RemoteUniqueIdentifier)); } return result; } - protected static int GetAllPlayersCount() { lock (Server.Players) return Server.Players.Count; } - protected static Dictionary GetAllPlayers() { lock (Server.Players) return Server.Players; } - - protected static void KickPlayerByUsername(string username, string reason) + public static int GetAllPlayersCount() { lock (Server.Players) { - foreach (KeyValuePair player in Server.Players) - { - if (player.Value.Username == username) - { - Server.MainNetServer.Connections.Find(e => e.RemoteUniqueIdentifier == player.Key).Disconnect(reason); - return; - } - } + return Server.Players.Count; } - - throw new Exception("Player [" + username + "] not found!"); } - protected static void SendChatMessageToAll(string message, string username = "Server") - { - ChatMessagePacket packet = new() - { - Username = username, - Message = message - }; - - Logging.Info(username + ": " + packet.Message); - - NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); - packet.PacketToNetOutGoingMessage(outgoingMessage); - Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0); - } - - protected static void SendChatMessageToPlayer(string username, string message, string from = "Server") + public static Dictionary GetAllPlayers() { lock (Server.Players) { - foreach (KeyValuePair player in Server.Players) - { - if (player.Value.Username == username) - { - ChatMessagePacket packet = new() - { - Username = from, - Message = message - }; - - Logging.Info(from + ": " + packet.Message); - - NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); - packet.PacketToNetOutGoingMessage(outgoingMessage); - Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections.Find(con => con.RemoteUniqueIdentifier == player.Key), NetDeliveryMethod.ReliableOrdered, 0); - return; - } - } + return Server.Players; } } + + public static void KickPlayerByUsername(string username, string[] reason) + { + lock (Server.MainNetServer.Connections) + { + NetConnection userConnection = Util.GetConnectionByUsername(username); + if (userConnection == null) + { + Logging.Warning("[ServerScript->KickPlayerByUsername(\"" + username + "\", \"" + string.Join(" ", reason) + "\")]: User not found!"); + return; + } + + userConnection.Disconnect(string.Join(" ", reason)); + } + } + + public static void SendChatMessageToAll(string message, string username = "Server") + { + List connections = Server.MainNetServer.Connections; + + if (connections.Count != 0) + { + ChatMessagePacket packet = new() + { + Username = username, + Message = message + }; + + NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); + packet.PacketToNetOutGoingMessage(outgoingMessage); + Server.MainNetServer.SendMessage(outgoingMessage, Server.MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0); + } + + Logging.Info(username + ": " + message); + } + + public static void SendChatMessageToPlayer(string username, string message, string from = "Server") + { + lock (Server.MainNetServer.Connections) + { + NetConnection userConnection = Util.GetConnectionByUsername(username); + if (userConnection == null) + { + Logging.Warning("[ServerScript->SendChatMessageToPlayer(\"" + username + "\", \"" + message + "\", \"" + from + "\")]: User not found!"); + return; + } + + ChatMessagePacket packet = new() + { + Username = from, + Message = message + }; + + NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage(); + packet.PacketToNetOutGoingMessage(outgoingMessage); + Server.MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0); + } + + Logging.Info(from + ": " + message); + } + + public static void RegisterCommand(string name, Action callback) + { + Server.RegisterCommand(name, callback); + } + + public static void RegisterCommands() + { + Server.RegisterCommands(); + } + } + + public class Command + { + public string Name { get; set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public class CommandAttribute : Attribute + { + /// + /// Sets name of the command + /// + public string Name { get; set; } + + public CommandAttribute(string name) + { + Name = name; + } + } + + public class CommandContext + { + /// + /// Gets the client which executed the command + /// + public Entities.EntitiesPlayer Player { get; internal set; } + + /// + /// Gets the chatdata associated with the command + /// + public string[] Args { get; internal set; } } } diff --git a/Server/Settings.cs b/Server/Settings.cs index 5f44833..f4021eb 100644 --- a/Server/Settings.cs +++ b/Server/Settings.cs @@ -6,7 +6,6 @@ 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 DefaultGameMode { get; set; } = true; public string GameMode { get; set; } = ""; public bool Allowlist { get; set; } = false; public bool NpcsAllowed { get; set; } = true; diff --git a/Server/Util.cs b/Server/Util.cs index 2997511..e6996ee 100644 --- a/Server/Util.cs +++ b/Server/Util.cs @@ -1,11 +1,52 @@ using System; using System.IO; using System.Xml.Serialization; +using System.Linq; +using System.Collections.Generic; + +using Lidgren.Network; namespace CoopServer { class Util { + public static NetConnection GetConnectionByUsername(string username) + { + long? userID = GetIdByUsername(username); + if (userID == null || !Server.MainNetServer.Connections.Any(x => x.RemoteUniqueIdentifier == userID)) + { + return null; + } + + return Server.MainNetServer.Connections.First(x => x.RemoteUniqueIdentifier == userID); + } + + public static long? GetIdByUsername(string username) + { + return Server.Players.Any(x => x.Value.Username == username) ? Server.Players.First(x => x.Value.Username == username).Key : null; + } + + // Return a list of all connections but not the local connection + public static List FilterAllLocal(NetConnection local) + { + return new(Server.MainNetServer.Connections.Where(e => e != local)); + } + public static List FilterAllLocal(long local) + { + return new(Server.MainNetServer.Connections.Where(e => e.RemoteUniqueIdentifier != local)); + } + + // Return a list of players within range of ... + public static List GetAllInRange(LVector3 position, float range) + { + return new(Server.MainNetServer.Connections.FindAll(e => Server.Players[e.RemoteUniqueIdentifier].Ped.IsInRangeOf(position, range))); + } + // Return a list of players within range of ... but not the local one + public static List GetAllInRange(LVector3 position, float range, NetConnection local) + { + return new(Server.MainNetServer.Connections.Where(e => e != local && Server.Players[e.RemoteUniqueIdentifier].Ped.IsInRangeOf(position, range))); + } + public static T Read(string file) where T : new() { XmlSerializer ser = new(typeof(T));