diff --git a/ReallifeGamemode.Client/package-lock.json b/ReallifeGamemode.Client/package-lock.json index d39f08d2..34a97af4 100644 --- a/ReallifeGamemode.Client/package-lock.json +++ b/ReallifeGamemode.Client/package-lock.json @@ -346,7 +346,10 @@ "NativeUI": { "version": "git+https://github.com/sprayzcs/RageMP-NativeUI.git#3f2412469f0be4dfd8f7ff495aac67949704bff6", "from": "git+https://github.com/sprayzcs/RageMP-NativeUI.git", - "dev": true + "dev": true, + "requires": { + "ajv": "^6.8.1" + } }, "acorn": { "version": "6.2.1", @@ -1647,8 +1650,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1669,14 +1671,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1691,20 +1691,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1821,8 +1818,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1834,7 +1830,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1849,7 +1844,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1857,14 +1851,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1883,7 +1875,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1964,8 +1955,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1977,7 +1967,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2063,8 +2052,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2100,7 +2088,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2120,7 +2107,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2164,14 +2150,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/ReallifeGamemode.DataService/ReallifeGamemode.DataService.csproj b/ReallifeGamemode.DataService/ReallifeGamemode.DataService.csproj index b8dbd337..4907947c 100644 --- a/ReallifeGamemode.DataService/ReallifeGamemode.DataService.csproj +++ b/ReallifeGamemode.DataService/ReallifeGamemode.DataService.csproj @@ -7,6 +7,7 @@ true true AnyCPU;x64 + 8.0 @@ -18,21 +19,15 @@ - - + - - - - - - + diff --git a/ReallifeGamemode.DataService/Startup.cs b/ReallifeGamemode.DataService/Startup.cs index efdcea70..ce5d120e 100644 --- a/ReallifeGamemode.DataService/Startup.cs +++ b/ReallifeGamemode.DataService/Startup.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Logging; @@ -26,9 +27,9 @@ namespace ReallifeGamemode.DataService { private readonly ILogger logger; private readonly IConfiguration configuration; - private readonly IHostingEnvironment environment; + private readonly IWebHostEnvironment environment; - public Startup(IConfiguration configuration, IHostingEnvironment environment, ILogger logger) + public Startup(IConfiguration configuration,IWebHostEnvironment environment, ILogger logger) { this.configuration = configuration; this.environment = environment; @@ -122,15 +123,15 @@ namespace ReallifeGamemode.DataService } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) { - app.UseAuthentication(); - if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + app.UseAuthentication(); + app.UseStaticFiles(); app.UseCors(c => @@ -151,7 +152,10 @@ namespace ReallifeGamemode.DataService c.RouteTemplate = "doc/{documentName}.json"; }); - app.UseMvc(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); } } } diff --git a/ReallifeGamemode.Database/Models/DatabaseContext.cs b/ReallifeGamemode.Database/Models/DatabaseContext.cs index a5ad8d4f..d8d1c4ff 100644 --- a/ReallifeGamemode.Database/Models/DatabaseContext.cs +++ b/ReallifeGamemode.Database/Models/DatabaseContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; /** * @overview Life of German Reallife - DatabaseContext.cs @@ -10,13 +11,23 @@ namespace ReallifeGamemode.Database.Models { public partial class DatabaseContext : DbContext { + private readonly ILoggerFactory loggerFactory; + public DatabaseContext(DbContextOptions options) : base(options) { } - public DatabaseContext() { } + public DatabaseContext(ILoggerFactory loggerFactory = null) { + this.loggerFactory = loggerFactory; + } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); + + if(loggerFactory != null) + { + optionsBuilder.UseLoggerFactory(loggerFactory); + } + optionsBuilder.UseMySql("Host=localhost;Port=3306;Database=gtav-devdb;Username=gtav-dev;Password=Test123"); } diff --git a/ReallifeGamemode.Database/ReallifeGamemode.Database.csproj b/ReallifeGamemode.Database/ReallifeGamemode.Database.csproj index ab543c94..4af8ed0d 100644 --- a/ReallifeGamemode.Database/ReallifeGamemode.Database.csproj +++ b/ReallifeGamemode.Database/ReallifeGamemode.Database.csproj @@ -3,6 +3,7 @@ netcoreapp3.1 AnyCPU;x64 + 8.0 diff --git a/ReallifeGamemode.Server.Common/PasswordHasher.cs b/ReallifeGamemode.Server.Common/PasswordHasher.cs new file mode 100644 index 00000000..01bc7a87 --- /dev/null +++ b/ReallifeGamemode.Server.Common/PasswordHasher.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace ReallifeGamemode.Server.Common +{ + public class PasswordHasher + { + public static byte[] GetNewSalt(int length = 256) + { + if ((length % 8) != 0) + { + throw new ArgumentException("Length mus be completely divisble through 8"); + } + + var salt = new byte[length / 8]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + + return salt; + } + + public static string HashPassword(string password, byte[] salt, int length = 512) + { + if ((length % 8) != 0) + { + throw new ArgumentException("Length mus be completely divisble through 8"); + } + + var hashedPassword = KeyDerivation.Pbkdf2(password, salt, KeyDerivationPrf.HMACSHA512, 50000, length / 8); + + return Convert.ToBase64String(hashedPassword); + } + } +} diff --git a/ReallifeGamemode.Server.Common/ReallifeGamemode.Server.Common.csproj b/ReallifeGamemode.Server.Common/ReallifeGamemode.Server.Common.csproj new file mode 100644 index 00000000..e09130e6 --- /dev/null +++ b/ReallifeGamemode.Server.Common/ReallifeGamemode.Server.Common.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.1 + + + + + + + + + + + + + ..\Import\Newtonsoft.Json.dll + + + + diff --git a/ReallifeGamemode.Server.Common/TypeExtensions.cs b/ReallifeGamemode.Server.Common/TypeExtensions.cs new file mode 100644 index 00000000..94ae4bf9 --- /dev/null +++ b/ReallifeGamemode.Server.Common/TypeExtensions.cs @@ -0,0 +1,114 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using ReallifeGamemode.Server.Types; +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +namespace ReallifeGamemode.Server.Common +{ + public static class TypeExtensions + { + private static readonly JsonSerializerSettings settings = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + DateParseHandling = DateParseHandling.DateTime + }; + + static TypeExtensions() + { + settings.Converters.Add(new StringEnumConverter()); + } + + public static string SerializeJson(this object obj) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + return JsonConvert.SerializeObject(obj, settings); + } + + public static T DeserializeJson(this string str) + { + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + + return JsonConvert.DeserializeObject(str, settings); + } + + public static bool IsNullOrEmpty(this string str) + { + if (str == null) + { + return true; + } + + if (str.Trim().Length == 0) + { + return true; + } + + return false; + } + + public static bool IsNotNullOrEmpty(this string str) => !str.IsNullOrEmpty(); + + public static T GetEnumAttribute(this Enum @enum) where T : Attribute + { + var type = @enum?.GetType(); + var memberInfo = type?.GetMember(@enum.ToString()).First(); + return memberInfo?.GetCustomAttribute(); + } + + public static string GetValue(this Enum @enum) + { + return @enum?.GetEnumAttribute()?.Value; + } + + public static string GetPrefix(this ChatPrefix prefix) + { + return prefix.GetValue(); + } + + public static bool TryParseEnum(this string str, Type type, out object @enum) + { + var members = Enum.GetValues(type).Cast(); + var directEnumMember = members.Where(e => e.ToString().ToLower() == str.ToLower()).FirstOrDefault(); + + if (directEnumMember != null) + { + @enum = directEnumMember; + return true; + } + + var memberValueMember = members.Where(e => e.GetValue()?.ToLower() == str.ToLower()).FirstOrDefault(); + + if (memberValueMember != null) + { + @enum = memberValueMember; + return true; + } + + var nativeResult = Enum.TryParse(type, str, true, out var result); + @enum = result ?? (default); + return nativeResult; + } + + public static long ToLong(this object obj) + { + return long.Parse(obj.ToString()); + } + + public static int ToInt(this object obj) + { + return (int)obj.ToLong(); + } + } +} diff --git a/ReallifeGamemode.Server.Core.API/IAPI.cs b/ReallifeGamemode.Server.Core.API/IAPI.cs new file mode 100644 index 00000000..c8528289 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IAPI.cs @@ -0,0 +1,28 @@ +using ReallifeGamemode.Server.Core.API.API; + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace ReallifeGamemode.Server.Core.API +{ + public interface IAPI + { + IColShapeAPI ColShape { get; } + + IVehicleAPI Vehicle { get; } + + void DisableDefaultCommandErrorMessages(); + + void DisableDefaultSpawnBehavior(); + + IPlayer GetPlayerFromNameOrId(string nameOrId); + + void SetGlobalChatEnabled(bool enable); + + void SetTime(int hour, int minute, int second); + + void TriggerClientEventForAll(string eventName, params object[] args); + } +} diff --git a/ReallifeGamemode.Server.Core.API/IColShape.cs b/ReallifeGamemode.Server.Core.API/IColShape.cs new file mode 100644 index 00000000..ba194363 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IColShape.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ReallifeGamemode.Server.Core.API +{ + public interface IColShape : IEntity + { + delegate void ColShapeEvent(IColShape colShape, IEntity entity); + + event ColShapeEvent OnEntityEnter; + event ColShapeEvent OnEntityExit; + } +} diff --git a/ReallifeGamemode.Server.Core.API/IColShapeAPI.cs b/ReallifeGamemode.Server.Core.API/IColShapeAPI.cs new file mode 100644 index 00000000..baa02c22 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IColShapeAPI.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ReallifeGamemode.Server.Core.API.API +{ + public interface IColShapeAPI + { + IColShape CreateCyclinder(Position position, float height, float range); + + IColShape CreateSphere(Position position, float range); + } +} diff --git a/ReallifeGamemode.Server.Core.API/IEntity.cs b/ReallifeGamemode.Server.Core.API/IEntity.cs new file mode 100644 index 00000000..d2831f14 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IEntity.cs @@ -0,0 +1,21 @@ +using System; + +namespace ReallifeGamemode.Server.Core.API +{ + public interface IEntity + { + ulong Handle { get; } + + Position Position { get; set; } + + Position Rotation { get; set; } + + double Heading { get; set; } + + void Remove(); + + void SetSharedData(string key, T data); + + T GetSharedData(string key, T fallback); + } +} diff --git a/ReallifeGamemode.Server.Core.API/IPlayer.cs b/ReallifeGamemode.Server.Core.API/IPlayer.cs new file mode 100644 index 00000000..ca4ae3f8 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IPlayer.cs @@ -0,0 +1,43 @@ +using ReallifeGamemode.Server.Types; +using ReallifeGamemode.Server.Common; +using System.Net; + +namespace ReallifeGamemode.Server.Core.API +{ + public interface IPlayer : IEntity + { + string Name { get; set; } + + string SocialClubName { get; } + + int Health { get; set; } + + int Armor { get; set; } + + IPAddress RemoteAddress { get; } + + IVehicle Vehicle { get; } + + bool IsInVehicle { get; } + + VehicleSeat VehicleSeat { get; } + + void SendMessage(string message, ChatPrefix prefix = ChatPrefix.None) => SendRawMessage(prefix.GetValue() + message); + + void SendRawMessage(string message); + + void TriggerEvent(string eventName, params object[] args) => TriggerEventRaw("SERVER:" + eventName, args); + void CancelAnimation(); + void TriggerEventRaw(string eventName, params object[] args); + + void SetIntoVehicle(IVehicle vehicle, VehicleSeat seat); + + void Kick(); + + void SendNotification(string message, bool flashing = true); + + void Spawn(Position position, float heading = 0.0f); + + void PlayAnimation(string dict, string name, AnimationFlags flags, float speed = 8.0f); + } +} diff --git a/ReallifeGamemode.Server.Core.API/IVehicle.cs b/ReallifeGamemode.Server.Core.API/IVehicle.cs new file mode 100644 index 00000000..ea9f00ac --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IVehicle.cs @@ -0,0 +1,17 @@ + + +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.API +{ + public interface IVehicle : IEntity + { + VehicleModel Model { get; } + + sbyte PrimaryColor { get; set; } + + sbyte SecondaryColor { get; set; } + + void Repair(); + } +} diff --git a/ReallifeGamemode.Server.Core.API/IVehicleAPI.cs b/ReallifeGamemode.Server.Core.API/IVehicleAPI.cs new file mode 100644 index 00000000..12e05293 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/IVehicleAPI.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.API.API +{ + public interface IVehicleAPI + { + IVehicle Spawn(VehicleModel model, Position position, Position rotation, sbyte primaryColor, sbyte secondaryColor); + + IVehicle GetVehicleFromHandle(ulong handle); + } +} diff --git a/ReallifeGamemode.Server.Core.API/Position.cs b/ReallifeGamemode.Server.Core.API/Position.cs new file mode 100644 index 00000000..880372d7 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/Position.cs @@ -0,0 +1,76 @@ +using System; +using System.Globalization; + +namespace ReallifeGamemode.Server.Core.API +{ + public class Position + { + public double X { get; set; } + + public double Y { get; set; } + + public double Z { get; set; } + + public Position() + { + X = 0.0; + Y = 0.0; + Z = 0.0; + } + + public Position(double x, double y, double z) + { + X = x; + Y = y; + Z = z; + } + + public Position(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + public Position(decimal x, decimal y, decimal z) + { + X = (double)x; + Y = (double)y; + Z = (double)z; + } + + public Position(int x, int y, int z) + { + X = x; + Y = y; + Z = z; + } + + public Position Add(Position toAdd) + { + return new Position(X + toAdd.X, Y + toAdd.Y, Z + toAdd.Z); + } + + public Position Subtract(Position toSubtract) + { + return new Position(X - toSubtract.X, Y - toSubtract.Y, Z - toSubtract.Z); + } + + public double DistanceTo(Position position) + { + var x = position.X - X; + var y = position.Y - Y; + var z = position.Z - Z; + + return Math.Sqrt(x * x + y * y + z * z); + } + + public override string ToString() + { + var x = Math.Round(this.X, 2).ToString(CultureInfo.InvariantCulture); + var y = Math.Round(this.Y, 2).ToString(CultureInfo.InvariantCulture); + var z = Math.Round(this.Z, 2).ToString(CultureInfo.InvariantCulture); + + return $"{x}, {y}, {z}"; + } + } +} diff --git a/ReallifeGamemode.Server.Core.API/ReallifeGamemode.Server.Core.API.csproj b/ReallifeGamemode.Server.Core.API/ReallifeGamemode.Server.Core.API.csproj new file mode 100644 index 00000000..d155e553 --- /dev/null +++ b/ReallifeGamemode.Server.Core.API/ReallifeGamemode.Server.Core.API.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + + + + + + + + diff --git a/ReallifeGamemode.Server.Core.Events/EventHandler.cs b/ReallifeGamemode.Server.Core.Events/EventHandler.cs new file mode 100644 index 00000000..693f0fbf --- /dev/null +++ b/ReallifeGamemode.Server.Core.Events/EventHandler.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Extensions.Logging; +using ReallifeGamemode.Server.Common; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Log; +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.Events +{ + public class EventHandler + { + private readonly IAPI api; + + public delegate void PlayerEvent(IPlayer player); + public delegate void VehicleEvent(IVehicle vehicle); + public delegate void PlayerVehicleEvent(IPlayer player, IVehicle vehicle); + public delegate void PlayerDisconnectEvent(IPlayer player, DisconnectReason reason, string reasonString); + + public delegate void ClientEvent(IPlayer player, params object[] args); + private static readonly Dictionary clientEvents = new Dictionary(); + + public static event PlayerEvent OnPlayerJoin; + public static event PlayerDisconnectEvent OnPlayerLeave; + public static event PlayerVehicleEvent OnPlayerExitVehicle; + + private static readonly ILogger logger = LogManager.GetLogger(); + + public EventHandler(IAPI api) + { + this.api = api; + } + + public void RegisterClientEvent(string eventName, ClientEvent clientEvent) + { + if (eventName.IsNullOrEmpty()) + { + logger.LogWarning("'eventName' is null"); + return; + } + + if (clientEvents.ContainsKey(eventName)) + { + logger.LogError("Client event '{EventName}' is already registered", eventName); + return; + } + + clientEvents[eventName] = clientEvent; + } + + public void HandleEvent(IPlayer player, List data) + { + logger.LogDebug("Client event from '{Name}', data: '{DataStr}'", player.Name, data.SerializeJson()); + + var eventName = data.Last() as string; + + if (eventName.IsNullOrEmpty()) + { + logger.LogError("Client event name is null"); + return; + } + + if (!clientEvents.ContainsKey(eventName)) + { + logger.LogError("Client event '{EventName}' is not registered", eventName); + return; + } + + var arguments = data.Take(data.Count - 1).ToArray(); + + clientEvents[eventName](player, arguments); + } + } +} diff --git a/ReallifeGamemode.Server.Core.Events/ReallifeGamemode.Server.Core.Events.csproj b/ReallifeGamemode.Server.Core.Events/ReallifeGamemode.Server.Core.Events.csproj new file mode 100644 index 00000000..13a1a551 --- /dev/null +++ b/ReallifeGamemode.Server.Core.Events/ReallifeGamemode.Server.Core.Events.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + + + + + + + + + + + + + diff --git a/ReallifeGamemode.Server.Core.RageMP/RageAPI.cs b/ReallifeGamemode.Server.Core.RageMP/RageAPI.cs new file mode 100644 index 00000000..780d2974 --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageAPI.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using GTANetworkAPI; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Core.API.API; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + public class RageAPI : IAPI + { + public IColShapeAPI ColShape => new RageColShapeAPI(); + + public IVehicleAPI Vehicle => new RageVehicleAPI(); + + public void DisableDefaultCommandErrorMessages() + { + NAPI.Server.SetCommandErrorMessage(null); + NAPI.Server.SetGlobalDefaultCommandMessages(false); + } + + public void SetGlobalChatEnabled(bool enable) + { + NAPI.Server.SetGlobalServerChat(enable); + } + + public void DisableDefaultSpawnBehavior() + { + NAPI.Server.SetAutoSpawnOnConnect(false); + } + + public IPlayer GetPlayerFromNameOrId(string nameOrId) + { + Player player; + if (int.TryParse(nameOrId, out var playerId)) + { + player = NAPI.Pools.GetAllPlayers().Where(u => u.Handle.Value == playerId).FirstOrDefault(); + } + else + { + player = NAPI.Player.GetPlayerFromName(nameOrId); + } + + if (player == null || player.Handle == null) + { + return null; + } + + return new RagePlayer(player); + } + + public void SetTime(int hour, int minute, int second) + { + NAPI.World.SetTime(hour, minute, second); + } + + public void TriggerClientEventForAll(string eventName, params object[] args) + { + NAPI.ClientEvent.TriggerClientEventForAll("SERVER:" + eventName, args); + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RageColShape.cs b/ReallifeGamemode.Server.Core.RageMP/RageColShape.cs new file mode 100644 index 00000000..affabd4f --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageColShape.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ReallifeGamemode.Server.Core.API; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + public class RageColShape : RageEntity, IColShape + { + private GTANetworkAPI.ColShape colShape; + + public RageColShape(GTANetworkAPI.ColShape colShape) : base(colShape) + { + this.colShape = colShape; + + this.colShape.OnEntityEnterColShape += (c, p) => OnEntityEnter?.Invoke(this, new RagePlayer(p)); + this.colShape.OnEntityExitColShape += (c, p) => OnEntityExit?.Invoke(this, new RagePlayer(p)); + } + + public event IColShape.ColShapeEvent OnEntityEnter; + public event IColShape.ColShapeEvent OnEntityExit; + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RageColShapeAPI.cs b/ReallifeGamemode.Server.Core.RageMP/RageColShapeAPI.cs new file mode 100644 index 00000000..75298ec6 --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageColShapeAPI.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Core.API.API; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + class RageColShapeAPI : IColShapeAPI + { + public IColShape CreateCyclinder(Position position, float height, float range) + { + throw new NotImplementedException(); + } + + public IColShape CreateSphere(Position position, float range) + { + throw new NotImplementedException(); + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RageEntity.cs b/ReallifeGamemode.Server.Core.RageMP/RageEntity.cs new file mode 100644 index 00000000..5881e38a --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageEntity.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ReallifeGamemode.Server.Common; +using ReallifeGamemode.Server.Core.API; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + public class RageEntity : IEntity + { + + private readonly GTANetworkAPI.Entity entity; + + public Position Position + { + get => entity.Position.ToPosition(); + set => entity.Position = value.ToVector3(); + } + public Position Rotation + { + get => entity.Rotation.ToPosition(); + set => entity.Rotation = value.ToVector3(); + } + + public ulong Handle => entity.Handle.Value; + + public double Heading + { + get => Rotation.Z; + set + { + var rotation = Rotation; + rotation.Z = value; + Rotation = rotation; + } + } + + public RageEntity(GTANetworkAPI.Entity rageEntity) + { + entity = rageEntity; + } + + public void Remove() + { + entity.Delete(); + } + + public void SetSharedData(string key, T data) + { + entity.SetSharedData(key, data.SerializeJson()); + } + + public T GetSharedData(string key, T fallback) + { + if (!entity.HasSharedData(key)) + { + return fallback; + } + + return (entity.GetSharedData(key)).DeserializeJson(); + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RageExtensions.cs b/ReallifeGamemode.Server.Core.RageMP/RageExtensions.cs new file mode 100644 index 00000000..6679b4fa --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using GTANetworkAPI; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + public static class RageExtensions + { + public static Vector3 ToVector3(this Position position) + { + return new Vector3(position.X, position.Y, position.Z); + } + + public static Position ToPosition(this Vector3 vector) + { + return new Position(vector.X, vector.Y, vector.Z); + } + + public static DisconnectReason ToDisconnectReason(this DisconnectionType disconnectionType) + { + switch (disconnectionType) + { + case DisconnectionType.Left: + return DisconnectReason.Quit; + + case DisconnectionType.Kicked: + return DisconnectReason.Kick; + + case DisconnectionType.Timeout: + return DisconnectReason.Timeout; + default: + return DisconnectReason.Unknown; + } + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RagePlayer.cs b/ReallifeGamemode.Server.Core.RageMP/RagePlayer.cs new file mode 100644 index 00000000..09565dce --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RagePlayer.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + public class RagePlayer : RageEntity, IPlayer + { + private readonly GTANetworkAPI.Player client; + + public string Name + { + get => client.Name; + set => client.Name = value; + } + + public string SocialClubName => client.SocialClubName; + + public IPAddress RemoteAddress => IPAddress.Parse(client.Address); + + public IVehicle Vehicle => client.IsInVehicle ? new RageVehicle(client.Vehicle) : null; + + public VehicleSeat VehicleSeat => (VehicleSeat)client.VehicleSeat; + + public bool IsInVehicle => Vehicle != null; + + public int Health { get => client.Health; set => client.Health = value; } + public int Armor { get => client.Armor; set => client.Armor = value; } + + public RagePlayer(GTANetworkAPI.Player client) : base(client) + { + this.client = client; + } + + public void SendRawMessage(string message) + { + client.SendChatMessage(message); + } + + public void TriggerEventRaw(string eventName, params object[] args) + { + client.TriggerEvent(eventName, args); + } + + public void SetIntoVehicle(IVehicle vehicle, VehicleSeat seat) + { + client.SetIntoVehicle(new GTANetworkAPI.NetHandle((ushort)vehicle.Handle, GTANetworkAPI.EntityType.Vehicle), (int)seat - 1); + } + + public void Kick() => client.Kick(); + + public void SendNotification(string message, bool flashing = true) + { + client.SendNotification(message, flashing); + } + + public void Spawn(Position position, float heading = 0) + { + Health = 100; + Armor = 0; + + Position = position; + Heading = heading; + } + + public void PlayAnimation(string dict, string name, AnimationFlags flags, float speed = 8.0f) + { + GTANetworkAPI.NAPI.Player.PlayPlayerAnimation(client, (int)flags, dict, name, speed); + } + + public void CancelAnimation() + { + client.StopAnimation(); + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RageVehicle.cs b/ReallifeGamemode.Server.Core.RageMP/RageVehicle.cs new file mode 100644 index 00000000..e9659d8b --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageVehicle.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using GTANetworkAPI; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + class RageVehicle : RageEntity, IVehicle + { + private readonly Vehicle vehicle; + + public RageVehicle(Vehicle vehicle) : base(vehicle) + { + this.vehicle = vehicle; + } + + public VehicleModel Model => (VehicleModel)vehicle.Model; + + public sbyte PrimaryColor { get => (sbyte)vehicle.PrimaryColor; set => vehicle.PrimaryColor = value; } + + public sbyte SecondaryColor { get => (sbyte)vehicle.SecondaryColor; set => vehicle.SecondaryColor = value; } + + public void Repair() + { + vehicle.Repair(); + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/RageVehicleAPI.cs b/ReallifeGamemode.Server.Core.RageMP/RageVehicleAPI.cs new file mode 100644 index 00000000..8aec27df --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/RageVehicleAPI.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Core.API.API; +using ReallifeGamemode.Server.Types; + +namespace ReallifeGamemode.Server.Core.RageMP +{ + class RageVehicleAPI : IVehicleAPI + { + public IVehicle GetVehicleFromHandle(ulong handle) + { + throw new NotImplementedException(); + } + + public IVehicle Spawn(VehicleModel model, Position position, Position rotation, sbyte primaryColor, sbyte secondaryColor) + { + throw new NotImplementedException(); + } + } +} diff --git a/ReallifeGamemode.Server.Core.RageMP/ReallifeGamemode.Server.Core.RageMP.csproj b/ReallifeGamemode.Server.Core.RageMP/ReallifeGamemode.Server.Core.RageMP.csproj new file mode 100644 index 00000000..e8123fa1 --- /dev/null +++ b/ReallifeGamemode.Server.Core.RageMP/ReallifeGamemode.Server.Core.RageMP.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.1 + + + + x64 + + + + + + + + + + ..\Import\Bootstrapper.dll + + + + diff --git a/ReallifeGamemode.Server.Core/Commands/Command.cs b/ReallifeGamemode.Server.Core/Commands/Command.cs new file mode 100644 index 00000000..3e75ed92 --- /dev/null +++ b/ReallifeGamemode.Server.Core/Commands/Command.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Logging; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Log; + +namespace ReallifeGamemode.Server.Core.Commands +{ + public abstract class Command + { + public abstract bool CanExecute(IPlayer player); + + protected abstract IAPI Api { get; } + protected ILogger Log { get; set; } + public abstract string CommandName { get; } + public virtual string HelpText { get; } = null; + + public Command() + { + Log = LogManager.GetLogger(this.GetType()); + } + } +} diff --git a/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs b/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs new file mode 100644 index 00000000..05b992a2 --- /dev/null +++ b/ReallifeGamemode.Server.Core/Commands/CommandHandler.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Types; +using ReallifeGamemode.Server.Common; +using ReallifeGamemode.Server.Log; +using Microsoft.Extensions.Logging; + +namespace ReallifeGamemode.Server.Core.Commands +{ + class CommandHandler + { + private readonly Dictionary registeredCommands = new Dictionary(); + private readonly ILogger logger = LogManager.GetLogger(); + + private readonly IAPI api; + + private Random random = new Random(); + + public CommandHandler(IAPI api) + { + this.api = api; + Main.EventHandler.RegisterClientEvent("Command", OnPlayerCommand); + } + + private void OnPlayerCommand(IPlayer player, object[] args) + { + var command = (args.First() as string)?.ToString()?.ToLower(); + + logger.LogInformation("Player '{Name}' executed command '{command}'", player.Name, command); + + if (!registeredCommands.ContainsKey(command)) + { + player.SendMessage($"Der Befehl ~b~/{command}~s~ existiert nicht", ChatPrefix.Error); + return; + } + + var executor = registeredCommands[command]; + + if (!executor.CanExecute(player)) + { + player.SendMessage($"Du darfst diesen Befehl nicht ausführen", ChatPrefix.Error); + return; + } + + var methodInfo = executor.GetType().GetMethod("Handle"); + + var arguments = new List + { + player + }; + + try + { + arguments.AddRange(ComputeParameters(methodInfo, args.Skip(1).ToArray())); + + var result = methodInfo.Invoke(executor, arguments.ToArray()); + } + catch (ArgumentException ex) + { + player.SendMessage($"Der Parameter ~y~'{ex.Message}'~s~ ist ungültig.", ChatPrefix.Error); + } + catch (SendHelpTextException) + { + player.SendMessage("/" + command + " " + executor.HelpText, ChatPrefix.Usage); + } + catch (TargetInvocationException ex) + { + var errorCode = GetNewErrorCode(); + ex.Data.Add("ErrorCode", errorCode); + logger.LogError(ex, "Error (Code: {ErrorCode}) while executing command '{Command}' from user '{Name}'", errorCode, command, player.Name); + player.SendMessage($"Bei der Ausführung des Befehls gab es einen unbekannten Fehler. ~m~({errorCode})", ChatPrefix.Error); + } + } + + public void RegisterCommand(string command, Command commandExecutor) + { + command = command.ToLower(); + logger.LogDebug("Adding command '{command}'", command); + + if (command.IsNullOrEmpty()) + { + logger.LogError("Command name is null"); + return; + } + + if (commandExecutor == null) + { + logger.LogError("Command executor for command '{command}' is null", command); + return; + } + + var methodInfo = commandExecutor.GetType().GetMethod("Handle"); + + if (methodInfo == null) + { + logger.LogError("The executor for command '{command}' does not have a 'Handle' Method", command); + return; + } + + registeredCommands[command] = commandExecutor; + } + + private object[] ComputeParameters(MethodInfo methodInfo, object[] arguments) + { + var computedParameters = new List(); + + var parameterInfos = methodInfo.GetParameters(); + var parameterCount = parameterInfos.Length - 1; + + var argumentCount = arguments.Length; + + for (var i = 1; i <= parameterCount; i++) + { + var currentParameter = parameterInfos[i]; + + var parameterType = currentParameter.ParameterType; + + object currentArgument; + + if (i < argumentCount + 1) + { + currentArgument = arguments[i - 1]; + } + else if (argumentCount <= (i - 1) && currentParameter.HasDefaultValue) + { + currentArgument = currentParameter.DefaultValue; + computedParameters.Add(currentArgument); + continue; + } + else + { + throw new CommandArgumentCountMismatchException(); + } + + if (parameterType == typeof(IPlayer)) + { + var player = api.GetPlayerFromNameOrId(currentArgument.ToString()); + + if (player == null) + { + throw new ArgumentException($"Der Spieler wurde nicht gefunden."); + } + + computedParameters.Add(player); + + continue; + } + + if (parameterType == typeof(string)) + { + if (i == parameterCount) + { + var str = string.Join(' ', arguments.Skip(i - 1)); + computedParameters.Add(str); + break; + } + else + { + computedParameters.Add(currentArgument); + } + } + + if (parameterType.IsEnum) + { + var stringArg = currentArgument.ToString(); + if (stringArg.TryParseEnum(parameterType, out var result)) + { + computedParameters.Add(result); + } + else + { + throw new ArgumentException(currentArgument.ToString()); + } + } + else if (parameterType.IsPrimitive) + { + try + { + computedParameters.Add(Convert.ChangeType(currentArgument, parameterType)); + } + catch + { + throw new ArgumentException(currentArgument.ToString()); + } + } + } + + return computedParameters.ToArray(); + } + + private string GetNewErrorCode() + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#?$"; + return new string(Enumerable.Repeat(chars, 6) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + } +} diff --git a/ReallifeGamemode.Server.Core/Main.cs b/ReallifeGamemode.Server.Core/Main.cs new file mode 100644 index 00000000..f53c9fb4 --- /dev/null +++ b/ReallifeGamemode.Server.Core/Main.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.Extensions.Logging; +using ReallifeGamemode.Database.Models; +using ReallifeGamemode.Server.Core.API; +using ReallifeGamemode.Server.Core.Commands; +using ReallifeGamemode.Server.Log; + +namespace ReallifeGamemode.Server.Core +{ + public class Main + { + internal static IAPI API { get; private set; } + internal static Events.EventHandler EventHandler { get; private set; } + + private static List