From 8ce671940104731ae0d98e57434a7358f2991815 Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:19:45 +0100 Subject: [PATCH 01/10] Start working on separating traffic into plugin --- AssettoServer.sln | 6 + .../Commands/Modules/AiTrafficModule.cs | 38 -- .../Commands/Modules/GeneralModule.cs | 13 - .../Network/CSPClientMessageHandler.cs | 7 - AssettoServer/Network/Tcp/ACTcpClient.cs | 2 +- AssettoServer/Server/ACServer.cs | 13 +- AssettoServer/Server/Ai/AiModule.cs | 40 -- .../ACServerConfigurationValidator.cs | 25 -- .../Extra/ACExtraConfiguration.cs | 62 --- AssettoServer/Server/EntryCarManager.cs | 2 +- AssettoServer/Server/Lua/assettoserver.lua | 11 - .../Server/OpenSlotFilters/AiSlotFilter.cs | 26 -- AssettoServer/Startup.cs | 2 - .../Ai => TrafficAiPlugin}/AiBehavior.cs | 60 +-- TrafficAiPlugin/AiSlotFilter.cs | 29 ++ .../Server/Ai => TrafficAiPlugin}/AiState.cs | 57 +-- .../Ai => TrafficAiPlugin}/AiUpdater.cs | 4 +- .../Configuration}/CarSpecificOverrides.cs | 3 +- .../Configuration/Indicator.cs | 2 +- .../Configuration/JunctionRecord.cs | 2 +- .../LaneCountSpecificOverrides.cs | 2 +- .../Configuration}/LaneSpawnBehavior.cs | 2 +- .../Configuration}/Sphere.cs | 2 +- .../Configuration/SplineConfiguration.cs | 5 +- .../Configuration/TrafficAIConfiguration.cs | 8 +- .../TrafficAiConfigurationValidator.cs | 34 ++ .../Configuration/TrafficConfiguration.cs | 5 +- .../DynamicTrafficDensity.cs | 32 +- TrafficAiPlugin/EntryCarTrafficAi.cs | 358 ++++++++++++++++++ .../Splines/AdjacentLaneDetector.cs | 5 +- .../Splines/AiSpline.cs | 6 +- .../Splines/AiSplineHeader.cs | 2 +- .../Splines/AiSplineLocator.cs | 7 +- .../Splines/AiSplineWriter.cs | 5 +- .../Splines/FastLane.cs | 4 +- .../Splines/FastLaneParser.cs | 10 +- .../Splines/JunctionEvaluator.cs | 5 +- .../Splines/MutableAiSpline.cs | 8 +- .../Splines/SlowestAiStates.cs | 4 +- .../Splines/SplineJunction.cs | 2 +- .../Splines/SplinePoint.cs | 2 +- .../Splines/SplinePointOperations.cs | 6 +- TrafficAiPlugin/TrafficAi.cs | 58 +++ TrafficAiPlugin/TrafficAiCommandModule.cs | 50 +++ TrafficAiPlugin/TrafficAiModule.cs | 92 +++++ TrafficAiPlugin/TrafficAiPlugin.csproj | 31 ++ .../Ai => TrafficAiPlugin}/ai_debug.lua | 0 TrafficAiPlugin/lua/resetcar.lua | 10 + 48 files changed, 790 insertions(+), 369 deletions(-) delete mode 100644 AssettoServer/Commands/Modules/AiTrafficModule.cs delete mode 100644 AssettoServer/Server/Ai/AiModule.cs delete mode 100644 AssettoServer/Server/OpenSlotFilters/AiSlotFilter.cs rename {AssettoServer/Server/Ai => TrafficAiPlugin}/AiBehavior.cs (89%) create mode 100644 TrafficAiPlugin/AiSlotFilter.cs rename {AssettoServer/Server/Ai => TrafficAiPlugin}/AiState.cs (93%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/AiUpdater.cs (90%) rename {AssettoServer/Server/Configuration/Extra => TrafficAiPlugin/Configuration}/CarSpecificOverrides.cs (97%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Configuration/Indicator.cs (52%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Configuration/JunctionRecord.cs (92%) rename {AssettoServer/Server/Configuration/Extra => TrafficAiPlugin/Configuration}/LaneCountSpecificOverrides.cs (92%) rename {AssettoServer/Server/Configuration/Extra => TrafficAiPlugin/Configuration}/LaneSpawnBehavior.cs (56%) rename {AssettoServer/Server/Configuration/Extra => TrafficAiPlugin/Configuration}/Sphere.cs (82%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Configuration/SplineConfiguration.cs (80%) rename AssettoServer/Server/Configuration/Extra/AiParams.cs => TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs (96%) create mode 100644 TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Configuration/TrafficConfiguration.cs (72%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/DynamicTrafficDensity.cs (55%) create mode 100644 TrafficAiPlugin/EntryCarTrafficAi.cs rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/AdjacentLaneDetector.cs (97%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/AiSpline.cs (97%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/AiSplineHeader.cs (75%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/AiSplineLocator.cs (96%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/AiSplineWriter.cs (93%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/FastLane.cs (70%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/FastLaneParser.cs (97%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/JunctionEvaluator.cs (96%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/MutableAiSpline.cs (96%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/SlowestAiStates.cs (96%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/SplineJunction.cs (91%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/SplinePoint.cs (91%) rename {AssettoServer/Server/Ai => TrafficAiPlugin}/Splines/SplinePointOperations.cs (95%) create mode 100644 TrafficAiPlugin/TrafficAi.cs create mode 100644 TrafficAiPlugin/TrafficAiCommandModule.cs create mode 100644 TrafficAiPlugin/TrafficAiModule.cs create mode 100644 TrafficAiPlugin/TrafficAiPlugin.csproj rename {AssettoServer/Server/Ai => TrafficAiPlugin}/ai_debug.lua (100%) create mode 100644 TrafficAiPlugin/lua/resetcar.lua diff --git a/AssettoServer.sln b/AssettoServer.sln index 9cdf2757..b0e91006 100644 --- a/AssettoServer.sln +++ b/AssettoServer.sln @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomCommandPlugin", "Cust EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReplayPlugin", "ReplayPlugin\ReplayPlugin.csproj", "{85A4319F-EB8C-4CC3-B107-6C319E537EB8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafficAiPlugin", "TrafficAiPlugin\TrafficAiPlugin.csproj", "{F20A785C-686A-40A4-8821-28627BA6FA48}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -117,6 +119,10 @@ Global {85A4319F-EB8C-4CC3-B107-6C319E537EB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {85A4319F-EB8C-4CC3-B107-6C319E537EB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {85A4319F-EB8C-4CC3-B107-6C319E537EB8}.Release|Any CPU.Build.0 = Release|Any CPU + {F20A785C-686A-40A4-8821-28627BA6FA48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F20A785C-686A-40A4-8821-28627BA6FA48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F20A785C-686A-40A4-8821-28627BA6FA48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F20A785C-686A-40A4-8821-28627BA6FA48}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AssettoServer/Commands/Modules/AiTrafficModule.cs b/AssettoServer/Commands/Modules/AiTrafficModule.cs deleted file mode 100644 index dbf38996..00000000 --- a/AssettoServer/Commands/Modules/AiTrafficModule.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using AssettoServer.Commands.Attributes; -using AssettoServer.Server; -using AssettoServer.Server.Configuration; -using JetBrains.Annotations; -using Qmmands; - -namespace AssettoServer.Commands.Modules; - -[RequireAdmin] -[UsedImplicitly(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.WithMembers)] -public class AiTrafficModule : ACModuleBase -{ - private readonly ACServerConfiguration _configuration; - private readonly EntryCarManager _entryCarManager; - - public AiTrafficModule(ACServerConfiguration configuration, EntryCarManager entryCarManager) - { - _configuration = configuration; - _entryCarManager = entryCarManager; - } - - [Command("setaioverbooking")] - public void SetAiOverbooking(int count) - { - if (!_configuration.Extra.EnableAi) - { - Reply("AI disabled"); - return; - } - - foreach (var aiCar in _entryCarManager.EntryCars.Where(car => car.AiControlled && car.Client == null)) - { - aiCar.SetAiOverbooking(count); - } - Reply($"AI overbooking set to {count}"); - } -} diff --git a/AssettoServer/Commands/Modules/GeneralModule.cs b/AssettoServer/Commands/Modules/GeneralModule.cs index e0e6b117..8967619a 100644 --- a/AssettoServer/Commands/Modules/GeneralModule.cs +++ b/AssettoServer/Commands/Modules/GeneralModule.cs @@ -67,17 +67,4 @@ public async Task ShowLegalNotice() Reply(line); } } - - [Command("resetcar"), RequireConnectedPlayer] - public void ResetCarAsync() - { - if (_configuration.Extra is { EnableClientMessages: true, EnableCarReset: true, MinimumCSPVersion: >= CSPVersion.V0_2_3_p47, EnableAi: true }) - { - Reply(Client!.EntryCar.TryResetPosition() - ? "Position successfully reset" - : "Couldn't reset position"); - } - else - Reply("Reset is not enabled on this server"); - } } diff --git a/AssettoServer/Network/CSPClientMessageHandler.cs b/AssettoServer/Network/CSPClientMessageHandler.cs index c46583a7..8eff3aa6 100644 --- a/AssettoServer/Network/CSPClientMessageHandler.cs +++ b/AssettoServer/Network/CSPClientMessageHandler.cs @@ -26,7 +26,6 @@ public CSPClientMessageHandler(CSPClientMessageTypeManager cspClientMessageTypeM cspClientMessageTypeManager.RegisterOnlineEvent(OnCollisionUpdate); cspClientMessageTypeManager.RegisterOnlineEvent(OnTeleportCar); - cspClientMessageTypeManager.RegisterOnlineEvent((client, _) => { OnResetCar(client); }); } public void OnCSPClientMessageUdp(ACTcpClient sender, PacketReader reader) @@ -200,12 +199,6 @@ private void OnAdminPenaltyOut(ACTcpClient sender, PacketReader reader) } } - private void OnResetCar(ACTcpClient sender) - { - if (_configuration.Extra.EnableCarReset) - sender.EntryCar.TryResetPosition(); - } - private void OnTeleportCar(ACTcpClient sender, TeleportCarPacket packet) { if (!sender.IsAdministrator) return; diff --git a/AssettoServer/Network/Tcp/ACTcpClient.cs b/AssettoServer/Network/Tcp/ACTcpClient.cs index 15a0db28..7cf21ca6 100644 --- a/AssettoServer/Network/Tcp/ACTcpClient.cs +++ b/AssettoServer/Network/Tcp/ACTcpClient.cs @@ -259,7 +259,7 @@ public void SendPacket(TPacket packet) where TPacket : IOutgoingNetwork } } - internal void SendPacketUdp(in TPacket packet) where TPacket : IOutgoingNetworkPacket + public void SendPacketUdp(in TPacket packet) where TPacket : IOutgoingNetworkPacket { if (UdpEndpoint == null) return; diff --git a/AssettoServer/Server/ACServer.cs b/AssettoServer/Server/ACServer.cs index 52ce50ca..dcc4ab14 100644 --- a/AssettoServer/Server/ACServer.cs +++ b/AssettoServer/Server/ACServer.cs @@ -7,7 +7,6 @@ using AssettoServer.Network.Tcp; using AssettoServer.Server.Configuration; using AssettoServer.Network.Udp; -using AssettoServer.Server.Ai.Splines; using AssettoServer.Server.Blacklist; using AssettoServer.Server.CMContentProviders; using AssettoServer.Server.GeoParams; @@ -58,8 +57,7 @@ public ACServer( CSPServerScriptProvider cspServerScriptProvider, IEnumerable autostartServices, KunosLobbyRegistration kunosLobbyRegistration, - IHostApplicationLifetime applicationLifetime, - AiSpline? aiSpline = null) : base(applicationLifetime) + IHostApplicationLifetime applicationLifetime) : base(applicationLifetime) { Log.Information("Starting server"); @@ -106,15 +104,6 @@ public ACServer( using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("AssettoServer.Server.Lua.assettoserver.lua")!; cspServerScriptProvider.AddScript(stream, "assettoserver.lua"); - - if (_configuration.Extra.EnableCarReset) - { - if (!_configuration.Extra.EnableClientMessages || _configuration.CSPTrackOptions.MinimumCSPVersion < CSPVersion.V0_2_3_p47 || aiSpline == null) - { - throw new ConfigurationException( - "Reset car: Minimum required CSP version of 0.2.3-preview47 (2796); Requires enabled client messages; Requires working AI spline"); - } - } } private void OnApplicationStopping() diff --git a/AssettoServer/Server/Ai/AiModule.cs b/AssettoServer/Server/Ai/AiModule.cs deleted file mode 100644 index 90eb9599..00000000 --- a/AssettoServer/Server/Ai/AiModule.cs +++ /dev/null @@ -1,40 +0,0 @@ -using AssettoServer.Server.Ai.Splines; -using AssettoServer.Server.Configuration; -using AssettoServer.Server.OpenSlotFilters; -using AssettoServer.Server.Plugin; -using Autofac; -using Microsoft.Extensions.Hosting; - -namespace AssettoServer.Server.Ai; - -public class AiModule : Module -{ - private readonly ACServerConfiguration _configuration; - - public AiModule(ACServerConfiguration configuration) - { - _configuration = configuration; - } - - protected override void Load(ContainerBuilder builder) - { - builder.RegisterType().AsSelf(); - - if (_configuration.Extra.EnableAi) - { - builder.RegisterType().AsSelf().As().SingleInstance(); - builder.RegisterType().AsSelf().SingleInstance().AutoActivate(); - builder.RegisterType().As(); - - if (_configuration.Extra.AiParams.HourlyTrafficDensity != null) - { - builder.RegisterType().As().SingleInstance(); - } - - builder.RegisterType().AsSelf(); - builder.RegisterType().AsSelf(); - builder.RegisterType().AsSelf(); - builder.Register((AiSplineLocator locator) => locator.Locate()).AsSelf().SingleInstance(); - } - } -} diff --git a/AssettoServer/Server/Configuration/ACServerConfigurationValidator.cs b/AssettoServer/Server/Configuration/ACServerConfigurationValidator.cs index 968af5dd..f276a170 100644 --- a/AssettoServer/Server/Configuration/ACServerConfigurationValidator.cs +++ b/AssettoServer/Server/Configuration/ACServerConfigurationValidator.cs @@ -21,31 +21,6 @@ public ACServerConfigurationValidator() extra.RuleFor(x => x.WhitelistUserGroup).NotEmpty(); extra.RuleFor(x => x.AdminUserGroup).NotEmpty(); extra.RuleFor(x => x.VoteKickMinimumConnectedPlayers).GreaterThanOrEqualTo((ushort)3); - - extra.RuleFor(x => x.AiParams).ChildRules(aiParams => - { - aiParams.RuleFor(ai => ai.MinSpawnDistancePoints).LessThanOrEqualTo(ai => ai.MaxSpawnDistancePoints); - aiParams.RuleFor(ai => ai.MinAiSafetyDistanceMeters).LessThanOrEqualTo(ai => ai.MaxAiSafetyDistanceMeters); - aiParams.RuleFor(ai => ai.MinSpawnProtectionTimeSeconds).LessThanOrEqualTo(ai => ai.MaxSpawnProtectionTimeSeconds); - aiParams.RuleFor(ai => ai.MinCollisionStopTimeSeconds).LessThanOrEqualTo(ai => ai.MaxCollisionStopTimeSeconds); - aiParams.RuleFor(ai => ai.MaxSpeedVariationPercent).InclusiveBetween(0, 1); - aiParams.RuleFor(ai => ai.DefaultAcceleration).GreaterThan(0); - aiParams.RuleFor(ai => ai.DefaultDeceleration).GreaterThan(0); - aiParams.RuleFor(ai => ai.NamePrefix).NotNull(); - aiParams.RuleFor(ai => ai.IgnoreObstaclesAfterSeconds).GreaterThanOrEqualTo(0); - aiParams.RuleFor(ai => ai.HourlyTrafficDensity) - .Must(htd => htd?.Count == 24) - .When(ai => ai.HourlyTrafficDensity != null) - .WithMessage("HourlyTrafficDensity must have exactly 24 entries"); - aiParams.RuleFor(ai => ai.CarSpecificOverrides).NotNull(); - aiParams.RuleFor(ai => ai.AiBehaviorUpdateIntervalHz).GreaterThan(0); - aiParams.RuleFor(ai => ai.LaneCountSpecificOverrides).NotNull(); - aiParams.RuleForEach(ai => ai.LaneCountSpecificOverrides).ChildRules(overrides => - { - overrides.RuleFor(o => o.Key).GreaterThan(0); - overrides.RuleFor(o => o.Value.MinAiSafetyDistanceMeters).LessThanOrEqualTo(o => o.Value.MaxAiSafetyDistanceMeters); - }); - }); }); RuleFor(cfg => cfg.Server).ChildRules(server => diff --git a/AssettoServer/Server/Configuration/Extra/ACExtraConfiguration.cs b/AssettoServer/Server/Configuration/Extra/ACExtraConfiguration.cs index 519ada92..01f7b802 100644 --- a/AssettoServer/Server/Configuration/Extra/ACExtraConfiguration.cs +++ b/AssettoServer/Server/Configuration/Extra/ACExtraConfiguration.cs @@ -29,8 +29,6 @@ public partial class ACExtraConfiguration : ObservableObject public int MandatoryClientSecurityLevel { get; internal set; } [YamlMember(Description = "Force headlights on for all cars")] public bool ForceLights { get; set; } - [YamlMember(Description = "Enable usage of /resetcar to teleport the player to the closest spline point. Requires CSP v0.2.3-preview47 or later")] - public bool EnableCarReset { get; set; } = false; [YamlMember(Description = "Enable vanilla server voting for: Session skip; Session restart")] public bool EnableSessionVote { get; set; } = true; [YamlMember(Description = "Enable vanilla server voting to kick a player")] @@ -57,8 +55,6 @@ public partial class ACExtraConfiguration : ObservableObject public bool LockServerDate { get; set; } = true; [YamlMember(Description = "Reduce track grip when the track is wet. This is much worse than proper CSP rain physics but allows you to run clients with public/Patreon CSP at the same time")] public double RainTrackGripReductionPercent { get; set; } = 0; - [YamlMember(Description = "Enable AI traffic")] - public bool EnableAi { get; init; } = false; [YamlMember(Description = "Override the country shown in CM. Please do not use this unless the autodetected country is wrong", DefaultValuesHandling = DefaultValuesHandling.OmitNull)] public List? GeoParamsCountryOverride { get; init; } = null; [YamlMember(Description = "List of plugins to enable")] @@ -115,8 +111,6 @@ public partial class ACExtraConfiguration : ObservableObject [YamlMember(Description = "Allow a user group to execute specific admin commands")] public List? UserGroupCommandPermissions { get; init; } - public AiParams AiParams { get; init; } = new(); - [YamlIgnore] internal bool ContainsObsoletePluginConfiguration { get; private set; } public void ToFile(string path) @@ -170,61 +164,5 @@ public static ACExtraConfiguration FromFile(string path) ] } ], - AiParams = new AiParams - { - CarSpecificOverrides = [ - new CarSpecificOverrides - { - Model = "my_car_model", - Acceleration = 2.5f, - Deceleration = 8.5f, - AllowedLanes = [LaneSpawnBehavior.Left, LaneSpawnBehavior.Middle, LaneSpawnBehavior.Right], - MaxOverbooking = 1, - CorneringSpeedFactor = 0.5f, - CorneringBrakeDistanceFactor = 3, - CorneringBrakeForceFactor = 0.5f, - EngineIdleRpm = 800, - EngineMaxRpm = 3000, - MaxLaneCount = 2, - MinLaneCount = 1, - TyreDiameterMeters = 0.8f, - SplineHeightOffsetMeters = 0, - VehicleLengthPostMeters = 2, - VehicleLengthPreMeters = 2, - MinAiSafetyDistanceMeters = 20, - MaxAiSafetyDistanceMeters = 25, - MinCollisionStopTimeSeconds = 0, - MaxCollisionStopTimeSeconds = 0, - MinSpawnProtectionTimeSeconds = 30, - MaxSpawnProtectionTimeSeconds = 60 - } - ], - LaneCountSpecificOverrides = new Dictionary - { - { - 1, - new LaneCountSpecificOverrides - { - MinAiSafetyDistanceMeters = 50, - MaxAiSafetyDistanceMeters = 100 - } - }, - { - 2, - new LaneCountSpecificOverrides - { - MinAiSafetyDistanceMeters = 40, - MaxAiSafetyDistanceMeters = 80 - } - } - }, - IgnorePlayerObstacleSpheres = [ - new Sphere - { - Center = new Vector3(0, 0, 0), - RadiusMeters = 50 - } - ] - } }; } diff --git a/AssettoServer/Server/EntryCarManager.cs b/AssettoServer/Server/EntryCarManager.cs index 15ae0ed0..3c512808 100644 --- a/AssettoServer/Server/EntryCarManager.cs +++ b/AssettoServer/Server/EntryCarManager.cs @@ -19,7 +19,7 @@ namespace AssettoServer.Server; public class EntryCarManager { public EntryCar[] EntryCars { get; private set; } = []; - internal ConcurrentDictionary ConnectedCars { get; } = new(); + public ConcurrentDictionary ConnectedCars { get; } = new(); private readonly ACServerConfiguration _configuration; private readonly IBlacklistService _blacklist; diff --git a/AssettoServer/Server/Lua/assettoserver.lua b/AssettoServer/Server/Lua/assettoserver.lua index 99159f7e..45f5c6b5 100644 --- a/AssettoServer/Server/Lua/assettoserver.lua +++ b/AssettoServer/Server/Lua/assettoserver.lua @@ -275,14 +275,3 @@ local teleportToPitsEvent = ac.OnlineEvent({ physics.teleportCarTo(0, ac.SpawnSet.Pits) end end) - -local requestResetCarEvent = ac.OnlineEvent({ - ac.StructItem.key("AS_RequestResetCar"), - dummy = ac.StructItem.byte(), -}, function (sender, message) - if sender ~= nil then return end - ac.debug("request_reset_car", message.dummy) -end) - -local resetCarControl = ac.ControlButton('__EXT_CMD_RESET', nil) -resetCarControl:onPressed(function() requestResetCarEvent({dummy=0}) end) diff --git a/AssettoServer/Server/OpenSlotFilters/AiSlotFilter.cs b/AssettoServer/Server/OpenSlotFilters/AiSlotFilter.cs deleted file mode 100644 index 2996e5ee..00000000 --- a/AssettoServer/Server/OpenSlotFilters/AiSlotFilter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AssettoServer.Server.Configuration; - -namespace AssettoServer.Server.OpenSlotFilters; - -public class AiSlotFilter : OpenSlotFilterBase -{ - private readonly EntryCarManager _entryCarManager; - private readonly ACServerConfiguration _configuration; - - public AiSlotFilter(EntryCarManager entryCarManager, ACServerConfiguration configuration) - { - _entryCarManager = entryCarManager; - _configuration = configuration; - } - - public override bool IsSlotOpen(EntryCar entryCar, ulong guid) - { - if (entryCar.AiMode == AiMode.Fixed - || (_configuration.Extra.AiParams.MaxPlayerCount > 0 && _entryCarManager.ConnectedCars.Count >= _configuration.Extra.AiParams.MaxPlayerCount)) - { - return false; - } - - return base.IsSlotOpen(entryCar, guid); - } -} diff --git a/AssettoServer/Startup.cs b/AssettoServer/Startup.cs index 5be20f22..412d2062 100644 --- a/AssettoServer/Startup.cs +++ b/AssettoServer/Startup.cs @@ -12,7 +12,6 @@ using AssettoServer.Network.Udp; using AssettoServer.Server; using AssettoServer.Server.Admin; -using AssettoServer.Server.Ai; using AssettoServer.Server.Blacklist; using AssettoServer.Server.CMContentProviders; using AssettoServer.Server.Configuration; @@ -53,7 +52,6 @@ public void ConfigureContainer(ContainerBuilder builder) builder.RegisterInstance(_configuration); builder.RegisterInstance(_loader); builder.RegisterModule(new WeatherModule(_configuration)); - builder.RegisterModule(new AiModule(_configuration)); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); diff --git a/AssettoServer/Server/Ai/AiBehavior.cs b/TrafficAiPlugin/AiBehavior.cs similarity index 89% rename from AssettoServer/Server/Ai/AiBehavior.cs rename to TrafficAiPlugin/AiBehavior.cs index 9ca4985b..3cdde0f5 100644 --- a/AssettoServer/Server/Ai/AiBehavior.cs +++ b/TrafficAiPlugin/AiBehavior.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; +using System.Numerics; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using AssettoServer.Network.Http; using AssettoServer.Network.Tcp; -using AssettoServer.Server.Ai.Splines; +using AssettoServer.Server; using AssettoServer.Server.Configuration; using AssettoServer.Server.Plugin; using AssettoServer.Shared.Network.Packets.Outgoing; @@ -16,12 +11,15 @@ using Microsoft.Extensions.Hosting; using Prometheus; using Serilog; +using TrafficAIPlugin.Configuration; +using TrafficAIPlugin.Splines; -namespace AssettoServer.Server.Ai; +namespace TrafficAIPlugin; public class AiBehavior : CriticalBackgroundService, IAssettoServerAutostart { - private readonly ACServerConfiguration _configuration; + private readonly ACServerConfiguration _serverConfiguration; + private readonly TrafficAiConfiguration _configuration; private readonly SessionManager _sessionManager; private readonly EntryCarManager _entryCarManager; private readonly AiSpline _spline; @@ -36,7 +34,8 @@ public class AiBehavior : CriticalBackgroundService, IAssettoServerAutostart private readonly Summary _obstacleDetectionDurationTimer; public AiBehavior(SessionManager sessionManager, - ACServerConfiguration configuration, + ACServerConfiguration serverConfiguration, + TrafficAiConfiguration configuration, EntryCarManager entryCarManager, IHostApplicationLifetime applicationLifetime, //EntryCar.Factory entryCarFactory, @@ -44,6 +43,7 @@ public AiBehavior(SessionManager sessionManager, AiSpline spline, HttpInfoCache httpInfoCache) : base(applicationLifetime) { _sessionManager = sessionManager; + _serverConfiguration = serverConfiguration; _configuration = configuration; _entryCarManager = entryCarManager; _spline = spline; @@ -51,7 +51,7 @@ public AiBehavior(SessionManager sessionManager, _junctionEvaluator = new JunctionEvaluator(spline, false); //_entryCarFactory = entryCarFactory; - if (_configuration.Extra.AiParams.Debug) + if (_configuration.Debug) { using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("AssettoServer.Server.Ai.ai_debug.lua")!; serverScriptProvider.AddScript(stream, "ai_debug.lua"); @@ -67,7 +67,7 @@ public AiBehavior(SessionManager sessionManager, }; _entryCarManager.ClientDisconnected += OnClientDisconnected; - _configuration.Extra.AiParams.PropertyChanged += (_, _) => AdjustOverbooking(); + _configuration.PropertyChanged += (_, _) => AdjustOverbooking(); _sessionManager.SessionChanged += OnSessionChanged; } @@ -109,7 +109,7 @@ private async Task ObstacleDetectionAsync(CancellationToken stoppingToken) } } - if (_configuration.Extra.AiParams.Debug) + if (_configuration.Debug) { SendDebugPackets(); } @@ -168,12 +168,12 @@ private void SendDebugPackets() } } - private readonly List _playerCars = new(); + private readonly List _playerCars = new(); private readonly List _initializedAiStates = new(); private readonly List _uninitializedAiStates = new(); private readonly List _playerOffsetPositions = new(); private readonly List> _aiMinDistanceToPlayer = new(); - private readonly List> _playerMinDistanceToAi = new(); + private readonly List> _playerMinDistanceToAi = new(); private void Update() { using var context = _updateDurationTimer.NewTimer(); @@ -192,8 +192,8 @@ private void Update() if (!entryCar.AiControlled && entryCar.Client?.HasSentFirstUpdate == true - && _sessionManager.ServerTimeMilliseconds - entryCar.LastActiveTime < _configuration.Extra.AiParams.PlayerAfkTimeoutMilliseconds - && (_configuration.Extra.AiParams.TwoWayTraffic || _configuration.Extra.AiParams.WrongWayTraffic || drivingTheRightWay)) + && _sessionManager.ServerTimeMilliseconds - entryCar.LastActiveTime < _configuration.PlayerAfkTimeoutMilliseconds + && (_configuration.TwoWayTraffic || _configuration.WrongWayTraffic || drivingTheRightWay)) { _playerCars.Add(entryCar); } @@ -217,7 +217,7 @@ private void Update() for (int i = 0; i < _playerCars.Count; i++) { - _playerMinDistanceToAi.Add(new KeyValuePair(_playerCars[i], float.MaxValue)); + _playerMinDistanceToAi.Add(new KeyValuePair(_playerCars[i], float.MaxValue)); } // Get minimum distance to a player for each AI @@ -231,7 +231,7 @@ private void Update() var offsetPosition = _playerCars[j].Status.Position; if (_playerCars[j].Status.Velocity != Vector3.Zero) { - offsetPosition += Vector3.Normalize(_playerCars[j].Status.Velocity) * _configuration.Extra.AiParams.PlayerPositionOffsetMeters; + offsetPosition += Vector3.Normalize(_playerCars[j].Status.Velocity) * _configuration.PlayerPositionOffsetMeters; } _playerOffsetPositions.Add(offsetPosition); @@ -246,7 +246,7 @@ private void Update() if (_playerMinDistanceToAi[j].Value > distanceSquared) { - _playerMinDistanceToAi[j] = new KeyValuePair(_playerCars[j], distanceSquared); + _playerMinDistanceToAi[j] = new KeyValuePair(_playerCars[j], distanceSquared); } } } @@ -256,7 +256,7 @@ private void Update() foreach (var dist in _aiMinDistanceToPlayer) { - if (dist.Value > _configuration.Extra.AiParams.PlayerRadiusSquared + if (dist.Value > _configuration.PlayerRadiusSquared && _sessionManager.ServerTimeMilliseconds > dist.Key.SpawnProtectionEnds) { _uninitializedAiStates.Add(dist.Key); @@ -334,7 +334,7 @@ private void Update() private async Task UpdateAsync(CancellationToken stoppingToken) { - using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(_configuration.Extra.AiParams.AiBehaviorUpdateIntervalMilliseconds)); + using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(_configuration.AiBehaviorUpdateIntervalMilliseconds)); while (await timer.WaitForNextTickAsync(stoppingToken)) { @@ -351,7 +351,7 @@ private async Task UpdateAsync(CancellationToken stoppingToken) private void OnClientDisconnected(ACTcpClient sender, EventArgs args) { - if (sender.EntryCar.AiMode != AiMode.None) + if (sender.EntryCar.AiMode != AssettoServer.Server.AiMode.None) { sender.EntryCar.SetAiControl(true); AdjustOverbooking(); @@ -399,7 +399,7 @@ private bool IsPositionSafe(int pointId) } if (entryCar.Client?.HasSentFirstUpdate == true - && Vector3.DistanceSquared(entryCar.Status.Position, ops.Points[pointId].Position) < _configuration.Extra.AiParams.SpawnSafetyDistanceToPlayerSquared) + && Vector3.DistanceSquared(entryCar.Status.Position, ops.Points[pointId].Position) < _configuration.SpawnSafetyDistanceToPlayerSquared) { return false; } @@ -408,7 +408,7 @@ private bool IsPositionSafe(int pointId) return true; } - private int GetSpawnPoint(EntryCar playerCar) + private int GetSpawnPoint(AssettoServer.Server.EntryCar playerCar) { var result = _spline.WorldToSpline(playerCar.Status.Position); var ops = _spline.Operations; @@ -419,12 +419,12 @@ private int GetSpawnPoint(EntryCar playerCar) int direction = Vector3.Dot(ops.GetForwardVector(result.PointId), playerCar.Status.Velocity) > 0 ? 1 : -1; // Do not not spawn if a player is too far away from the AI spline, e.g. in pits or in a part of the map without traffic - if (result.DistanceSquared > _configuration.Extra.AiParams.MaxPlayerDistanceToAiSplineSquared) + if (result.DistanceSquared > _configuration.MaxPlayerDistanceToAiSplineSquared) { return -1; } - int spawnDistance = Random.Shared.Next(_configuration.Extra.AiParams.MinSpawnDistancePoints, _configuration.Extra.AiParams.MaxSpawnDistancePoints); + int spawnDistance = Random.Shared.Next(_configuration.MinSpawnDistancePoints, _configuration.MaxSpawnDistancePoints); var spawnPointId = _junctionEvaluator.Traverse(result.PointId, spawnDistance * direction); if (spawnPointId >= 0) @@ -461,7 +461,7 @@ private void AdjustOverbooking() return; } - int targetAiCount = Math.Min(playerCount * Math.Min((int)Math.Round(_configuration.Extra.AiParams.AiPerPlayerTargetCount * _configuration.Extra.AiParams.TrafficDensity), aiSlots.Count), _configuration.Extra.AiParams.MaxAiTargetCount); + int targetAiCount = Math.Min(playerCount * Math.Min((int)Math.Round(_configuration.AiPerPlayerTargetCount * _configuration.TrafficDensity), aiSlots.Count), _configuration.MaxAiTargetCount); int overbooking = targetAiCount / aiSlots.Count; int rest = targetAiCount % aiSlots.Count; @@ -479,8 +479,8 @@ private void SetHttpDetailsExtensions() { _httpInfoCache.Extensions.Add("aiTraffic", new Dictionary> { - { "auto", _entryCarManager.EntryCars.Where(c => c.AiMode == AiMode.Auto).Select(c => c.SessionId).ToList() }, - { "fixed", _entryCarManager.EntryCars.Where(c => c.AiMode == AiMode.Fixed).Select(c => c.SessionId).ToList() } + { "auto", _entryCarManager.EntryCars.Where(c => c.AiMode == AssettoServer.Server.AiMode.Auto).Select(c => c.SessionId).ToList() }, + { "fixed", _entryCarManager.EntryCars.Where(c => c.AiMode == AssettoServer.Server.AiMode.Fixed).Select(c => c.SessionId).ToList() } }); } diff --git a/TrafficAiPlugin/AiSlotFilter.cs b/TrafficAiPlugin/AiSlotFilter.cs new file mode 100644 index 00000000..96c1993d --- /dev/null +++ b/TrafficAiPlugin/AiSlotFilter.cs @@ -0,0 +1,29 @@ +using AssettoServer.Server; +using AssettoServer.Server.Configuration; +using AssettoServer.Server.OpenSlotFilters; +using TrafficAIPlugin.Configuration; + +namespace TrafficAIPlugin; + +public class AiSlotFilter : OpenSlotFilterBase +{ + private readonly EntryCarManager _entryCarManager; + private readonly TrafficAiConfiguration _configuration; + + public AiSlotFilter(EntryCarManager entryCarManager, TrafficAiConfiguration configuration) + { + _entryCarManager = entryCarManager; + _configuration = configuration; + } + + public override bool IsSlotOpen(EntryCar entryCar, ulong guid) + { + if (entryCar.AiMode == AssettoServer.Server.AiMode.Fixed + || (_configuration.MaxPlayerCount > 0 && _entryCarManager.ConnectedCars.Count >= _configuration.MaxPlayerCount)) + { + return false; + } + + return base.IsSlotOpen(entryCar, guid); + } +} diff --git a/AssettoServer/Server/Ai/AiState.cs b/TrafficAiPlugin/AiState.cs similarity index 93% rename from AssettoServer/Server/Ai/AiState.cs rename to TrafficAiPlugin/AiState.cs index b18658be..da4bc370 100644 --- a/AssettoServer/Server/Ai/AiState.cs +++ b/TrafficAiPlugin/AiState.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Drawing; +using System.Drawing; using System.Numerics; -using AssettoServer.Server.Ai.Splines; +using AssettoServer.Server; using AssettoServer.Server.Configuration; -using AssettoServer.Server.Configuration.Extra; using AssettoServer.Server.Weather; using AssettoServer.Shared.Model; using AssettoServer.Shared.Network.Packets.Outgoing; @@ -12,8 +9,10 @@ using JPBotelho; using Serilog; using SunCalcNet.Model; +using TrafficAIPlugin.Configuration; +using TrafficAIPlugin.Splines; -namespace AssettoServer.Server.Ai; +namespace TrafficAIPlugin; public class AiState : IDisposable { @@ -43,7 +42,7 @@ private set public Color Color { get; private set; } public byte SpawnCounter { get; private set; } public float ClosestAiObstacleDistance { get; private set; } - public EntryCar EntryCar { get; } + public AssettoServer.Server.EntryCar EntryCar { get; } private const float WalkingSpeed = 10 / 3.6f; @@ -66,7 +65,8 @@ private set private float _minObstacleDistance; private double _randomTwilight; - private readonly ACServerConfiguration _configuration; + private readonly ACServerConfiguration _serverConfiguration; + private readonly TrafficAiConfiguration _configuration; private readonly SessionManager _sessionManager; private readonly EntryCarManager _entryCarManager; private readonly WeatherManager _weatherManager; @@ -95,11 +95,18 @@ private set Color.FromArgb(18, 46, 43) ]; - public AiState(EntryCar entryCar, SessionManager sessionManager, WeatherManager weatherManager, ACServerConfiguration configuration, EntryCarManager entryCarManager, AiSpline spline) + public AiState(EntryCar entryCar, + SessionManager sessionManager, + WeatherManager weatherManager, + ACServerConfiguration serverConfiguration, + TrafficAiConfiguration configuration, + EntryCarManager entryCarManager, + AiSpline spline) { EntryCar = entryCar; _sessionManager = sessionManager; _weatherManager = weatherManager; + _serverConfiguration = serverConfiguration; _configuration = configuration; _entryCarManager = entryCarManager; _spline = spline; @@ -127,14 +134,14 @@ public void Despawn() private void SetRandomSpeed() { - float variation = _configuration.Extra.AiParams.MaxSpeedMs * _configuration.Extra.AiParams.MaxSpeedVariationPercent; + float variation = _configuration.MaxSpeedMs * _configuration.MaxSpeedVariationPercent; float fastLaneOffset = 0; if (_spline.Points[CurrentSplinePointId].LeftId >= 0) { - fastLaneOffset = _configuration.Extra.AiParams.RightLaneOffsetMs; + fastLaneOffset = _configuration.RightLaneOffsetMs; } - InitialMaxSpeed = _configuration.Extra.AiParams.MaxSpeedMs + fastLaneOffset - (variation / 2) + (float)Random.Shared.NextDouble() * variation; + InitialMaxSpeed = _configuration.MaxSpeedMs + fastLaneOffset - (variation / 2) + (float)Random.Shared.NextDouble() * variation; CurrentSpeed = InitialMaxSpeed; TargetSpeed = InitialMaxSpeed; MaxSpeed = InitialMaxSpeed; @@ -159,9 +166,9 @@ public void Teleport(int pointId) SetRandomSpeed(); SetRandomColor(); - var minDist = _configuration.Extra.AiParams.MinAiSafetyDistanceSquared; - var maxDist = _configuration.Extra.AiParams.MaxAiSafetyDistanceSquared; - if (_configuration.Extra.AiParams.LaneCountSpecificOverrides.TryGetValue(_spline.GetLanes(CurrentSplinePointId).Length, out var overrides)) + var minDist = _configuration.MinAiSafetyDistanceSquared; + var maxDist = _configuration.MaxAiSafetyDistanceSquared; + if (_configuration.LaneCountSpecificOverrides.TryGetValue(_spline.GetLanes(CurrentSplinePointId).Length, out var overrides)) { minDist = overrides.MinAiSafetyDistanceSquared; maxDist = overrides.MaxAiSafetyDistanceSquared; @@ -173,8 +180,8 @@ public void Teleport(int pointId) maxDist = EntryCar.MaxAiSafetyDistanceMetersSquared.Value; SpawnProtectionEnds = _sessionManager.ServerTimeMilliseconds + Random.Shared.Next(EntryCar.AiMinSpawnProtectionTimeMilliseconds, EntryCar.AiMaxSpawnProtectionTimeMilliseconds); - SafetyDistanceSquared = Random.Shared.Next((int)Math.Round(minDist * (1.0f / _configuration.Extra.AiParams.TrafficDensity)), - (int)Math.Round(maxDist * (1.0f / _configuration.Extra.AiParams.TrafficDensity))); + SafetyDistanceSquared = Random.Shared.Next((int)Math.Round(minDist * (1.0f / _configuration.TrafficDensity)), + (int)Math.Round(maxDist * (1.0f / _configuration.TrafficDensity))); _stoppedForCollisionUntil = 0; _ignoreObstaclesUntil = 0; _obstacleHonkEnd = 0; @@ -402,9 +409,9 @@ private bool IsAllowedLane(in SplinePoint spawnPoint) private bool ShouldIgnorePlayerObstacles() { - if (_configuration.Extra.AiParams.IgnorePlayerObstacleSpheres != null) + if (_configuration.IgnorePlayerObstacleSpheres != null) { - foreach (var sphere in _configuration.Extra.AiParams.IgnorePlayerObstacleSpheres) + foreach (var sphere in _configuration.IgnorePlayerObstacleSpheres) { if (Vector3.DistanceSquared(Status.Position, sphere.Center) < sphere.RadiusMeters * sphere.RadiusMeters) { @@ -416,11 +423,11 @@ private bool ShouldIgnorePlayerObstacles() return false; } - private (EntryCar? entryCar, float distance) FindClosestPlayerObstacle() + private (AssettoServer.Server.EntryCar? entryCar, float distance) FindClosestPlayerObstacle() { if (!ShouldIgnorePlayerObstacles()) { - EntryCar? closestCar = null; + AssettoServer.Server.EntryCar? closestCar = null; float minDistance = float.MaxValue; for (var i = 0; i < _entryCarManager.EntryCars.Length; i++) { @@ -448,7 +455,7 @@ private bool ShouldIgnorePlayerObstacles() return (null, float.MaxValue); } - private bool IsObstacle(EntryCar playerCar) + private bool IsObstacle(AssettoServer.Server.EntryCar playerCar) { float aiRectWidth = 4; // Lane width float halfAiRectWidth = aiRectWidth / 2; @@ -553,7 +560,7 @@ public void DetectObstacles() _stoppedForObstacle = false; Log.Verbose("AI {SessionId} no longer stopped for obstacle", EntryCar.SessionId); } - else if (_stoppedForObstacle && _sessionManager.ServerTimeMilliseconds - _stoppedForObstacleSince > _configuration.Extra.AiParams.IgnoreObstaclesAfterMilliseconds) + else if (_stoppedForObstacle && _sessionManager.ServerTimeMilliseconds - _stoppedForObstacleSince > _configuration.IgnoreObstaclesAfterMilliseconds) { _ignoreObstaclesUntil = _sessionManager.ServerTimeMilliseconds + 10_000; Log.Verbose("AI {SessionId} ignoring obstacles until {IgnoreObstaclesUntil}", EntryCar.SessionId, _ignoreObstaclesUntil); @@ -669,8 +676,8 @@ public void Update() Status.TyreAngularSpeed[1] = encodedTyreAngularSpeed; Status.TyreAngularSpeed[2] = encodedTyreAngularSpeed; Status.TyreAngularSpeed[3] = encodedTyreAngularSpeed; - Status.EngineRpm = (ushort)MathUtils.Lerp(EntryCar.AiIdleEngineRpm, EntryCar.AiMaxEngineRpm, CurrentSpeed / _configuration.Extra.AiParams.MaxSpeedMs); - Status.StatusFlag = GetLights(_configuration.Extra.AiParams.EnableDaytimeLights, _weatherManager.CurrentSunPosition, _randomTwilight) + Status.EngineRpm = (ushort)MathUtils.Lerp(EntryCar.AiIdleEngineRpm, EntryCar.AiMaxEngineRpm, CurrentSpeed / _configuration.MaxSpeedMs); + Status.StatusFlag = GetLights(_configuration.EnableDaytimeLights, _weatherManager.CurrentSunPosition, _randomTwilight) | CarStatusFlags.HighBeamsOff | (_sessionManager.ServerTimeMilliseconds < _stoppedForCollisionUntil || CurrentSpeed < 20 / 3.6f ? CarStatusFlags.HazardsOn : 0) | (CurrentSpeed == 0 || Acceleration < 0 ? CarStatusFlags.BrakeLightsOn : 0) diff --git a/AssettoServer/Server/Ai/AiUpdater.cs b/TrafficAiPlugin/AiUpdater.cs similarity index 90% rename from AssettoServer/Server/Ai/AiUpdater.cs rename to TrafficAiPlugin/AiUpdater.cs index db3ad139..2b104a40 100644 --- a/AssettoServer/Server/Ai/AiUpdater.cs +++ b/TrafficAiPlugin/AiUpdater.cs @@ -1,6 +1,6 @@ -using System; +using AssettoServer.Server; -namespace AssettoServer.Server.Ai; +namespace TrafficAIPlugin; public class AiUpdater { diff --git a/AssettoServer/Server/Configuration/Extra/CarSpecificOverrides.cs b/TrafficAiPlugin/Configuration/CarSpecificOverrides.cs similarity index 97% rename from AssettoServer/Server/Configuration/Extra/CarSpecificOverrides.cs rename to TrafficAiPlugin/Configuration/CarSpecificOverrides.cs index b4d73559..84311859 100644 --- a/AssettoServer/Server/Configuration/Extra/CarSpecificOverrides.cs +++ b/TrafficAiPlugin/Configuration/CarSpecificOverrides.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace AssettoServer.Server.Configuration.Extra; +namespace TrafficAIPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class CarSpecificOverrides diff --git a/AssettoServer/Server/Ai/Configuration/Indicator.cs b/TrafficAiPlugin/Configuration/Indicator.cs similarity index 52% rename from AssettoServer/Server/Ai/Configuration/Indicator.cs rename to TrafficAiPlugin/Configuration/Indicator.cs index c0da2664..f14cb555 100644 --- a/AssettoServer/Server/Ai/Configuration/Indicator.cs +++ b/TrafficAiPlugin/Configuration/Indicator.cs @@ -1,4 +1,4 @@ -namespace AssettoServer.Server.Ai.Configuration; +namespace TrafficAIPlugin.Configuration; public enum Indicator { diff --git a/AssettoServer/Server/Ai/Configuration/JunctionRecord.cs b/TrafficAiPlugin/Configuration/JunctionRecord.cs similarity index 92% rename from AssettoServer/Server/Ai/Configuration/JunctionRecord.cs rename to TrafficAiPlugin/Configuration/JunctionRecord.cs index 9d734328..0edebb85 100644 --- a/AssettoServer/Server/Ai/Configuration/JunctionRecord.cs +++ b/TrafficAiPlugin/Configuration/JunctionRecord.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace AssettoServer.Server.Ai.Configuration; +namespace TrafficAIPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class JunctionRecord diff --git a/AssettoServer/Server/Configuration/Extra/LaneCountSpecificOverrides.cs b/TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs similarity index 92% rename from AssettoServer/Server/Configuration/Extra/LaneCountSpecificOverrides.cs rename to TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs index 255447fa..1d95eedf 100644 --- a/AssettoServer/Server/Configuration/Extra/LaneCountSpecificOverrides.cs +++ b/TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace AssettoServer.Server.Configuration.Extra; +namespace TrafficAIPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class LaneCountSpecificOverrides diff --git a/AssettoServer/Server/Configuration/Extra/LaneSpawnBehavior.cs b/TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs similarity index 56% rename from AssettoServer/Server/Configuration/Extra/LaneSpawnBehavior.cs rename to TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs index aa936060..64a6ce17 100644 --- a/AssettoServer/Server/Configuration/Extra/LaneSpawnBehavior.cs +++ b/TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs @@ -1,4 +1,4 @@ -namespace AssettoServer.Server.Configuration.Extra; +namespace TrafficAIPlugin.Configuration; public enum LaneSpawnBehavior { diff --git a/AssettoServer/Server/Configuration/Extra/Sphere.cs b/TrafficAiPlugin/Configuration/Sphere.cs similarity index 82% rename from AssettoServer/Server/Configuration/Extra/Sphere.cs rename to TrafficAiPlugin/Configuration/Sphere.cs index fedaa6d8..669bb5f1 100644 --- a/AssettoServer/Server/Configuration/Extra/Sphere.cs +++ b/TrafficAiPlugin/Configuration/Sphere.cs @@ -1,7 +1,7 @@ using System.Numerics; using JetBrains.Annotations; -namespace AssettoServer.Server.Configuration.Extra; +namespace TrafficAIPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class Sphere diff --git a/AssettoServer/Server/Ai/Configuration/SplineConfiguration.cs b/TrafficAiPlugin/Configuration/SplineConfiguration.cs similarity index 80% rename from AssettoServer/Server/Ai/Configuration/SplineConfiguration.cs rename to TrafficAiPlugin/Configuration/SplineConfiguration.cs index 259ed49a..f047b735 100644 --- a/AssettoServer/Server/Ai/Configuration/SplineConfiguration.cs +++ b/TrafficAiPlugin/Configuration/SplineConfiguration.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using JetBrains.Annotations; +using JetBrains.Annotations; -namespace AssettoServer.Server.Ai.Configuration; +namespace TrafficAIPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class SplineConfiguration diff --git a/AssettoServer/Server/Configuration/Extra/AiParams.cs b/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs similarity index 96% rename from AssettoServer/Server/Configuration/Extra/AiParams.cs rename to TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs index 17374258..024001fb 100644 --- a/AssettoServer/Server/Configuration/Extra/AiParams.cs +++ b/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs @@ -1,14 +1,16 @@ -using System.Collections.Generic; +using AssettoServer.Server.Configuration; using CommunityToolkit.Mvvm.ComponentModel; using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace AssettoServer.Server.Configuration.Extra; +namespace TrafficAIPlugin.Configuration; #pragma warning disable CS0657 [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] -public partial class AiParams : ObservableObject +public partial class TrafficAiConfiguration : ObservableObject, IValidateConfiguration { + [YamlMember(Description = "Enable usage of /resetcar to teleport the player to the closest spline point. Requires CSP v0.2.3-preview47 or later")] + public bool EnableCarReset { get; set; } = false; [YamlMember(Description = "Automatically assign traffic cars based on the car folder name")] public bool AutoAssignTrafficCars { get; init; } = true; [YamlMember(Description = "Radius around a player in which AI cars won't despawn")] diff --git a/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs b/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs new file mode 100644 index 00000000..6894e472 --- /dev/null +++ b/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs @@ -0,0 +1,34 @@ +using FluentValidation; +using JetBrains.Annotations; + +namespace TrafficAIPlugin.Configuration; + +// Use FluentValidation to validate plugin configuration +[UsedImplicitly] +public class TrafficAiConfigurationValidator : AbstractValidator +{ + public TrafficAiConfigurationValidator() + { + RuleFor(ai => ai.MinSpawnDistancePoints).LessThanOrEqualTo(ai => ai.MaxSpawnDistancePoints); + RuleFor(ai => ai.MinAiSafetyDistanceMeters).LessThanOrEqualTo(ai => ai.MaxAiSafetyDistanceMeters); + RuleFor(ai => ai.MinSpawnProtectionTimeSeconds).LessThanOrEqualTo(ai => ai.MaxSpawnProtectionTimeSeconds); + RuleFor(ai => ai.MinCollisionStopTimeSeconds).LessThanOrEqualTo(ai => ai.MaxCollisionStopTimeSeconds); + RuleFor(ai => ai.MaxSpeedVariationPercent).InclusiveBetween(0, 1); + RuleFor(ai => ai.DefaultAcceleration).GreaterThan(0); + RuleFor(ai => ai.DefaultDeceleration).GreaterThan(0); + RuleFor(ai => ai.NamePrefix).NotNull(); + RuleFor(ai => ai.IgnoreObstaclesAfterSeconds).GreaterThanOrEqualTo(0); + RuleFor(ai => ai.HourlyTrafficDensity) + .Must(htd => htd?.Count == 24) + .When(ai => ai.HourlyTrafficDensity != null) + .WithMessage("HourlyTrafficDensity must have exactly 24 entries"); + RuleFor(ai => ai.CarSpecificOverrides).NotNull(); + RuleFor(ai => ai.AiBehaviorUpdateIntervalHz).GreaterThan(0); + RuleFor(ai => ai.LaneCountSpecificOverrides).NotNull(); + RuleForEach(ai => ai.LaneCountSpecificOverrides).ChildRules(overrides => + { + overrides.RuleFor(o => o.Key).GreaterThan(0); + overrides.RuleFor(o => o.Value.MinAiSafetyDistanceMeters).LessThanOrEqualTo(o => o.Value.MaxAiSafetyDistanceMeters); + }); + } +} diff --git a/AssettoServer/Server/Ai/Configuration/TrafficConfiguration.cs b/TrafficAiPlugin/Configuration/TrafficConfiguration.cs similarity index 72% rename from AssettoServer/Server/Ai/Configuration/TrafficConfiguration.cs rename to TrafficAiPlugin/Configuration/TrafficConfiguration.cs index 991f80a5..7c67d48c 100644 --- a/AssettoServer/Server/Ai/Configuration/TrafficConfiguration.cs +++ b/TrafficAiPlugin/Configuration/TrafficConfiguration.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; -using JetBrains.Annotations; +using JetBrains.Annotations; -namespace AssettoServer.Server.Ai.Configuration; +namespace TrafficAIPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class TrafficConfiguration diff --git a/AssettoServer/Server/Ai/DynamicTrafficDensity.cs b/TrafficAiPlugin/DynamicTrafficDensity.cs similarity index 55% rename from AssettoServer/Server/Ai/DynamicTrafficDensity.cs rename to TrafficAiPlugin/DynamicTrafficDensity.cs index 6c08fc6c..acddf3fc 100644 --- a/AssettoServer/Server/Ai/DynamicTrafficDensity.cs +++ b/TrafficAiPlugin/DynamicTrafficDensity.cs @@ -1,23 +1,25 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using AssettoServer.Server.Configuration; +using AssettoServer.Server.Configuration; using AssettoServer.Server.Weather; using AssettoServer.Shared.Services; using AssettoServer.Utils; using Microsoft.Extensions.Hosting; using Serilog; +using TrafficAIPlugin.Configuration; -namespace AssettoServer.Server.Ai; +namespace TrafficAIPlugin; public class DynamicTrafficDensity : CriticalBackgroundService { - private readonly ACServerConfiguration _configuration; + private readonly ACServerConfiguration _serverConfiguration; + private readonly TrafficAiConfiguration _configuration; private readonly WeatherManager _weatherManager; - public DynamicTrafficDensity(ACServerConfiguration configuration, WeatherManager weatherManager, IHostApplicationLifetime applicationLifetime) : base(applicationLifetime) + public DynamicTrafficDensity(ACServerConfiguration serverConfiguration, + TrafficAiConfiguration configuration, + WeatherManager weatherManager, + IHostApplicationLifetime applicationLifetime) : base(applicationLifetime) { + _serverConfiguration = serverConfiguration; _configuration = configuration; _weatherManager = weatherManager; } @@ -27,23 +29,25 @@ private float GetDensity(double hourOfDay) // ReSharper disable once CompareOfFloatsByEqualityOperator if (Math.Truncate(hourOfDay) == hourOfDay) { - return _configuration.Extra.AiParams.HourlyTrafficDensity![(int)hourOfDay]; + return _configuration.HourlyTrafficDensity![(int)hourOfDay]; } int lowerBound = (int)Math.Floor(hourOfDay); int higherBound = (int)Math.Ceiling(hourOfDay) % 24; - return (float)MathUtils.Lerp(_configuration.Extra.AiParams.HourlyTrafficDensity![lowerBound], _configuration.Extra.AiParams.HourlyTrafficDensity![higherBound], hourOfDay - lowerBound); + return (float)MathUtils.Lerp(_configuration.HourlyTrafficDensity![lowerBound], _configuration.HourlyTrafficDensity![higherBound], hourOfDay - lowerBound); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - if (_configuration.Server.TimeOfDayMultiplier == 0 ) + if (_configuration.HourlyTrafficDensity == null) return; + + if (_serverConfiguration.Server.TimeOfDayMultiplier == 0 ) { throw new ConfigurationException("TIME_OF_DAY_MULT in server_cfg.ini must be greater than 0"); } - foreach (var wfxParam in _configuration.Server.Weathers.Where(w => w.WeatherFxParams.TimeMultiplier.HasValue)) + foreach (var wfxParam in _serverConfiguration.Server.Weathers.Where(w => w.WeatherFxParams.TimeMultiplier.HasValue)) { if (wfxParam.WeatherFxParams.TimeMultiplier == 0) { @@ -56,7 +60,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) try { double hours = _weatherManager.CurrentDateTime.TimeOfDay.TickOfDay / 10_000_000.0 / 3600.0; - _configuration.Extra.AiParams.TrafficDensity = GetDensity(hours); + _configuration.TrafficDensity = GetDensity(hours); } catch (Exception ex) { @@ -64,7 +68,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } finally { - await Task.Delay(TimeSpan.FromMinutes(10.0 / _configuration.Server.TimeOfDayMultiplier), stoppingToken); + await Task.Delay(TimeSpan.FromMinutes(10.0 / _serverConfiguration.Server.TimeOfDayMultiplier), stoppingToken); } } } diff --git a/TrafficAiPlugin/EntryCarTrafficAi.cs b/TrafficAiPlugin/EntryCarTrafficAi.cs new file mode 100644 index 00000000..fd1aa695 --- /dev/null +++ b/TrafficAiPlugin/EntryCarTrafficAi.cs @@ -0,0 +1,358 @@ +using System.ComponentModel; +using System.Numerics; +using System.Runtime.InteropServices; +using AssettoServer.Shared.Model; +using AssettoServer.Shared.Network.Packets.Outgoing; + +namespace TrafficAIPlugin; + +public enum AiMode +{ + None, + Auto, + Fixed +} + +public class EntryCarTrafficAi +{ + public bool AiControlled { get; set; } + public AiMode AiMode { get; set; } + public int TargetAiStateCount { get; private set; } = 1; + public byte[] LastSeenAiSpawn { get; } + public byte[] AiPakSequenceIds { get; } + public AiState?[] LastSeenAiState { get; } + public string? AiName { get; private set; } + public bool AiEnableColorChanges { get; set; } = false; + public int AiIdleEngineRpm { get; set; } = 800; + public int AiMaxEngineRpm { get; set; } = 3000; + public float AiAcceleration { get; set; } + public float AiDeceleration { get; set; } + public float AiCorneringSpeedFactor { get; set; } + public float AiCorneringBrakeDistanceFactor { get; set; } + public float AiCorneringBrakeForceFactor { get; set; } + public float AiSplineHeightOffsetMeters { get; set; } + public int? AiMaxOverbooking { get; set; } + public int AiMinSpawnProtectionTimeMilliseconds { get; set; } + public int AiMaxSpawnProtectionTimeMilliseconds { get; set; } + public int? MinLaneCount { get; set; } + public int? MaxLaneCount { get; set; } + public int AiMinCollisionStopTimeMilliseconds { get; set; } + public int AiMaxCollisionStopTimeMilliseconds { get; set; } + public float VehicleLengthPreMeters { get; set; } + public float VehicleLengthPostMeters { get; set; } + public int? MinAiSafetyDistanceMetersSquared { get; set; } + public int? MaxAiSafetyDistanceMetersSquared { get; set; } + public List? AiAllowedLanes { get; set; } + public float TyreDiameterMeters { get; set; } + private readonly List _aiStates = []; + private Span AiStatesSpan => CollectionsMarshal.AsSpan(_aiStates); + + private readonly Func _aiStateFactory; + private readonly AiSpline? _spline; + + private void AiInit() + { + AiName = $"{_configuration.Extra.AiParams.NamePrefix} {SessionId}"; + SetAiOverbooking(0); + + _configuration.Extra.AiParams.PropertyChanged += OnConfigReload; + OnConfigReload(_configuration, new PropertyChangedEventArgs(string.Empty)); + } + + private void OnConfigReload(object? sender, PropertyChangedEventArgs args) + { + AiSplineHeightOffsetMeters = _configuration.Extra.AiParams.SplineHeightOffsetMeters; + AiAcceleration = _configuration.Extra.AiParams.DefaultAcceleration; + AiDeceleration = _configuration.Extra.AiParams.DefaultDeceleration; + AiCorneringSpeedFactor = _configuration.Extra.AiParams.CorneringSpeedFactor; + AiCorneringBrakeDistanceFactor = _configuration.Extra.AiParams.CorneringBrakeDistanceFactor; + AiCorneringBrakeForceFactor = _configuration.Extra.AiParams.CorneringBrakeForceFactor; + TyreDiameterMeters = _configuration.Extra.AiParams.TyreDiameterMeters; + AiMinSpawnProtectionTimeMilliseconds = _configuration.Extra.AiParams.MinSpawnProtectionTimeMilliseconds; + AiMaxSpawnProtectionTimeMilliseconds = _configuration.Extra.AiParams.MaxSpawnProtectionTimeMilliseconds; + AiMinCollisionStopTimeMilliseconds = _configuration.Extra.AiParams.MinCollisionStopTimeMilliseconds; + AiMaxCollisionStopTimeMilliseconds = _configuration.Extra.AiParams.MaxCollisionStopTimeMilliseconds; + + foreach (var carOverrides in _configuration.Extra.AiParams.CarSpecificOverrides) + { + if (carOverrides.Model == Model) + { + if (carOverrides.SplineHeightOffsetMeters.HasValue) + AiSplineHeightOffsetMeters = carOverrides.SplineHeightOffsetMeters.Value; + if (carOverrides.EngineIdleRpm.HasValue) + AiIdleEngineRpm = carOverrides.EngineIdleRpm.Value; + if (carOverrides.EngineMaxRpm.HasValue) + AiMaxEngineRpm = carOverrides.EngineMaxRpm.Value; + if (carOverrides.Acceleration.HasValue) + AiAcceleration = carOverrides.Acceleration.Value; + if (carOverrides.Deceleration.HasValue) + AiDeceleration = carOverrides.Deceleration.Value; + if (carOverrides.CorneringSpeedFactor.HasValue) + AiCorneringSpeedFactor = carOverrides.CorneringSpeedFactor.Value; + if (carOverrides.CorneringBrakeDistanceFactor.HasValue) + AiCorneringBrakeDistanceFactor = carOverrides.CorneringBrakeDistanceFactor.Value; + if (carOverrides.CorneringBrakeForceFactor.HasValue) + AiCorneringBrakeForceFactor = carOverrides.CorneringBrakeForceFactor.Value; + if (carOverrides.TyreDiameterMeters.HasValue) + TyreDiameterMeters = carOverrides.TyreDiameterMeters.Value; + if (carOverrides.MaxOverbooking.HasValue) + AiMaxOverbooking = carOverrides.MaxOverbooking.Value; + if (carOverrides.MinSpawnProtectionTimeMilliseconds.HasValue) + AiMinSpawnProtectionTimeMilliseconds = carOverrides.MinSpawnProtectionTimeMilliseconds.Value; + if (carOverrides.MaxSpawnProtectionTimeMilliseconds.HasValue) + AiMaxSpawnProtectionTimeMilliseconds = carOverrides.MaxSpawnProtectionTimeMilliseconds.Value; + if (carOverrides.MinCollisionStopTimeMilliseconds.HasValue) + AiMinCollisionStopTimeMilliseconds = carOverrides.MinCollisionStopTimeMilliseconds.Value; + if (carOverrides.MaxCollisionStopTimeMilliseconds.HasValue) + AiMaxCollisionStopTimeMilliseconds = carOverrides.MaxCollisionStopTimeMilliseconds.Value; + if (carOverrides.VehicleLengthPreMeters.HasValue) + VehicleLengthPreMeters = carOverrides.VehicleLengthPreMeters.Value; + if (carOverrides.VehicleLengthPostMeters.HasValue) + VehicleLengthPostMeters = carOverrides.VehicleLengthPostMeters.Value; + + AiAllowedLanes = carOverrides.AllowedLanes; + MinAiSafetyDistanceMetersSquared = carOverrides.MinAiSafetyDistanceMetersSquared; + MaxAiSafetyDistanceMetersSquared = carOverrides.MaxAiSafetyDistanceMetersSquared; + MinLaneCount = carOverrides.MinLaneCount; + MaxLaneCount = carOverrides.MaxLaneCount; + } + } + } + + public void RemoveUnsafeStates() + { + foreach (var aiState in AiStatesSpan) + { + if (!aiState.Initialized) continue; + + foreach (var targetAiState in AiStatesSpan) + { + if (aiState != targetAiState + && targetAiState.Initialized + && Vector3.DistanceSquared(aiState.Status.Position, targetAiState.Status.Position) < _configuration.Extra.AiParams.MinStateDistanceSquared + && (_configuration.Extra.AiParams.TwoWayTraffic || Vector3.Dot(aiState.Status.Velocity, targetAiState.Status.Velocity) > 0)) + { + aiState.Despawn(); + Logger.Verbose("Removed close state from AI {SessionId}", SessionId); + } + } + } + } + + public void AiUpdate() + { + foreach (var aiState in AiStatesSpan) + { + aiState.Update(); + } + } + + public void AiObstacleDetection() + { + foreach (var aiState in AiStatesSpan) + { + aiState.DetectObstacles(); + } + } + + public AiState? GetBestStateForPlayer(CarStatus playerStatus) + { + AiState? bestState = null; + float minDistance = float.MaxValue; + + foreach (var aiState in AiStatesSpan) + { + if (!aiState.Initialized) continue; + + float distance = Vector3.DistanceSquared(aiState.Status.Position, playerStatus.Position); + + if (_configuration.Extra.AiParams.TwoWayTraffic) + { + if (distance < minDistance) + { + bestState = aiState; + minDistance = distance; + } + } + else + { + bool isBestSameDirection = bestState != null && Vector3.Dot(bestState.Status.Velocity, playerStatus.Velocity) > 0; + bool isCandidateSameDirection = Vector3.Dot(aiState.Status.Velocity, playerStatus.Velocity) > 0; + bool isPlayerFastEnough = playerStatus.Velocity.LengthSquared() > 1; + bool isTieBreaker = minDistance < _configuration.Extra.AiParams.MinStateDistanceSquared && + distance < _configuration.Extra.AiParams.MinStateDistanceSquared && + isPlayerFastEnough; + + // Tie breaker: Multiple close states, so take the one with min distance and same direction + if ((isTieBreaker && isCandidateSameDirection && (distance < minDistance || !isBestSameDirection)) + || (!isTieBreaker && distance < minDistance)) + { + bestState = aiState; + minDistance = distance; + } + } + } + + return bestState; + } + + public bool IsPositionSafe(int pointId) + { + ArgumentNullException.ThrowIfNull(_spline); + + var ops = _spline.Operations; + + foreach (var aiState in AiStatesSpan) + { + if (aiState.Initialized + && Vector3.DistanceSquared(aiState.Status.Position, ops.Points[pointId].Position) < aiState.SafetyDistanceSquared + && ops.IsSameDirection(aiState.CurrentSplinePointId, pointId)) + { + return false; + } + } + + return true; + } + + public (AiState? AiState, float DistanceSquared) GetClosestAiState(Vector3 position) + { + AiState? closestState = null; + float minDistanceSquared = float.MaxValue; + + foreach (var aiState in AiStatesSpan) + { + float distanceSquared = Vector3.DistanceSquared(position, aiState.Status.Position); + if (distanceSquared < minDistanceSquared) + { + closestState = aiState; + minDistanceSquared = distanceSquared; + } + } + + return (closestState, minDistanceSquared); + } + + public void GetInitializedStates(List initializedStates, List? uninitializedStates = null) + { + foreach (var aiState in AiStatesSpan) + { + if (aiState.Initialized) + { + initializedStates.Add(aiState); + } + else + { + uninitializedStates?.Add(aiState); + } + } + } + + public bool CanSpawnAiState(Vector3 spawnPoint, AiState aiState) + { + // Remove state if AI slot overbooking was reduced + if (_aiStates.IndexOf(aiState) >= TargetAiStateCount) + { + aiState.Dispose(); + _aiStates.Remove(aiState); + + Logger.Verbose("Removed state of Traffic {SessionId} due to overbooking reduction", SessionId); + + if (_aiStates.Count == 0) + { + Logger.Verbose("Traffic {SessionId} has no states left, disconnecting", SessionId); + _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = SessionId }); + } + + return false; + } + + foreach (var state in AiStatesSpan) + { + if (state == aiState || !state.Initialized) continue; + + if (Vector3.DistanceSquared(spawnPoint, state.Status.Position) < _configuration.Extra.AiParams.StateSpawnDistanceSquared) + { + return false; + } + } + + return true; + } + + public void SetAiControl(bool aiControlled) + { + if (AiControlled != aiControlled) + { + AiControlled = aiControlled; + + if (AiControlled) + { + Logger.Debug("Slot {SessionId} is now controlled by AI", SessionId); + + AiReset(); + _entryCarManager.BroadcastPacket(new CarConnected + { + SessionId = SessionId, + Name = AiName + }); + if (_configuration.Extra.AiParams.HideAiCars) + { + _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate + { + SessionId = SessionId, + Visible = CSPCarVisibility.Invisible + }); + } + } + else + { + Logger.Debug("Slot {SessionId} is no longer controlled by AI", SessionId); + if (_aiStates.Count > 0) + { + _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = SessionId }); + } + + if (_configuration.Extra.AiParams.HideAiCars) + { + _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate + { + SessionId = SessionId, + Visible = CSPCarVisibility.Visible + }); + } + + AiReset(); + } + } + } + + public void SetAiOverbooking(int count) + { + if (AiMaxOverbooking.HasValue) + { + count = Math.Min(count, AiMaxOverbooking.Value); + } + + if (count > _aiStates.Count) + { + int newAis = count - _aiStates.Count; + for (int i = 0; i < newAis; i++) + { + _aiStates.Add(_aiStateFactory(this)); + } + } + + TargetAiStateCount = count; + } + + private void AiReset() + { + foreach (var state in AiStatesSpan) + { + state.Despawn(); + } + _aiStates.Clear(); + _aiStates.Add(_aiStateFactory(this)); + } +} diff --git a/AssettoServer/Server/Ai/Splines/AdjacentLaneDetector.cs b/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs similarity index 97% rename from AssettoServer/Server/Ai/Splines/AdjacentLaneDetector.cs rename to TrafficAiPlugin/Splines/AdjacentLaneDetector.cs index f0ec89d1..686548e5 100644 --- a/AssettoServer/Server/Ai/Splines/AdjacentLaneDetector.cs +++ b/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs @@ -1,9 +1,8 @@ -using System; -using System.Numerics; +using System.Numerics; using Serilog; using SerilogTimings; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public static class AdjacentLaneDetector { diff --git a/AssettoServer/Server/Ai/Splines/AiSpline.cs b/TrafficAiPlugin/Splines/AiSpline.cs similarity index 97% rename from AssettoServer/Server/Ai/Splines/AiSpline.cs rename to TrafficAiPlugin/Splines/AiSpline.cs index 6f46de19..52c5af15 100644 --- a/AssettoServer/Server/Ai/Splines/AiSpline.cs +++ b/TrafficAiPlugin/Splines/AiSpline.cs @@ -1,6 +1,4 @@ -using System; -using System.Buffers; -using System.IO; +using System.Buffers; using System.IO.MemoryMappedFiles; using System.Numerics; using System.Runtime.InteropServices; @@ -10,7 +8,7 @@ using Serilog; using Supercluster.KDTree; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class AiSpline : IDisposable { diff --git a/AssettoServer/Server/Ai/Splines/AiSplineHeader.cs b/TrafficAiPlugin/Splines/AiSplineHeader.cs similarity index 75% rename from AssettoServer/Server/Ai/Splines/AiSplineHeader.cs rename to TrafficAiPlugin/Splines/AiSplineHeader.cs index 2e646502..f4d9b64f 100644 --- a/AssettoServer/Server/Ai/Splines/AiSplineHeader.cs +++ b/TrafficAiPlugin/Splines/AiSplineHeader.cs @@ -1,4 +1,4 @@ -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public struct AiSplineHeader { diff --git a/AssettoServer/Server/Ai/Splines/AiSplineLocator.cs b/TrafficAiPlugin/Splines/AiSplineLocator.cs similarity index 96% rename from AssettoServer/Server/Ai/Splines/AiSplineLocator.cs rename to TrafficAiPlugin/Splines/AiSplineLocator.cs index 741078f9..5a60ab68 100644 --- a/AssettoServer/Server/Ai/Splines/AiSplineLocator.cs +++ b/TrafficAiPlugin/Splines/AiSplineLocator.cs @@ -1,12 +1,9 @@ -using System; -using System.IO; -using System.IO.Hashing; -using System.Linq; +using System.IO.Hashing; using AssettoServer.Server.Configuration; using Serilog; using SerilogTimings; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class AiSplineLocator { diff --git a/AssettoServer/Server/Ai/Splines/AiSplineWriter.cs b/TrafficAiPlugin/Splines/AiSplineWriter.cs similarity index 93% rename from AssettoServer/Server/Ai/Splines/AiSplineWriter.cs rename to TrafficAiPlugin/Splines/AiSplineWriter.cs index ee1c6a70..7225fa1b 100644 --- a/AssettoServer/Server/Ai/Splines/AiSplineWriter.cs +++ b/TrafficAiPlugin/Splines/AiSplineWriter.cs @@ -1,8 +1,7 @@ -using System.IO; -using AssettoServer.Utils; +using AssettoServer.Utils; using Serilog; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class AiSplineWriter { diff --git a/AssettoServer/Server/Ai/Splines/FastLane.cs b/TrafficAiPlugin/Splines/FastLane.cs similarity index 70% rename from AssettoServer/Server/Ai/Splines/FastLane.cs rename to TrafficAiPlugin/Splines/FastLane.cs index a58d191e..7f7e8a18 100644 --- a/AssettoServer/Server/Ai/Splines/FastLane.cs +++ b/TrafficAiPlugin/Splines/FastLane.cs @@ -1,6 +1,4 @@ -using System; - -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class FastLane { diff --git a/AssettoServer/Server/Ai/Splines/FastLaneParser.cs b/TrafficAiPlugin/Splines/FastLaneParser.cs similarity index 97% rename from AssettoServer/Server/Ai/Splines/FastLaneParser.cs rename to TrafficAiPlugin/Splines/FastLaneParser.cs index 18ba8e4a..704bd59e 100644 --- a/AssettoServer/Server/Ai/Splines/FastLaneParser.cs +++ b/TrafficAiPlugin/Splines/FastLaneParser.cs @@ -1,16 +1,12 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; +using System.IO.Compression; using System.Numerics; -using AssettoServer.Server.Ai.Configuration; using AssettoServer.Server.Configuration; using AssettoServer.Utils; using Serilog; +using TrafficAIPlugin.Configuration; using YamlDotNet.Serialization; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class FastLaneParser { diff --git a/AssettoServer/Server/Ai/Splines/JunctionEvaluator.cs b/TrafficAiPlugin/Splines/JunctionEvaluator.cs similarity index 96% rename from AssettoServer/Server/Ai/Splines/JunctionEvaluator.cs rename to TrafficAiPlugin/Splines/JunctionEvaluator.cs index b55162df..10405583 100644 --- a/AssettoServer/Server/Ai/Splines/JunctionEvaluator.cs +++ b/TrafficAiPlugin/Splines/JunctionEvaluator.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class JunctionEvaluator { diff --git a/AssettoServer/Server/Ai/Splines/MutableAiSpline.cs b/TrafficAiPlugin/Splines/MutableAiSpline.cs similarity index 96% rename from AssettoServer/Server/Ai/Splines/MutableAiSpline.cs rename to TrafficAiPlugin/Splines/MutableAiSpline.cs index fab45127..067d5f12 100644 --- a/AssettoServer/Server/Ai/Splines/MutableAiSpline.cs +++ b/TrafficAiPlugin/Splines/MutableAiSpline.cs @@ -1,13 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using AssettoServer.Server.Ai.Configuration; +using System.Numerics; using AssettoServer.Shared.Network.Packets.Outgoing; using Serilog; using Supercluster.KDTree; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class MutableAiSpline { diff --git a/AssettoServer/Server/Ai/Splines/SlowestAiStates.cs b/TrafficAiPlugin/Splines/SlowestAiStates.cs similarity index 96% rename from AssettoServer/Server/Ai/Splines/SlowestAiStates.cs rename to TrafficAiPlugin/Splines/SlowestAiStates.cs index d9a4d7df..7a218afc 100644 --- a/AssettoServer/Server/Ai/Splines/SlowestAiStates.cs +++ b/TrafficAiPlugin/Splines/SlowestAiStates.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public class SlowestAiStates { diff --git a/AssettoServer/Server/Ai/Splines/SplineJunction.cs b/TrafficAiPlugin/Splines/SplineJunction.cs similarity index 91% rename from AssettoServer/Server/Ai/Splines/SplineJunction.cs rename to TrafficAiPlugin/Splines/SplineJunction.cs index aa6c239c..99b69bdd 100644 --- a/AssettoServer/Server/Ai/Splines/SplineJunction.cs +++ b/TrafficAiPlugin/Splines/SplineJunction.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using AssettoServer.Shared.Network.Packets.Outgoing; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct SplineJunction diff --git a/AssettoServer/Server/Ai/Splines/SplinePoint.cs b/TrafficAiPlugin/Splines/SplinePoint.cs similarity index 91% rename from AssettoServer/Server/Ai/Splines/SplinePoint.cs rename to TrafficAiPlugin/Splines/SplinePoint.cs index b1a78746..c1cad9d7 100644 --- a/AssettoServer/Server/Ai/Splines/SplinePoint.cs +++ b/TrafficAiPlugin/Splines/SplinePoint.cs @@ -1,7 +1,7 @@ using System.Numerics; using System.Runtime.InteropServices; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct SplinePoint diff --git a/AssettoServer/Server/Ai/Splines/SplinePointOperations.cs b/TrafficAiPlugin/Splines/SplinePointOperations.cs similarity index 95% rename from AssettoServer/Server/Ai/Splines/SplinePointOperations.cs rename to TrafficAiPlugin/Splines/SplinePointOperations.cs index 5d0d4945..94ef69a4 100644 --- a/AssettoServer/Server/Ai/Splines/SplinePointOperations.cs +++ b/TrafficAiPlugin/Splines/SplinePointOperations.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Numerics; +using System.Numerics; using AssettoServer.Utils; using Serilog; -namespace AssettoServer.Server.Ai.Splines; +namespace TrafficAIPlugin.Splines; public readonly ref struct SplinePointOperations { diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs new file mode 100644 index 00000000..4422c6a5 --- /dev/null +++ b/TrafficAiPlugin/TrafficAi.cs @@ -0,0 +1,58 @@ +using AssettoServer.Network.ClientMessages; +using AssettoServer.Network.Tcp; +using AssettoServer.Server; +using AssettoServer.Server.Configuration; +using AssettoServer.Server.Plugin; +using AssettoServer.Shared.Services; +using AssettoServer.Utils; +using Microsoft.Extensions.Hosting; +using Serilog; +using TrafficAiConfiguration = TrafficAIPlugin.Configuration.TrafficAiConfiguration; + +namespace TrafficAIPlugin; + +public class TrafficAi : CriticalBackgroundService, IAssettoServerAutostart +{ + private readonly TrafficAiConfiguration _configuration; + private readonly ACServerConfiguration _serverConfiguration; + + public TrafficAi(TrafficAiConfiguration configuration, + ACServerConfiguration serverConfiguration, + ACServer server, + CSPClientMessageTypeManager cspClientMessageTypeManager, + IHostApplicationLifetime applicationLifetime) : base(applicationLifetime) + { + _configuration = configuration; + _serverConfiguration = serverConfiguration; + + if (_configuration.EnableCarReset) + { + if (!_serverConfiguration.Extra.EnableClientMessages || _serverConfiguration.CSPTrackOptions.MinimumCSPVersion < CSPVersion.V0_2_3_p47) + { + throw new ConfigurationException( + "Reset car: Minimum required CSP version of 0.2.3-preview47 (2796); Requires enabled client messages; Requires working AI spline"); + } + cspClientMessageTypeManager.RegisterOnlineEvent((client, _) => { OnResetCar(client); }); + } + + + server.Update += MainLoop; + } + + private void OnResetCar(ACTcpClient sender) + { + if (_configuration.EnableCarReset) + sender.EntryCar.TryResetPosition(); + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + Log.Debug("Sample plugin autostart called"); + return Task.CompletedTask; + } + + private void MainLoop(ACServer server, EventArgs args) + { + + } +} diff --git a/TrafficAiPlugin/TrafficAiCommandModule.cs b/TrafficAiPlugin/TrafficAiCommandModule.cs new file mode 100644 index 00000000..b00bfd2e --- /dev/null +++ b/TrafficAiPlugin/TrafficAiCommandModule.cs @@ -0,0 +1,50 @@ +using AssettoServer.Commands; +using AssettoServer.Commands.Attributes; +using AssettoServer.Server; +using AssettoServer.Server.Configuration; +using AssettoServer.Utils; +using JetBrains.Annotations; +using Qmmands; +using TrafficAIPlugin.Configuration; + +namespace TrafficAIPlugin; + +[RequireAdmin] +[UsedImplicitly(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.WithMembers)] +public class TrafficAiCommandModule : ACModuleBase +{ + private readonly ACServerConfiguration _serverConfiguration; + private readonly TrafficAiConfiguration _configuration; + private readonly EntryCarManager _entryCarManager; + + public TrafficAiCommandModule(ACServerConfiguration serverConfiguration, TrafficAiConfiguration configuration, EntryCarManager entryCarManager) + { + _serverConfiguration = serverConfiguration; + _configuration = configuration; + _entryCarManager = entryCarManager; + } + + [Command("setaioverbooking")] + public void SetAiOverbooking(int count) + { + foreach (var aiCar in _entryCarManager.EntryCars.Where(car => car.AiControlled && car.Client == null)) + { + aiCar.SetAiOverbooking(count); + } + Reply($"AI overbooking set to {count}"); + } + + [Command("resetcar"), RequireConnectedPlayer] + public void ResetCarAsync() + { + if (_serverConfiguration.Extra is { EnableClientMessages: true, MinimumCSPVersion: >= CSPVersion.V0_2_3_p47 } && + _configuration.EnableCarReset) + { + Reply(Client!.EntryCar.TryResetPosition() + ? "Position successfully reset" + : "Couldn't reset position"); + } + else + Reply("Reset is not enabled on this server"); + } +} diff --git a/TrafficAiPlugin/TrafficAiModule.cs b/TrafficAiPlugin/TrafficAiModule.cs new file mode 100644 index 00000000..b2ed90a6 --- /dev/null +++ b/TrafficAiPlugin/TrafficAiModule.cs @@ -0,0 +1,92 @@ +using System.Numerics; +using AssettoServer.Server.OpenSlotFilters; +using AssettoServer.Server.Plugin; +using Autofac; +using Microsoft.Extensions.Hosting; +using TrafficAIPlugin.Configuration; +using TrafficAIPlugin.Splines; + +namespace TrafficAIPlugin; + +public class TrafficAiModule : AssettoServerModule +{ + + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().AsSelf().As().SingleInstance(); + + + + builder.RegisterType().AsSelf(); + + builder.RegisterType().AsSelf().As().SingleInstance(); + builder.RegisterType().AsSelf().SingleInstance().AutoActivate(); + builder.RegisterType().As(); + + builder.RegisterType().As().SingleInstance(); + + builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf(); + builder.Register((AiSplineLocator locator) => locator.Locate()).AsSelf().SingleInstance(); + } + + public override object? ReferenceConfiguration => new TrafficAiConfiguration + { + CarSpecificOverrides = + [ + new CarSpecificOverrides + { + Model = "my_car_model", + Acceleration = 2.5f, + Deceleration = 8.5f, + AllowedLanes = [LaneSpawnBehavior.Left, LaneSpawnBehavior.Middle, LaneSpawnBehavior.Right], + MaxOverbooking = 1, + CorneringSpeedFactor = 0.5f, + CorneringBrakeDistanceFactor = 3, + CorneringBrakeForceFactor = 0.5f, + EngineIdleRpm = 800, + EngineMaxRpm = 3000, + MaxLaneCount = 2, + MinLaneCount = 1, + TyreDiameterMeters = 0.8f, + SplineHeightOffsetMeters = 0, + VehicleLengthPostMeters = 2, + VehicleLengthPreMeters = 2, + MinAiSafetyDistanceMeters = 20, + MaxAiSafetyDistanceMeters = 25, + MinCollisionStopTimeSeconds = 0, + MaxCollisionStopTimeSeconds = 0, + MinSpawnProtectionTimeSeconds = 30, + MaxSpawnProtectionTimeSeconds = 60 + } + ], + LaneCountSpecificOverrides = new Dictionary + { + { + 1, + new LaneCountSpecificOverrides + { + MinAiSafetyDistanceMeters = 50, + MaxAiSafetyDistanceMeters = 100 + } + }, + { + 2, + new LaneCountSpecificOverrides + { + MinAiSafetyDistanceMeters = 40, + MaxAiSafetyDistanceMeters = 80 + } + } + }, + IgnorePlayerObstacleSpheres = + [ + new Sphere + { + Center = new Vector3(0, 0, 0), + RadiusMeters = 50 + } + ] + }; +} diff --git a/TrafficAiPlugin/TrafficAiPlugin.csproj b/TrafficAiPlugin/TrafficAiPlugin.csproj new file mode 100644 index 00000000..7db1fff3 --- /dev/null +++ b/TrafficAiPlugin/TrafficAiPlugin.csproj @@ -0,0 +1,31 @@ + + + + net9.0 + enable + enable + true + false + embedded + ..\out-$(RuntimeIdentifier)\plugins\$(MSBuildProjectName)\ + $(MSBuildProjectDirectory)=$(MSBuildProjectName) + TrafficAIPlugin + + + + false + ..\AssettoServer\bin\$(Configuration)\$(TargetFramework)\plugins\$(MSBuildProjectName) + + + + + false + runtime + + + false + runtime + + + + diff --git a/AssettoServer/Server/Ai/ai_debug.lua b/TrafficAiPlugin/ai_debug.lua similarity index 100% rename from AssettoServer/Server/Ai/ai_debug.lua rename to TrafficAiPlugin/ai_debug.lua diff --git a/TrafficAiPlugin/lua/resetcar.lua b/TrafficAiPlugin/lua/resetcar.lua new file mode 100644 index 00000000..796274cb --- /dev/null +++ b/TrafficAiPlugin/lua/resetcar.lua @@ -0,0 +1,10 @@ +local requestResetCarEvent = ac.OnlineEvent({ + ac.StructItem.key("AS_RequestResetCar"), + dummy = ac.StructItem.byte(), +}, function (sender, message) + if sender ~= nil then return end + ac.debug("request_reset_car", message.dummy) +end) + +local resetCarControl = ac.ControlButton('__EXT_CMD_RESET', nil) +resetCarControl:onPressed(function() requestResetCarEvent({dummy=0}) end) From b1caa9136cd8ffe3b45de80862ea16a1428c880b Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:39:21 +0100 Subject: [PATCH 02/10] it runs --- AssettoServer/AssettoServer.csproj | 2 - AssettoServer/Network/Tcp/ACTcpClient.cs | 15 +- AssettoServer/Server/ACServer.cs | 33 +- .../Configuration/ACServerConfiguration.cs | 21 - .../Server/Configuration/Kunos/EntryList.cs | 2 +- AssettoServer/Server/EntryCar.cs | 122 ++---- AssettoServer/Server/EntryCarAi.cs | 363 ------------------ AssettoServer/Server/EntryCarManager.cs | 4 - AutoModerationPlugin/AutoModerationPlugin.cs | 2 +- .../AutoModerationPlugin.csproj | 1 + .../EntryCarAutoModeration.cs | 7 +- ReplayPlugin/Data/ReplayFrameState.cs | 4 +- ReplayPlugin/ReplayPlugin.cs | 15 +- ReplayPlugin/ReplayPlugin.csproj | 1 + TrafficAiPlugin/AiBehavior.cs | 48 ++- TrafficAiPlugin/AiSlotFilter.cs | 2 +- TrafficAiPlugin/AiState.cs | 78 ++-- TrafficAiPlugin/AiUpdater.cs | 26 -- .../Configuration/TrafficAIConfiguration.cs | 16 +- TrafficAiPlugin/EntryCarTrafficAi.cs | 294 ++++++++++---- TrafficAiPlugin/Splines/FastLaneParser.cs | 12 +- TrafficAiPlugin/Splines/MutableAiSpline.cs | 1 + TrafficAiPlugin/TrafficAi.cs | 64 ++- TrafficAiPlugin/TrafficAiCommandModule.cs | 13 +- TrafficAiPlugin/TrafficAiModule.cs | 5 +- TrafficAiPlugin/TrafficAiPlugin.csproj | 8 +- TrafficAiPlugin/TrafficAiUpdater.cs | 100 +++++ TrafficAiPlugin/{ => lua}/ai_debug.lua | 0 28 files changed, 549 insertions(+), 710 deletions(-) delete mode 100644 AssettoServer/Server/EntryCarAi.cs delete mode 100644 TrafficAiPlugin/AiUpdater.cs create mode 100644 TrafficAiPlugin/TrafficAiUpdater.cs rename TrafficAiPlugin/{ => lua}/ai_debug.lua (100%) diff --git a/AssettoServer/AssettoServer.csproj b/AssettoServer/AssettoServer.csproj index da53c377..b5ca17a2 100644 --- a/AssettoServer/AssettoServer.csproj +++ b/AssettoServer/AssettoServer.csproj @@ -130,8 +130,6 @@ - - diff --git a/AssettoServer/Network/Tcp/ACTcpClient.cs b/AssettoServer/Network/Tcp/ACTcpClient.cs index 7cf21ca6..d35474ec 100644 --- a/AssettoServer/Network/Tcp/ACTcpClient.cs +++ b/AssettoServer/Network/Tcp/ACTcpClient.cs @@ -63,7 +63,8 @@ public class ACTcpClient : IClient public InputMethod InputMethod { get; set; } internal SocketAddress? UdpEndpoint { get; private set; } - internal bool SupportsCSPCustomUpdate { get; private set; } + public bool HasUdpEndpoint => UdpEndpoint != null; + public bool SupportsCSPCustomUpdate { get; private set; } public int? CSPVersion { get; private set; } internal string ApiKey { get; } @@ -413,9 +414,6 @@ private async Task ReceiveLoopAsync() CSPVersion = cspVersion; } - // Gracefully despawn AI cars - EntryCar.SetAiOverbooking(0); - if (_configuration.Server.CheckAdminPassword(handshakeRequest.Password)) IsAdministrator = true; @@ -845,15 +843,6 @@ private async Task SendFirstUpdateAsync() batched.Packets.Add(new P2PUpdate { SessionId = car.SessionId, P2PCount = car.Status.P2PCount }); batched.Packets.Add(new BallastUpdate { SessionId = car.SessionId, BallastKg = car.Ballast, Restrictor = car.Restrictor }); - - if (_configuration.Extra.AiParams.HideAiCars) - { - batched.Packets.Add(new CSPCarVisibilityUpdate - { - SessionId = car.SessionId, - Visible = car.AiControlled ? CSPCarVisibility.Invisible : CSPCarVisibility.Visible - }); - } } if (EntryCar.FixedSetup != null diff --git a/AssettoServer/Server/ACServer.cs b/AssettoServer/Server/ACServer.cs index dcc4ab14..e8557702 100644 --- a/AssettoServer/Server/ACServer.cs +++ b/AssettoServer/Server/ACServer.cs @@ -207,26 +207,25 @@ private void MainLoop(CancellationToken stoppingToken) } } - if (fromCar.AiControlled || fromCar.HasUpdateToSend) + if (!fromCar.HasUpdateToSend) continue; + + fromCar.HasUpdateToSend = false; + + for (int j = 0; j < _entryCarManager.EntryCars.Length; j++) { - fromCar.HasUpdateToSend = false; + var toCar = _entryCarManager.EntryCars[j]; + var toClient = toCar.Client; + if (toCar == fromCar + || toClient == null || !toClient.HasSentFirstUpdate || !toClient.HasUdpEndpoint + || !fromCar.GetPositionUpdateForCar(toCar, out var update)) continue; - for (int j = 0; j < _entryCarManager.EntryCars.Length; j++) + if (toClient.SupportsCSPCustomUpdate) { - var toCar = _entryCarManager.EntryCars[j]; - var toClient = toCar.Client; - if (toCar == fromCar - || toClient == null || !toClient.HasSentFirstUpdate || toClient.UdpEndpoint == null - || !fromCar.GetPositionUpdateForCar(toCar, out var update)) continue; - - if (toClient.SupportsCSPCustomUpdate || fromCar.AiControlled) - { - positionUpdates[toCar].Add(update); - } - else - { - toClient.SendPacketUdp(in update); - } + positionUpdates[toCar].Add(update); + } + else + { + toClient.SendPacketUdp(in update); } } } diff --git a/AssettoServer/Server/Configuration/ACServerConfiguration.cs b/AssettoServer/Server/Configuration/ACServerConfiguration.cs index d4105415..3897c310 100644 --- a/AssettoServer/Server/Configuration/ACServerConfiguration.cs +++ b/AssettoServer/Server/Configuration/ACServerConfiguration.cs @@ -267,27 +267,6 @@ private void ApplyConfigurationFixes() Server.MaxClients = EntryList.Cars.Count; } - if (Extra is { EnableAi: true, AiParams.AutoAssignTrafficCars: true }) - { - foreach (var entry in EntryList.Cars) - { - if (entry.Model.Contains("traffic")) - { - entry.AiMode = AiMode.Fixed; - } - } - } - - if (Extra.AiParams.AiPerPlayerTargetCount == 0) - { - Extra.AiParams.AiPerPlayerTargetCount = EntryList.Cars.Count(c => c.AiMode != AiMode.None); - } - - if (Extra.AiParams.MaxAiTargetCount == 0) - { - Extra.AiParams.MaxAiTargetCount = EntryList.Cars.Count(c => c.AiMode != AiMode.Fixed) * Extra.AiParams.AiPerPlayerTargetCount; - } - var filteredServerName = ServerDetailsIdRegex().Replace(Server.Name, ""); if (filteredServerName != Server.Name) { diff --git a/AssettoServer/Server/Configuration/Kunos/EntryList.cs b/AssettoServer/Server/Configuration/Kunos/EntryList.cs index 74bdaf3a..aa06d153 100644 --- a/AssettoServer/Server/Configuration/Kunos/EntryList.cs +++ b/AssettoServer/Server/Configuration/Kunos/EntryList.cs @@ -24,7 +24,7 @@ public class Entry [IniField("TEAM")] public string? Team { get; init; } [IniField("FIXED_SETUP")] public string? FixedSetup { get; init; } = null; [IniField("GUID")] public string Guid { get; init; } = ""; - [IniField("AI")] public AiMode AiMode { get; internal set; } = AiMode.None; + [IniField("AI")] public AiMode AiMode { get; set; } = AiMode.None; [IniField("LEGAL_TYRES")] public string? LegalTyres { get; init; } } diff --git a/AssettoServer/Server/EntryCar.cs b/AssettoServer/Server/EntryCar.cs index c5c3e93a..8db76c86 100644 --- a/AssettoServer/Server/EntryCar.cs +++ b/AssettoServer/Server/EntryCar.cs @@ -3,11 +3,7 @@ using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; -using AssettoServer.Network.ClientMessages; -using AssettoServer.Server.Ai; -using AssettoServer.Server.Ai.Splines; using AssettoServer.Server.Configuration; -using AssettoServer.Server.Configuration.Extra; using AssettoServer.Shared.Model; using AssettoServer.Shared.Network.Packets.Incoming; using AssettoServer.Shared.Network.Packets.Outgoing; @@ -18,6 +14,13 @@ namespace AssettoServer.Server; +public enum AiMode +{ + None, + Auto, + Fixed +} + public partial class EntryCar : IEntryCar { public ACTcpClient? Client { get; internal set; } @@ -48,10 +51,14 @@ public partial class EntryCar : IEntryCar public float NetworkDistanceSquared { get; internal set; } public int OutsideNetworkBubbleUpdateRateMs { get; internal set; } - internal long[] OtherCarsLastSentUpdateTime { get; } - internal EntryCar? TargetCar { get; set; } + public long[] OtherCarsLastSentUpdateTime { get; } + public EntryCar? TargetCar { get; internal set; } private long LastFallCheckTime{ get; set; } + public bool AiControlled { get; set; } = false; + public AiMode AiMode { get; set; } = AiMode.None; + public string? AiName { get; set; } + /// /// Fires when a position update is received. /// @@ -87,7 +94,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) } } - public EntryCar(string model, string? skin, byte sessionId, Func aiStateFactory, SessionManager sessionManager, ACServerConfiguration configuration, EntryCarManager entryCarManager, AiSpline? spline = null) + public EntryCar(string model, string? skin, byte sessionId, SessionManager sessionManager, ACServerConfiguration configuration, EntryCarManager entryCarManager) { Model = model; Skin = skin ?? ""; @@ -95,21 +102,13 @@ public EntryCar(string model, string? skin, byte sessionId, Func NetworkDistanceSquared) @@ -270,7 +228,7 @@ public bool GetPositionUpdateForCar(EntryCar toCar, out PositionUpdateOut positi } positionUpdateOut = new PositionUpdateOut(SessionId, - AiControlled ? AiPakSequenceIds[toCar.SessionId]++ : status.PakSequenceId, + status.PakSequenceId, (uint)(status.Timestamp - toCar.TimeOffset), Ping, status.Position, @@ -297,40 +255,4 @@ public bool IsInRange(EntryCar target, float range) var targetPosition = target.TargetCar != null ? target.TargetCar.Status.Position : target.Status.Position; return Vector3.DistanceSquared(Status.Position, targetPosition) < range * range; } - - public bool TryResetPosition() - { - if (_spline == null) - { - Logger.Information("Failed reset position for {Player} ({SessionId})",Client?.Name, Client?.SessionId); - return false; - } - - if (_sessionManager.ServerTimeMilliseconds < _sessionManager.CurrentSession.StartTimeMilliseconds + 20_000 - || (_sessionManager.ServerTimeMilliseconds > _sessionManager.CurrentSession.EndTimeMilliseconds - && _sessionManager.CurrentSession.EndTimeMilliseconds > 0)) - return false; - - var (splinePointId, _) = _spline.WorldToSpline(Status.Position); - - var splinePoint = _spline.Points[splinePointId]; - - var position = splinePoint.Position; - var direction = - _spline.Operations.GetForwardVector(splinePoint.NextId); - - Client?.SendCollisionUpdatePacket(false); - - _ = Task.Run(async () => - { - await Task.Delay(500); - - Client?.SendTeleportCarPacket(position, direction); - await Task.Delay(10000); - - Client?.SendCollisionUpdatePacket(true); - }); - - Logger.Information("Reset position for {Player} ({SessionId})",Client?.Name, Client?.SessionId); - return true; - } } diff --git a/AssettoServer/Server/EntryCarAi.cs b/AssettoServer/Server/EntryCarAi.cs deleted file mode 100644 index f2f1675c..00000000 --- a/AssettoServer/Server/EntryCarAi.cs +++ /dev/null @@ -1,363 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Numerics; -using System.Runtime.InteropServices; -using AssettoServer.Server.Ai; -using AssettoServer.Server.Ai.Splines; -using AssettoServer.Server.Configuration.Extra; -using AssettoServer.Shared.Model; -using AssettoServer.Shared.Network.Packets.Outgoing; - -namespace AssettoServer.Server; - -public enum AiMode -{ - None, - Auto, - Fixed -} - -public partial class EntryCar -{ - public bool AiControlled { get; set; } - public AiMode AiMode { get; set; } - public int TargetAiStateCount { get; private set; } = 1; - public byte[] LastSeenAiSpawn { get; } - public byte[] AiPakSequenceIds { get; } - public AiState?[] LastSeenAiState { get; } - public string? AiName { get; private set; } - public bool AiEnableColorChanges { get; set; } = false; - public int AiIdleEngineRpm { get; set; } = 800; - public int AiMaxEngineRpm { get; set; } = 3000; - public float AiAcceleration { get; set; } - public float AiDeceleration { get; set; } - public float AiCorneringSpeedFactor { get; set; } - public float AiCorneringBrakeDistanceFactor { get; set; } - public float AiCorneringBrakeForceFactor { get; set; } - public float AiSplineHeightOffsetMeters { get; set; } - public int? AiMaxOverbooking { get; set; } - public int AiMinSpawnProtectionTimeMilliseconds { get; set; } - public int AiMaxSpawnProtectionTimeMilliseconds { get; set; } - public int? MinLaneCount { get; set; } - public int? MaxLaneCount { get; set; } - public int AiMinCollisionStopTimeMilliseconds { get; set; } - public int AiMaxCollisionStopTimeMilliseconds { get; set; } - public float VehicleLengthPreMeters { get; set; } - public float VehicleLengthPostMeters { get; set; } - public int? MinAiSafetyDistanceMetersSquared { get; set; } - public int? MaxAiSafetyDistanceMetersSquared { get; set; } - public List? AiAllowedLanes { get; set; } - public float TyreDiameterMeters { get; set; } - private readonly List _aiStates = []; - private Span AiStatesSpan => CollectionsMarshal.AsSpan(_aiStates); - - private readonly Func _aiStateFactory; - private readonly AiSpline? _spline; - - private void AiInit() - { - AiName = $"{_configuration.Extra.AiParams.NamePrefix} {SessionId}"; - SetAiOverbooking(0); - - _configuration.Extra.AiParams.PropertyChanged += OnConfigReload; - OnConfigReload(_configuration, new PropertyChangedEventArgs(string.Empty)); - } - - private void OnConfigReload(object? sender, PropertyChangedEventArgs args) - { - AiSplineHeightOffsetMeters = _configuration.Extra.AiParams.SplineHeightOffsetMeters; - AiAcceleration = _configuration.Extra.AiParams.DefaultAcceleration; - AiDeceleration = _configuration.Extra.AiParams.DefaultDeceleration; - AiCorneringSpeedFactor = _configuration.Extra.AiParams.CorneringSpeedFactor; - AiCorneringBrakeDistanceFactor = _configuration.Extra.AiParams.CorneringBrakeDistanceFactor; - AiCorneringBrakeForceFactor = _configuration.Extra.AiParams.CorneringBrakeForceFactor; - TyreDiameterMeters = _configuration.Extra.AiParams.TyreDiameterMeters; - AiMinSpawnProtectionTimeMilliseconds = _configuration.Extra.AiParams.MinSpawnProtectionTimeMilliseconds; - AiMaxSpawnProtectionTimeMilliseconds = _configuration.Extra.AiParams.MaxSpawnProtectionTimeMilliseconds; - AiMinCollisionStopTimeMilliseconds = _configuration.Extra.AiParams.MinCollisionStopTimeMilliseconds; - AiMaxCollisionStopTimeMilliseconds = _configuration.Extra.AiParams.MaxCollisionStopTimeMilliseconds; - - foreach (var carOverrides in _configuration.Extra.AiParams.CarSpecificOverrides) - { - if (carOverrides.Model == Model) - { - if (carOverrides.SplineHeightOffsetMeters.HasValue) - AiSplineHeightOffsetMeters = carOverrides.SplineHeightOffsetMeters.Value; - if (carOverrides.EngineIdleRpm.HasValue) - AiIdleEngineRpm = carOverrides.EngineIdleRpm.Value; - if (carOverrides.EngineMaxRpm.HasValue) - AiMaxEngineRpm = carOverrides.EngineMaxRpm.Value; - if (carOverrides.Acceleration.HasValue) - AiAcceleration = carOverrides.Acceleration.Value; - if (carOverrides.Deceleration.HasValue) - AiDeceleration = carOverrides.Deceleration.Value; - if (carOverrides.CorneringSpeedFactor.HasValue) - AiCorneringSpeedFactor = carOverrides.CorneringSpeedFactor.Value; - if (carOverrides.CorneringBrakeDistanceFactor.HasValue) - AiCorneringBrakeDistanceFactor = carOverrides.CorneringBrakeDistanceFactor.Value; - if (carOverrides.CorneringBrakeForceFactor.HasValue) - AiCorneringBrakeForceFactor = carOverrides.CorneringBrakeForceFactor.Value; - if (carOverrides.TyreDiameterMeters.HasValue) - TyreDiameterMeters = carOverrides.TyreDiameterMeters.Value; - if (carOverrides.MaxOverbooking.HasValue) - AiMaxOverbooking = carOverrides.MaxOverbooking.Value; - if (carOverrides.MinSpawnProtectionTimeMilliseconds.HasValue) - AiMinSpawnProtectionTimeMilliseconds = carOverrides.MinSpawnProtectionTimeMilliseconds.Value; - if (carOverrides.MaxSpawnProtectionTimeMilliseconds.HasValue) - AiMaxSpawnProtectionTimeMilliseconds = carOverrides.MaxSpawnProtectionTimeMilliseconds.Value; - if (carOverrides.MinCollisionStopTimeMilliseconds.HasValue) - AiMinCollisionStopTimeMilliseconds = carOverrides.MinCollisionStopTimeMilliseconds.Value; - if (carOverrides.MaxCollisionStopTimeMilliseconds.HasValue) - AiMaxCollisionStopTimeMilliseconds = carOverrides.MaxCollisionStopTimeMilliseconds.Value; - if (carOverrides.VehicleLengthPreMeters.HasValue) - VehicleLengthPreMeters = carOverrides.VehicleLengthPreMeters.Value; - if (carOverrides.VehicleLengthPostMeters.HasValue) - VehicleLengthPostMeters = carOverrides.VehicleLengthPostMeters.Value; - - AiAllowedLanes = carOverrides.AllowedLanes; - MinAiSafetyDistanceMetersSquared = carOverrides.MinAiSafetyDistanceMetersSquared; - MaxAiSafetyDistanceMetersSquared = carOverrides.MaxAiSafetyDistanceMetersSquared; - MinLaneCount = carOverrides.MinLaneCount; - MaxLaneCount = carOverrides.MaxLaneCount; - } - } - } - - public void RemoveUnsafeStates() - { - foreach (var aiState in AiStatesSpan) - { - if (!aiState.Initialized) continue; - - foreach (var targetAiState in AiStatesSpan) - { - if (aiState != targetAiState - && targetAiState.Initialized - && Vector3.DistanceSquared(aiState.Status.Position, targetAiState.Status.Position) < _configuration.Extra.AiParams.MinStateDistanceSquared - && (_configuration.Extra.AiParams.TwoWayTraffic || Vector3.Dot(aiState.Status.Velocity, targetAiState.Status.Velocity) > 0)) - { - aiState.Despawn(); - Logger.Verbose("Removed close state from AI {SessionId}", SessionId); - } - } - } - } - - public void AiUpdate() - { - foreach (var aiState in AiStatesSpan) - { - aiState.Update(); - } - } - - public void AiObstacleDetection() - { - foreach (var aiState in AiStatesSpan) - { - aiState.DetectObstacles(); - } - } - - public AiState? GetBestStateForPlayer(CarStatus playerStatus) - { - AiState? bestState = null; - float minDistance = float.MaxValue; - - foreach (var aiState in AiStatesSpan) - { - if (!aiState.Initialized) continue; - - float distance = Vector3.DistanceSquared(aiState.Status.Position, playerStatus.Position); - - if (_configuration.Extra.AiParams.TwoWayTraffic) - { - if (distance < minDistance) - { - bestState = aiState; - minDistance = distance; - } - } - else - { - bool isBestSameDirection = bestState != null && Vector3.Dot(bestState.Status.Velocity, playerStatus.Velocity) > 0; - bool isCandidateSameDirection = Vector3.Dot(aiState.Status.Velocity, playerStatus.Velocity) > 0; - bool isPlayerFastEnough = playerStatus.Velocity.LengthSquared() > 1; - bool isTieBreaker = minDistance < _configuration.Extra.AiParams.MinStateDistanceSquared && - distance < _configuration.Extra.AiParams.MinStateDistanceSquared && - isPlayerFastEnough; - - // Tie breaker: Multiple close states, so take the one with min distance and same direction - if ((isTieBreaker && isCandidateSameDirection && (distance < minDistance || !isBestSameDirection)) - || (!isTieBreaker && distance < minDistance)) - { - bestState = aiState; - minDistance = distance; - } - } - } - - return bestState; - } - - public bool IsPositionSafe(int pointId) - { - ArgumentNullException.ThrowIfNull(_spline); - - var ops = _spline.Operations; - - foreach (var aiState in AiStatesSpan) - { - if (aiState.Initialized - && Vector3.DistanceSquared(aiState.Status.Position, ops.Points[pointId].Position) < aiState.SafetyDistanceSquared - && ops.IsSameDirection(aiState.CurrentSplinePointId, pointId)) - { - return false; - } - } - - return true; - } - - public (AiState? AiState, float DistanceSquared) GetClosestAiState(Vector3 position) - { - AiState? closestState = null; - float minDistanceSquared = float.MaxValue; - - foreach (var aiState in AiStatesSpan) - { - float distanceSquared = Vector3.DistanceSquared(position, aiState.Status.Position); - if (distanceSquared < minDistanceSquared) - { - closestState = aiState; - minDistanceSquared = distanceSquared; - } - } - - return (closestState, minDistanceSquared); - } - - public void GetInitializedStates(List initializedStates, List? uninitializedStates = null) - { - foreach (var aiState in AiStatesSpan) - { - if (aiState.Initialized) - { - initializedStates.Add(aiState); - } - else - { - uninitializedStates?.Add(aiState); - } - } - } - - public bool CanSpawnAiState(Vector3 spawnPoint, AiState aiState) - { - // Remove state if AI slot overbooking was reduced - if (_aiStates.IndexOf(aiState) >= TargetAiStateCount) - { - aiState.Dispose(); - _aiStates.Remove(aiState); - - Logger.Verbose("Removed state of Traffic {SessionId} due to overbooking reduction", SessionId); - - if (_aiStates.Count == 0) - { - Logger.Verbose("Traffic {SessionId} has no states left, disconnecting", SessionId); - _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = SessionId }); - } - - return false; - } - - foreach (var state in AiStatesSpan) - { - if (state == aiState || !state.Initialized) continue; - - if (Vector3.DistanceSquared(spawnPoint, state.Status.Position) < _configuration.Extra.AiParams.StateSpawnDistanceSquared) - { - return false; - } - } - - return true; - } - - public void SetAiControl(bool aiControlled) - { - if (AiControlled != aiControlled) - { - AiControlled = aiControlled; - - if (AiControlled) - { - Logger.Debug("Slot {SessionId} is now controlled by AI", SessionId); - - AiReset(); - _entryCarManager.BroadcastPacket(new CarConnected - { - SessionId = SessionId, - Name = AiName - }); - if (_configuration.Extra.AiParams.HideAiCars) - { - _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate - { - SessionId = SessionId, - Visible = CSPCarVisibility.Invisible - }); - } - } - else - { - Logger.Debug("Slot {SessionId} is no longer controlled by AI", SessionId); - if (_aiStates.Count > 0) - { - _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = SessionId }); - } - - if (_configuration.Extra.AiParams.HideAiCars) - { - _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate - { - SessionId = SessionId, - Visible = CSPCarVisibility.Visible - }); - } - - AiReset(); - } - } - } - - public void SetAiOverbooking(int count) - { - if (AiMaxOverbooking.HasValue) - { - count = Math.Min(count, AiMaxOverbooking.Value); - } - - if (count > _aiStates.Count) - { - int newAis = count - _aiStates.Count; - for (int i = 0; i < newAis; i++) - { - _aiStates.Add(_aiStateFactory(this)); - } - } - - TargetAiStateCount = count; - } - - private void AiReset() - { - foreach (var state in AiStatesSpan) - { - state.Despawn(); - } - _aiStates.Clear(); - _aiStates.Add(_aiStateFactory(this)); - } -} diff --git a/AssettoServer/Server/EntryCarManager.cs b/AssettoServer/Server/EntryCarManager.cs index 3c512808..24b27a3d 100644 --- a/AssettoServer/Server/EntryCarManager.cs +++ b/AssettoServer/Server/EntryCarManager.cs @@ -242,7 +242,6 @@ internal void Initialize() { var entry = _configuration.EntryList.Cars[i]; var driverOptions = CSPDriverOptions.Parse(entry.Skin); - var aiMode = _configuration.Extra.EnableAi ? entry.AiMode : AiMode.None; var car = _entryCarFactory(entry.Model, entry.Skin, (byte)i); car.SpectatorMode = entry.SpectatorMode; @@ -250,9 +249,6 @@ internal void Initialize() car.Restrictor = entry.Restrictor; car.FixedSetup = entry.FixedSetup; car.DriverOptionsFlags = driverOptions; - car.AiMode = aiMode; - car.AiEnableColorChanges = driverOptions.HasFlag(DriverOptionsFlags.AllowColorChange); - car.AiControlled = aiMode != AiMode.None; car.NetworkDistanceSquared = MathF.Pow(_configuration.Extra.NetworkBubbleDistance, 2); car.OutsideNetworkBubbleUpdateRateMs = 1000 / _configuration.Extra.OutsideNetworkBubbleRefreshRateHz; car.LegalTyres = entry.LegalTyres ?? _configuration.Server.LegalTyres; diff --git a/AutoModerationPlugin/AutoModerationPlugin.cs b/AutoModerationPlugin/AutoModerationPlugin.cs index f61e525c..352257ae 100644 --- a/AutoModerationPlugin/AutoModerationPlugin.cs +++ b/AutoModerationPlugin/AutoModerationPlugin.cs @@ -1,7 +1,6 @@ using System.Reflection; using AssettoServer.Network.Tcp; using AssettoServer.Server; -using AssettoServer.Server.Ai.Splines; using AssettoServer.Server.Configuration; using AssettoServer.Server.Plugin; using AssettoServer.Server.Weather; @@ -9,6 +8,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Hosting; using Serilog; +using TrafficAIPlugin.Splines; namespace AutoModerationPlugin; diff --git a/AutoModerationPlugin/AutoModerationPlugin.csproj b/AutoModerationPlugin/AutoModerationPlugin.csproj index 02b5c4f6..398db292 100644 --- a/AutoModerationPlugin/AutoModerationPlugin.csproj +++ b/AutoModerationPlugin/AutoModerationPlugin.csproj @@ -25,6 +25,7 @@ false runtime + diff --git a/AutoModerationPlugin/EntryCarAutoModeration.cs b/AutoModerationPlugin/EntryCarAutoModeration.cs index dc37e102..e7ded132 100644 --- a/AutoModerationPlugin/EntryCarAutoModeration.cs +++ b/AutoModerationPlugin/EntryCarAutoModeration.cs @@ -1,7 +1,6 @@ using System.Numerics; using AssettoServer.Network.Tcp; using AssettoServer.Server; -using AssettoServer.Server.Ai.Splines; using AssettoServer.Server.Configuration; using AssettoServer.Server.Weather; using AssettoServer.Shared.Network.Packets.Incoming; @@ -9,6 +8,8 @@ using AssettoServer.Shared.Network.Packets.Shared; using AutoModerationPlugin.Packets; using Serilog; +using TrafficAIPlugin.Configuration; +using TrafficAIPlugin.Splines; namespace AutoModerationPlugin; @@ -56,6 +57,7 @@ public EntryCarAutoModeration(EntryCar entryCar, WeatherManager weatherManager, SessionManager sessionManager, ACServerConfiguration serverConfiguration, + TrafficAiConfiguration? trafficAiConfiguration = null, AiSpline? aiSpline = null) { _entryCar = entryCar; @@ -71,7 +73,8 @@ public EntryCarAutoModeration(EntryCar entryCar, _entryCar.PositionUpdateReceived += OnPositionUpdateReceived; } - _laneRadiusSquared = MathF.Pow(_serverConfiguration.Extra.AiParams.LaneWidthMeters / 2.0f * 1.25f, 2); + if (trafficAiConfiguration != null) + _laneRadiusSquared = MathF.Pow(trafficAiConfiguration.LaneWidthMeters / 2.0f * 1.25f, 2); } private void OnPositionUpdateReceived(EntryCar sender, in PositionUpdateIn positionUpdate) diff --git a/ReplayPlugin/Data/ReplayFrameState.cs b/ReplayPlugin/Data/ReplayFrameState.cs index 4a608423..33e924af 100644 --- a/ReplayPlugin/Data/ReplayFrameState.cs +++ b/ReplayPlugin/Data/ReplayFrameState.cs @@ -1,6 +1,6 @@ -using AssettoServer.Server.Ai; -using AssettoServer.Shared.Model; +using AssettoServer.Shared.Model; using Microsoft.Extensions.ObjectPool; +using TrafficAIPlugin; namespace ReplayPlugin.Data; diff --git a/ReplayPlugin/ReplayPlugin.cs b/ReplayPlugin/ReplayPlugin.cs index 1d805298..34d247bb 100644 --- a/ReplayPlugin/ReplayPlugin.cs +++ b/ReplayPlugin/ReplayPlugin.cs @@ -12,6 +12,7 @@ using ReplayPlugin.Data; using ReplayPlugin.Packets; using Serilog; +using TrafficAIPlugin; namespace ReplayPlugin; @@ -24,6 +25,7 @@ public class ReplayPlugin : CriticalBackgroundService, IAssettoServerAutostart private readonly ReplayManager _replayManager; private readonly Summary _onUpdateTimer; private readonly EntryCarExtraDataManager _extraData; + private readonly TrafficAi? _trafficAi; public ReplayPlugin(IHostApplicationLifetime applicationLifetime, EntryCarManager entryCarManager, @@ -33,7 +35,8 @@ public ReplayPlugin(IHostApplicationLifetime applicationLifetime, ReplayConfiguration configuration, CSPServerScriptProvider scriptProvider, CSPClientMessageTypeManager cspClientMessageTypeManager, - EntryCarExtraDataManager extraData) : base(applicationLifetime) + EntryCarExtraDataManager extraData, + TrafficAi? trafficAi = null) : base(applicationLifetime) { _entryCarManager = entryCarManager; _weather = weather; @@ -41,6 +44,7 @@ public ReplayPlugin(IHostApplicationLifetime applicationLifetime, _replayManager = replayManager; _configuration = configuration; _extraData = extraData; + _trafficAi = trafficAi; _onUpdateTimer = Metrics.CreateSummary("assettoserver_replayplugin_onupdate", "ReplayPlugin.OnUpdate Duration", MetricDefaults.DefaultQuantiles); @@ -75,16 +79,19 @@ private void Update() } else if (entryCar.AiControlled) { - for (int i = 0; i < entryCar.LastSeenAiState.Length; i++) + if (_trafficAi == null) continue; + + var entryCarAi = _trafficAi.GetAiCarBySessionId(entryCar.SessionId); + for (int i = 0; i < entryCarAi.LastSeenAiState.Length; i++) { - var aiState = entryCar.LastSeenAiState[i]; + var aiState = entryCarAi.LastSeenAiState[i]; if (aiState == null) continue; if (!_state.AiStateMapping.TryGetValue(aiState, out var aiStateId)) { aiStateId = (short)_state.AiCars.Count; _state.AiStateMapping.Add(aiState, aiStateId); - _state.AiCars.Add((aiState.EntryCar.SessionId, aiState.Status)); + _state.AiCars.Add((aiState.EntryCarAi.EntryCar.SessionId, aiState.Status)); } if (_state.AiFrameMapping.TryGetValue((byte)i, out var aiFrameMappingList)) diff --git a/ReplayPlugin/ReplayPlugin.csproj b/ReplayPlugin/ReplayPlugin.csproj index 5e0e84ce..90e4c3ff 100644 --- a/ReplayPlugin/ReplayPlugin.csproj +++ b/ReplayPlugin/ReplayPlugin.csproj @@ -25,6 +25,7 @@ false runtime + diff --git a/TrafficAiPlugin/AiBehavior.cs b/TrafficAiPlugin/AiBehavior.cs index 3cdde0f5..e06641e0 100644 --- a/TrafficAiPlugin/AiBehavior.cs +++ b/TrafficAiPlugin/AiBehavior.cs @@ -22,6 +22,7 @@ public class AiBehavior : CriticalBackgroundService, IAssettoServerAutostart private readonly TrafficAiConfiguration _configuration; private readonly SessionManager _sessionManager; private readonly EntryCarManager _entryCarManager; + private readonly TrafficAi _trafficAi; private readonly AiSpline _spline; private readonly HttpInfoCache _httpInfoCache; //private readonly EntryCar.Factory _entryCarFactory; @@ -39,13 +40,16 @@ public AiBehavior(SessionManager sessionManager, EntryCarManager entryCarManager, IHostApplicationLifetime applicationLifetime, //EntryCar.Factory entryCarFactory, - CSPServerScriptProvider serverScriptProvider, - AiSpline spline, HttpInfoCache httpInfoCache) : base(applicationLifetime) + CSPServerScriptProvider serverScriptProvider, + TrafficAi trafficAi, + AiSpline spline, + HttpInfoCache httpInfoCache) : base(applicationLifetime) { _sessionManager = sessionManager; _serverConfiguration = serverConfiguration; _configuration = configuration; _entryCarManager = entryCarManager; + _trafficAi = trafficAi; _spline = spline; _httpInfoCache = httpInfoCache; _junctionEvaluator = new JunctionEvaluator(spline, false); @@ -53,9 +57,11 @@ public AiBehavior(SessionManager sessionManager, if (_configuration.Debug) { - using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("AssettoServer.Server.Ai.ai_debug.lua")!; - serverScriptProvider.AddScript(stream, "ai_debug.lua"); + using var aiDebugStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TrafficAiPlugin.lua.ai_debug.lua")!; + serverScriptProvider.AddScript(aiDebugStream, "ai_debug.lua"); } + using var resetCarStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("TrafficAiPlugin.lua.resetcar.lua")!; + serverScriptProvider.AddScript(resetCarStream, "resetcar.lua"); _updateDurationTimer = Metrics.CreateSummary("assettoserver_aibehavior_update", "AiBehavior.Update Duration", MetricDefaults.DefaultQuantiles); _obstacleDetectionDurationTimer = Metrics.CreateSummary("assettoserver_aibehavior_obstacledetection", "AiBehavior.ObstacleDetection Duration", MetricDefaults.DefaultQuantiles); @@ -72,11 +78,12 @@ public AiBehavior(SessionManager sessionManager, _sessionManager.SessionChanged += OnSessionChanged; } - private static void OnCollision(ACTcpClient sender, CollisionEventArgs args) + private void OnCollision(ACTcpClient sender, CollisionEventArgs args) { if (args.TargetCar?.AiControlled == true) { - var targetAiState = args.TargetCar.GetClosestAiState(sender.EntryCar.Status.Position); + var target = _trafficAi.GetAiCarBySessionId(args.TargetCar.SessionId); + var targetAiState = target.GetClosestAiState(sender.EntryCar.Status.Position); if (targetAiState.AiState != null && targetAiState.DistanceSquared < 25 * 25) { Task.Delay(Random.Shared.Next(100, 500)).ContinueWith(_ => targetAiState.AiState.StopForCollision()); @@ -86,7 +93,8 @@ private static void OnCollision(ACTcpClient sender, CollisionEventArgs args) private void OnClientChecksumPassed(ACTcpClient sender, EventArgs args) { - sender.EntryCar.SetAiControl(false); + var entryCar = _trafficAi.GetAiCarBySessionId(sender.SessionId); + entryCar.SetAiControl(false); AdjustOverbooking(); } @@ -105,7 +113,8 @@ private async Task ObstacleDetectionAsync(CancellationToken stoppingToken) var entryCar = _entryCarManager.EntryCars[i]; if (entryCar.AiControlled) { - entryCar.AiObstacleDetection(); + var entryCarAi = _trafficAi.GetAiCarBySessionId(entryCar.SessionId); + entryCarAi.AiObstacleDetection(); } } @@ -142,7 +151,8 @@ private void SendDebugPackets() { if (!car.AiControlled) continue; - var (aiState, _) = car.GetClosestAiState(player.Status.Position); + var carAi = _trafficAi.GetAiCarBySessionId(car.SessionId); + var (aiState, _) = carAi.GetClosestAiState(player.Status.Position); if (aiState == null) continue; sessionIds.Add(car.SessionId); @@ -199,8 +209,9 @@ private void Update() } else if (entryCar.AiControlled) { - entryCar.RemoveUnsafeStates(); - entryCar.GetInitializedStates(_initializedAiStates, _uninitializedAiStates); + var entryCarAi = _trafficAi.GetAiCarBySessionId(entryCar.SessionId); + entryCarAi.RemoveUnsafeStates(); + entryCarAi.GetInitializedStates(_initializedAiStates, _uninitializedAiStates); } } @@ -351,9 +362,10 @@ private async Task UpdateAsync(CancellationToken stoppingToken) private void OnClientDisconnected(ACTcpClient sender, EventArgs args) { - if (sender.EntryCar.AiMode != AssettoServer.Server.AiMode.None) + if (sender.EntryCar.AiMode != AiMode.None) { - sender.EntryCar.SetAiControl(true); + var entryCarAi = _trafficAi.GetAiCarBySessionId(sender.SessionId); + entryCarAi.SetAiControl(true); AdjustOverbooking(); } } @@ -393,7 +405,8 @@ private bool IsPositionSafe(int pointId) for (var i = 0; i < _entryCarManager.EntryCars.Length; i++) { var entryCar = _entryCarManager.EntryCars[i]; - if (entryCar.AiControlled && !entryCar.IsPositionSafe(pointId)) + var entryCarAi = _trafficAi.GetAiCarBySessionId(entryCar.SessionId); + if (entryCar.AiControlled && !entryCarAi.IsPositionSafe(pointId)) { return false; } @@ -471,7 +484,8 @@ private void AdjustOverbooking() for (int i = 0; i < aiSlots.Count; i++) { - aiSlots[i].SetAiOverbooking(i < rest ? overbooking + 1 : overbooking); + var entryCarAi = _trafficAi.GetAiCarBySessionId(aiSlots[i].SessionId); + entryCarAi.SetAiOverbooking(i < rest ? overbooking + 1 : overbooking); } } @@ -479,8 +493,8 @@ private void SetHttpDetailsExtensions() { _httpInfoCache.Extensions.Add("aiTraffic", new Dictionary> { - { "auto", _entryCarManager.EntryCars.Where(c => c.AiMode == AssettoServer.Server.AiMode.Auto).Select(c => c.SessionId).ToList() }, - { "fixed", _entryCarManager.EntryCars.Where(c => c.AiMode == AssettoServer.Server.AiMode.Fixed).Select(c => c.SessionId).ToList() } + { "auto", _entryCarManager.EntryCars.Where(c => c.AiMode == AiMode.Auto).Select(c => c.SessionId).ToList() }, + { "fixed", _entryCarManager.EntryCars.Where(c => c.AiMode == AiMode.Fixed).Select(c => c.SessionId).ToList() } }); } diff --git a/TrafficAiPlugin/AiSlotFilter.cs b/TrafficAiPlugin/AiSlotFilter.cs index 96c1993d..a15fa052 100644 --- a/TrafficAiPlugin/AiSlotFilter.cs +++ b/TrafficAiPlugin/AiSlotFilter.cs @@ -18,7 +18,7 @@ public AiSlotFilter(EntryCarManager entryCarManager, TrafficAiConfiguration conf public override bool IsSlotOpen(EntryCar entryCar, ulong guid) { - if (entryCar.AiMode == AssettoServer.Server.AiMode.Fixed + if (entryCar.AiMode == AiMode.Fixed || (_configuration.MaxPlayerCount > 0 && _entryCarManager.ConnectedCars.Count >= _configuration.MaxPlayerCount)) { return false; diff --git a/TrafficAiPlugin/AiState.cs b/TrafficAiPlugin/AiState.cs index da4bc370..44f5b3f4 100644 --- a/TrafficAiPlugin/AiState.cs +++ b/TrafficAiPlugin/AiState.cs @@ -42,7 +42,7 @@ private set public Color Color { get; private set; } public byte SpawnCounter { get; private set; } public float ClosestAiObstacleDistance { get; private set; } - public AssettoServer.Server.EntryCar EntryCar { get; } + public EntryCarTrafficAi EntryCarAi { get; } private const float WalkingSpeed = 10 / 3.6f; @@ -95,7 +95,7 @@ private set Color.FromArgb(18, 46, 43) ]; - public AiState(EntryCar entryCar, + public AiState(EntryCarTrafficAi entryCarAi, SessionManager sessionManager, WeatherManager weatherManager, ACServerConfiguration serverConfiguration, @@ -103,7 +103,7 @@ public AiState(EntryCar entryCar, EntryCarManager entryCarManager, AiSpline spline) { - EntryCar = entryCar; + EntryCarAi = entryCarAi; _sessionManager = sessionManager; _weatherManager = weatherManager; _serverConfiguration = serverConfiguration; @@ -174,12 +174,12 @@ public void Teleport(int pointId) maxDist = overrides.MaxAiSafetyDistanceSquared; } - if (EntryCar.MinAiSafetyDistanceMetersSquared.HasValue) - minDist = EntryCar.MinAiSafetyDistanceMetersSquared.Value; - if (EntryCar.MaxAiSafetyDistanceMetersSquared.HasValue) - maxDist = EntryCar.MaxAiSafetyDistanceMetersSquared.Value; + if (EntryCarAi.MinAiSafetyDistanceMetersSquared.HasValue) + minDist = EntryCarAi.MinAiSafetyDistanceMetersSquared.Value; + if (EntryCarAi.MaxAiSafetyDistanceMetersSquared.HasValue) + maxDist = EntryCarAi.MaxAiSafetyDistanceMetersSquared.Value; - SpawnProtectionEnds = _sessionManager.ServerTimeMilliseconds + Random.Shared.Next(EntryCar.AiMinSpawnProtectionTimeMilliseconds, EntryCar.AiMaxSpawnProtectionTimeMilliseconds); + SpawnProtectionEnds = _sessionManager.ServerTimeMilliseconds + Random.Shared.Next(EntryCarAi.AiMinSpawnProtectionTimeMilliseconds, EntryCarAi.AiMaxSpawnProtectionTimeMilliseconds); SafetyDistanceSquared = Random.Shared.Next((int)Math.Round(minDist * (1.0f / _configuration.TrafficDensity)), (int)Math.Round(maxDist * (1.0f / _configuration.TrafficDensity))); _stoppedForCollisionUntil = 0; @@ -286,7 +286,7 @@ public bool CanSpawn(int spawnPointId, AiState? previousAi, AiState? nextAi) if (!IsKeepingSafetyDistances(in spawnPoint, previousAi, nextAi)) return false; - return EntryCar.CanSpawnAiState(spawnPoint.Position, this); + return EntryCarAi.CanSpawnAiState(spawnPoint.Position, this); } private bool IsKeepingSafetyDistances(in SplinePoint spawnPoint, AiState? previousAi, AiState? nextAi) @@ -294,8 +294,8 @@ private bool IsKeepingSafetyDistances(in SplinePoint spawnPoint, AiState? previo if (previousAi != null) { var distance = MathF.Max(0, Vector3.Distance(spawnPoint.Position, previousAi.Status.Position) - - previousAi.EntryCar.VehicleLengthPreMeters - - EntryCar.VehicleLengthPostMeters); + - previousAi.EntryCarAi.VehicleLengthPreMeters + - EntryCarAi.VehicleLengthPostMeters); var distanceSquared = distance * distance; if (distanceSquared < previousAi.SafetyDistanceSquared || distanceSquared < SafetyDistanceSquared) @@ -305,8 +305,8 @@ private bool IsKeepingSafetyDistances(in SplinePoint spawnPoint, AiState? previo if (nextAi != null) { var distance = MathF.Max(0, Vector3.Distance(spawnPoint.Position, nextAi.Status.Position) - - nextAi.EntryCar.VehicleLengthPostMeters - - EntryCar.VehicleLengthPreMeters); + - nextAi.EntryCarAi.VehicleLengthPostMeters + - EntryCarAi.VehicleLengthPreMeters); var distanceSquared = distance * distance; if (distanceSquared < nextAi.SafetyDistanceSquared || distanceSquared < SafetyDistanceSquared) @@ -319,9 +319,9 @@ private bool IsKeepingSafetyDistances(in SplinePoint spawnPoint, AiState? previo private bool IsAllowedLaneCount(int spawnPointId) { var laneCount = _spline.GetLanes(spawnPointId).Length; - if (EntryCar.MinLaneCount.HasValue && laneCount < EntryCar.MinLaneCount.Value) + if (EntryCarAi.MinLaneCount.HasValue && laneCount < EntryCarAi.MinLaneCount.Value) return false; - if (EntryCar.MaxLaneCount.HasValue && laneCount > EntryCar.MaxLaneCount.Value) + if (EntryCarAi.MaxLaneCount.HasValue && laneCount > EntryCarAi.MaxLaneCount.Value) return false; return true; @@ -330,11 +330,11 @@ private bool IsAllowedLaneCount(int spawnPointId) private bool IsAllowedLane(in SplinePoint spawnPoint) { var isAllowedLane = true; - if (EntryCar.AiAllowedLanes != null) + if (EntryCarAi.AiAllowedLanes != null) { - isAllowedLane = (EntryCar.AiAllowedLanes.Contains(LaneSpawnBehavior.Middle) && spawnPoint.LeftId >= 0 && spawnPoint.RightId >= 0) - || (EntryCar.AiAllowedLanes.Contains(LaneSpawnBehavior.Left) && spawnPoint.LeftId < 0) - || (EntryCar.AiAllowedLanes.Contains(LaneSpawnBehavior.Right) && spawnPoint.RightId < 0); + isAllowedLane = (EntryCarAi.AiAllowedLanes.Contains(LaneSpawnBehavior.Middle) && spawnPoint.LeftId >= 0 && spawnPoint.RightId >= 0) + || (EntryCarAi.AiAllowedLanes.Contains(LaneSpawnBehavior.Left) && spawnPoint.LeftId < 0) + || (EntryCarAi.AiAllowedLanes.Contains(LaneSpawnBehavior.Right) && spawnPoint.RightId < 0); } return isAllowedLane; @@ -345,7 +345,7 @@ private bool IsAllowedLane(in SplinePoint spawnPoint) var points = _spline.Points; var junctions = _spline.Junctions; - float maxBrakingDistance = PhysicsUtils.CalculateBrakingDistance(CurrentSpeed, EntryCar.AiDeceleration) * 2 + 20; + float maxBrakingDistance = PhysicsUtils.CalculateBrakingDistance(CurrentSpeed, EntryCarAi.AiDeceleration) * 2 + 20; AiState? closestAiState = null; float closestAiStateDistance = float.MaxValue; bool junctionFound = false; @@ -384,18 +384,18 @@ private bool IsAllowedLane(in SplinePoint spawnPoint) { closestAiState = slowest; closestAiStateDistance = MathF.Max(0, Vector3.Distance(Status.Position, closestAiState.Status.Position) - - EntryCar.VehicleLengthPreMeters - - closestAiState.EntryCar.VehicleLengthPostMeters); + - EntryCarAi.VehicleLengthPreMeters + - closestAiState.EntryCarAi.VehicleLengthPostMeters); } } - float maxCorneringSpeedSquared = PhysicsUtils.CalculateMaxCorneringSpeedSquared(point.Radius, EntryCar.AiCorneringSpeedFactor); + float maxCorneringSpeedSquared = PhysicsUtils.CalculateMaxCorneringSpeedSquared(point.Radius, EntryCarAi.AiCorneringSpeedFactor); if (maxCorneringSpeedSquared < currentSpeedSquared) { float maxCorneringSpeed = MathF.Sqrt(maxCorneringSpeedSquared); float brakingDistance = PhysicsUtils.CalculateBrakingDistance(CurrentSpeed - maxCorneringSpeed, - EntryCar.AiDeceleration * EntryCar.AiCorneringBrakeForceFactor) - * EntryCar.AiCorneringBrakeDistanceFactor; + EntryCarAi.AiDeceleration * EntryCarAi.AiCorneringBrakeForceFactor) + * EntryCarAi.AiCorneringBrakeDistanceFactor; if (brakingDistance > distanceTravelled) { @@ -528,7 +528,7 @@ public void DetectObstacles() } if ((playerSpeed < CurrentSpeed || playerSpeed == 0) - && playerObstacle.distance < PhysicsUtils.CalculateBrakingDistance(CurrentSpeed - playerSpeed, EntryCar.AiDeceleration) * 2 + 20) + && playerObstacle.distance < PhysicsUtils.CalculateBrakingDistance(CurrentSpeed - playerSpeed, EntryCarAi.AiDeceleration) * 2 + 20) { targetSpeed = Math.Max(WalkingSpeed, playerSpeed); hasObstacle = true; @@ -538,7 +538,7 @@ public void DetectObstacles() { float closestTargetSpeed = Math.Min(splineLookahead.ClosestAiState.CurrentSpeed, splineLookahead.ClosestAiState.TargetSpeed); if ((closestTargetSpeed < CurrentSpeed || splineLookahead.ClosestAiState.CurrentSpeed == 0) - && splineLookahead.ClosestAiStateDistance < PhysicsUtils.CalculateBrakingDistance(CurrentSpeed - closestTargetSpeed, EntryCar.AiDeceleration) * 2 + 20) + && splineLookahead.ClosestAiStateDistance < PhysicsUtils.CalculateBrakingDistance(CurrentSpeed - closestTargetSpeed, EntryCarAi.AiDeceleration) * 2 + 20) { targetSpeed = Math.Max(WalkingSpeed, closestTargetSpeed); hasObstacle = true; @@ -553,34 +553,34 @@ public void DetectObstacles() _stoppedForObstacleSince = _sessionManager.ServerTimeMilliseconds; _obstacleHonkStart = _stoppedForObstacleSince + Random.Shared.Next(3000, 7000); _obstacleHonkEnd = _obstacleHonkStart + Random.Shared.Next(500, 1500); - Log.Verbose("AI {SessionId} stopped for obstacle", EntryCar.SessionId); + Log.Verbose("AI {SessionId} stopped for obstacle", EntryCarAi.EntryCar.SessionId); } else if (CurrentSpeed > 0 && _stoppedForObstacle) { _stoppedForObstacle = false; - Log.Verbose("AI {SessionId} no longer stopped for obstacle", EntryCar.SessionId); + Log.Verbose("AI {SessionId} no longer stopped for obstacle", EntryCarAi.EntryCar.SessionId); } else if (_stoppedForObstacle && _sessionManager.ServerTimeMilliseconds - _stoppedForObstacleSince > _configuration.IgnoreObstaclesAfterMilliseconds) { _ignoreObstaclesUntil = _sessionManager.ServerTimeMilliseconds + 10_000; - Log.Verbose("AI {SessionId} ignoring obstacles until {IgnoreObstaclesUntil}", EntryCar.SessionId, _ignoreObstaclesUntil); + Log.Verbose("AI {SessionId} ignoring obstacles until {IgnoreObstaclesUntil}", EntryCarAi.EntryCar.SessionId, _ignoreObstaclesUntil); } - float deceleration = EntryCar.AiDeceleration; + float deceleration = EntryCarAi.AiDeceleration; if (!hasObstacle) { - deceleration *= EntryCar.AiCorneringBrakeForceFactor; + deceleration *= EntryCarAi.AiCorneringBrakeForceFactor; } MaxSpeed = maxSpeed; - SetTargetSpeed(targetSpeed, deceleration, EntryCar.AiAcceleration); + SetTargetSpeed(targetSpeed, deceleration, EntryCarAi.AiAcceleration); } public void StopForCollision() { if (!ShouldIgnorePlayerObstacles()) { - _stoppedForCollisionUntil = _sessionManager.ServerTimeMilliseconds + Random.Shared.Next(EntryCar.AiMinCollisionStopTimeMilliseconds, EntryCar.AiMaxCollisionStopTimeMilliseconds); + _stoppedForCollisionUntil = _sessionManager.ServerTimeMilliseconds + Random.Shared.Next(EntryCarAi.AiMinCollisionStopTimeMilliseconds, EntryCarAi.AiMaxCollisionStopTimeMilliseconds); } } @@ -617,7 +617,7 @@ private void SetTargetSpeed(float speed, float deceleration, float acceleration) private void SetTargetSpeed(float speed) { - SetTargetSpeed(speed, EntryCar.AiDeceleration, EntryCar.AiAcceleration); + SetTargetSpeed(speed, EntryCarAi.AiDeceleration, EntryCarAi.AiAcceleration); } public void Update() @@ -645,7 +645,7 @@ public void Update() float moveMeters = (dt / 1000.0f) * CurrentSpeed; if (!Move(_currentVecProgress + moveMeters) || !_junctionEvaluator.TryNext(CurrentSplinePointId, out var nextPoint)) { - Log.Debug("Car {SessionId} reached spline end, despawning", EntryCar.SessionId); + Log.Debug("Car {SessionId} reached spline end, despawning", EntryCarAi.EntryCar.SessionId); Despawn(); return; } @@ -663,11 +663,11 @@ public void Update() Z = ops.GetCamber(CurrentSplinePointId, _currentVecProgress / _currentVecLength) }; - float tyreAngularSpeed = GetTyreAngularSpeed(CurrentSpeed, EntryCar.TyreDiameterMeters); + float tyreAngularSpeed = GetTyreAngularSpeed(CurrentSpeed, EntryCarAi.TyreDiameterMeters); byte encodedTyreAngularSpeed = (byte) (Math.Clamp(MathF.Round(MathF.Log10(tyreAngularSpeed + 1.0f) * 20.0f) * Math.Sign(tyreAngularSpeed), -100.0f, 154.0f) + 100.0f); Status.Timestamp = _sessionManager.ServerTimeMilliseconds; - Status.Position = smoothPos.Position with { Y = smoothPos.Position.Y + EntryCar.AiSplineHeightOffsetMeters }; + Status.Position = smoothPos.Position with { Y = smoothPos.Position.Y + EntryCarAi.AiSplineHeightOffsetMeters }; Status.Rotation = rotation; Status.Velocity = smoothPos.Tangent * CurrentSpeed; Status.SteerAngle = 127; @@ -676,7 +676,7 @@ public void Update() Status.TyreAngularSpeed[1] = encodedTyreAngularSpeed; Status.TyreAngularSpeed[2] = encodedTyreAngularSpeed; Status.TyreAngularSpeed[3] = encodedTyreAngularSpeed; - Status.EngineRpm = (ushort)MathUtils.Lerp(EntryCar.AiIdleEngineRpm, EntryCar.AiMaxEngineRpm, CurrentSpeed / _configuration.MaxSpeedMs); + Status.EngineRpm = (ushort)MathUtils.Lerp(EntryCarAi.AiIdleEngineRpm, EntryCarAi.AiMaxEngineRpm, CurrentSpeed / _configuration.MaxSpeedMs); Status.StatusFlag = GetLights(_configuration.EnableDaytimeLights, _weatherManager.CurrentSunPosition, _randomTwilight) | CarStatusFlags.HighBeamsOff | (_sessionManager.ServerTimeMilliseconds < _stoppedForCollisionUntil || CurrentSpeed < 20 / 3.6f ? CarStatusFlags.HazardsOn : 0) diff --git a/TrafficAiPlugin/AiUpdater.cs b/TrafficAiPlugin/AiUpdater.cs deleted file mode 100644 index 2b104a40..00000000 --- a/TrafficAiPlugin/AiUpdater.cs +++ /dev/null @@ -1,26 +0,0 @@ -using AssettoServer.Server; - -namespace TrafficAIPlugin; - -public class AiUpdater -{ - private readonly EntryCarManager _entryCarManager; - - public AiUpdater(EntryCarManager entryCarManager, ACServer server) - { - _entryCarManager = entryCarManager; - server.Update += OnUpdate; - } - - private void OnUpdate(object sender, EventArgs args) - { - for (var i = 0; i < _entryCarManager.EntryCars.Length; i++) - { - var entryCar = _entryCarManager.EntryCars[i]; - if (entryCar.AiControlled) - { - entryCar.AiUpdate(); - } - } - } -} diff --git a/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs b/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs index 024001fb..831c1e97 100644 --- a/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs +++ b/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs @@ -1,4 +1,5 @@ -using AssettoServer.Server.Configuration; +using AssettoServer.Server; +using AssettoServer.Server.Configuration; using CommunityToolkit.Mvvm.ComponentModel; using JetBrains.Annotations; using YamlDotNet.Serialization; @@ -142,4 +143,17 @@ public partial class TrafficAiConfiguration : ObservableObject, IValidateConfigu [YamlIgnore] public float RightLaneOffsetMs => RightLaneOffsetKph / 3.6f; [YamlIgnore] public int IgnoreObstaclesAfterMilliseconds => IgnoreObstaclesAfterSeconds * 1000; [YamlIgnore] public int AiBehaviorUpdateIntervalMilliseconds => 1000 / AiBehaviorUpdateIntervalHz; + + internal void ApplyConfigurationFixes(ACServerConfiguration serverConfiguration) + { + if (AiPerPlayerTargetCount == 0) + { + AiPerPlayerTargetCount = serverConfiguration.EntryList.Cars.Count(c => c.AiMode != AiMode.None); + } + + if (MaxAiTargetCount == 0) + { + MaxAiTargetCount = serverConfiguration.EntryList.Cars.Count(c => c.AiMode != AiMode.Fixed) * AiPerPlayerTargetCount; + } + } } diff --git a/TrafficAiPlugin/EntryCarTrafficAi.cs b/TrafficAiPlugin/EntryCarTrafficAi.cs index fd1aa695..0fdfa1ea 100644 --- a/TrafficAiPlugin/EntryCarTrafficAi.cs +++ b/TrafficAiPlugin/EntryCarTrafficAi.cs @@ -1,28 +1,24 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.InteropServices; +using AssettoServer.Server; +using AssettoServer.Server.Configuration; using AssettoServer.Shared.Model; using AssettoServer.Shared.Network.Packets.Outgoing; +using AssettoServer.Shared.Network.Packets.Shared; +using Grpc.Core; +using TrafficAIPlugin.Configuration; +using TrafficAIPlugin.Splines; namespace TrafficAIPlugin; -public enum AiMode -{ - None, - Auto, - Fixed -} - public class EntryCarTrafficAi { - public bool AiControlled { get; set; } - public AiMode AiMode { get; set; } public int TargetAiStateCount { get; private set; } = 1; public byte[] LastSeenAiSpawn { get; } public byte[] AiPakSequenceIds { get; } public AiState?[] LastSeenAiState { get; } - public string? AiName { get; private set; } - public bool AiEnableColorChanges { get; set; } = false; + public bool AiEnableColorChanges => EntryCar.DriverOptionsFlags.HasFlag(DriverOptionsFlags.AllowColorChange); public int AiIdleEngineRpm { get; set; } = 800; public int AiMaxEngineRpm { get; set; } = 3000; public float AiAcceleration { get; set; } @@ -47,35 +43,69 @@ public class EntryCarTrafficAi private readonly List _aiStates = []; private Span AiStatesSpan => CollectionsMarshal.AsSpan(_aiStates); - private readonly Func _aiStateFactory; - private readonly AiSpline? _spline; + private readonly Func _aiStateFactory; + private readonly TrafficAi _trafficAi; + + public readonly EntryCar EntryCar; + + private readonly ACServerConfiguration _serverConfiguration; + private readonly TrafficAiConfiguration _configuration; + private readonly EntryCarManager _entryCarManager; + private readonly SessionManager _sessionManager; + private readonly AiSpline _aiSpline; + + public EntryCarTrafficAi(EntryCar entryCar, + TrafficAiConfiguration configuration, + EntryCarManager entryCarManager, + SessionManager sessionManager, + ACServerConfiguration serverConfiguration, + Func aiStateFactory, + TrafficAi trafficAi, + AiSpline aiSpline) + { + EntryCar = entryCar; + _configuration = configuration; + _entryCarManager = entryCarManager; + _sessionManager = sessionManager; + _serverConfiguration = serverConfiguration; + _aiStateFactory = aiStateFactory; + _trafficAi = trafficAi; + _aiSpline = aiSpline; + + + AiPakSequenceIds = new byte[entryCarManager.EntryCars.Length]; + LastSeenAiState = new AiState[entryCarManager.EntryCars.Length]; + LastSeenAiSpawn = new byte[entryCarManager.EntryCars.Length]; + + AiInit(); + } private void AiInit() { - AiName = $"{_configuration.Extra.AiParams.NamePrefix} {SessionId}"; + EntryCar.AiName = $"{_configuration.NamePrefix} {EntryCar.SessionId}"; SetAiOverbooking(0); - _configuration.Extra.AiParams.PropertyChanged += OnConfigReload; + _configuration.PropertyChanged += OnConfigReload; OnConfigReload(_configuration, new PropertyChangedEventArgs(string.Empty)); } private void OnConfigReload(object? sender, PropertyChangedEventArgs args) { - AiSplineHeightOffsetMeters = _configuration.Extra.AiParams.SplineHeightOffsetMeters; - AiAcceleration = _configuration.Extra.AiParams.DefaultAcceleration; - AiDeceleration = _configuration.Extra.AiParams.DefaultDeceleration; - AiCorneringSpeedFactor = _configuration.Extra.AiParams.CorneringSpeedFactor; - AiCorneringBrakeDistanceFactor = _configuration.Extra.AiParams.CorneringBrakeDistanceFactor; - AiCorneringBrakeForceFactor = _configuration.Extra.AiParams.CorneringBrakeForceFactor; - TyreDiameterMeters = _configuration.Extra.AiParams.TyreDiameterMeters; - AiMinSpawnProtectionTimeMilliseconds = _configuration.Extra.AiParams.MinSpawnProtectionTimeMilliseconds; - AiMaxSpawnProtectionTimeMilliseconds = _configuration.Extra.AiParams.MaxSpawnProtectionTimeMilliseconds; - AiMinCollisionStopTimeMilliseconds = _configuration.Extra.AiParams.MinCollisionStopTimeMilliseconds; - AiMaxCollisionStopTimeMilliseconds = _configuration.Extra.AiParams.MaxCollisionStopTimeMilliseconds; - - foreach (var carOverrides in _configuration.Extra.AiParams.CarSpecificOverrides) + AiSplineHeightOffsetMeters = _configuration.SplineHeightOffsetMeters; + AiAcceleration = _configuration.DefaultAcceleration; + AiDeceleration = _configuration.DefaultDeceleration; + AiCorneringSpeedFactor = _configuration.CorneringSpeedFactor; + AiCorneringBrakeDistanceFactor = _configuration.CorneringBrakeDistanceFactor; + AiCorneringBrakeForceFactor = _configuration.CorneringBrakeForceFactor; + TyreDiameterMeters = _configuration.TyreDiameterMeters; + AiMinSpawnProtectionTimeMilliseconds = _configuration.MinSpawnProtectionTimeMilliseconds; + AiMaxSpawnProtectionTimeMilliseconds = _configuration.MaxSpawnProtectionTimeMilliseconds; + AiMinCollisionStopTimeMilliseconds = _configuration.MinCollisionStopTimeMilliseconds; + AiMaxCollisionStopTimeMilliseconds = _configuration.MaxCollisionStopTimeMilliseconds; + + foreach (var carOverrides in _configuration.CarSpecificOverrides) { - if (carOverrides.Model == Model) + if (carOverrides.Model == EntryCar.Model) { if (carOverrides.SplineHeightOffsetMeters.HasValue) AiSplineHeightOffsetMeters = carOverrides.SplineHeightOffsetMeters.Value; @@ -129,11 +159,11 @@ public void RemoveUnsafeStates() { if (aiState != targetAiState && targetAiState.Initialized - && Vector3.DistanceSquared(aiState.Status.Position, targetAiState.Status.Position) < _configuration.Extra.AiParams.MinStateDistanceSquared - && (_configuration.Extra.AiParams.TwoWayTraffic || Vector3.Dot(aiState.Status.Velocity, targetAiState.Status.Velocity) > 0)) + && Vector3.DistanceSquared(aiState.Status.Position, targetAiState.Status.Position) < _configuration.MinStateDistanceSquared + && (_configuration.TwoWayTraffic || Vector3.Dot(aiState.Status.Velocity, targetAiState.Status.Velocity) > 0)) { aiState.Despawn(); - Logger.Verbose("Removed close state from AI {SessionId}", SessionId); + EntryCar.Logger.Verbose("Removed close state from AI {SessionId}", EntryCar.SessionId); } } } @@ -166,7 +196,7 @@ public void AiObstacleDetection() float distance = Vector3.DistanceSquared(aiState.Status.Position, playerStatus.Position); - if (_configuration.Extra.AiParams.TwoWayTraffic) + if (_configuration.TwoWayTraffic) { if (distance < minDistance) { @@ -179,8 +209,8 @@ public void AiObstacleDetection() bool isBestSameDirection = bestState != null && Vector3.Dot(bestState.Status.Velocity, playerStatus.Velocity) > 0; bool isCandidateSameDirection = Vector3.Dot(aiState.Status.Velocity, playerStatus.Velocity) > 0; bool isPlayerFastEnough = playerStatus.Velocity.LengthSquared() > 1; - bool isTieBreaker = minDistance < _configuration.Extra.AiParams.MinStateDistanceSquared && - distance < _configuration.Extra.AiParams.MinStateDistanceSquared && + bool isTieBreaker = minDistance < _configuration.MinStateDistanceSquared && + distance < _configuration.MinStateDistanceSquared && isPlayerFastEnough; // Tie breaker: Multiple close states, so take the one with min distance and same direction @@ -198,9 +228,9 @@ public void AiObstacleDetection() public bool IsPositionSafe(int pointId) { - ArgumentNullException.ThrowIfNull(_spline); + ArgumentNullException.ThrowIfNull(_aiSpline); - var ops = _spline.Operations; + var ops = _aiSpline.Operations; foreach (var aiState in AiStatesSpan) { @@ -256,12 +286,12 @@ public bool CanSpawnAiState(Vector3 spawnPoint, AiState aiState) aiState.Dispose(); _aiStates.Remove(aiState); - Logger.Verbose("Removed state of Traffic {SessionId} due to overbooking reduction", SessionId); + EntryCar.Logger.Verbose("Removed state of Traffic {SessionId} due to overbooking reduction", EntryCar.SessionId); if (_aiStates.Count == 0) { - Logger.Verbose("Traffic {SessionId} has no states left, disconnecting", SessionId); - _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = SessionId }); + EntryCar.Logger.Verbose("Traffic {SessionId} has no states left, disconnecting", EntryCar.SessionId); + _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = EntryCar.SessionId }); } return false; @@ -271,7 +301,7 @@ public bool CanSpawnAiState(Vector3 spawnPoint, AiState aiState) { if (state == aiState || !state.Initialized) continue; - if (Vector3.DistanceSquared(spawnPoint, state.Status.Position) < _configuration.Extra.AiParams.StateSpawnDistanceSquared) + if (Vector3.DistanceSquared(spawnPoint, state.Status.Position) < _configuration.StateSpawnDistanceSquared) { return false; } @@ -282,48 +312,46 @@ public bool CanSpawnAiState(Vector3 spawnPoint, AiState aiState) public void SetAiControl(bool aiControlled) { - if (AiControlled != aiControlled) + if (EntryCar.AiControlled == aiControlled) return; + + EntryCar.AiControlled = aiControlled; + if (EntryCar.AiControlled) { - AiControlled = aiControlled; + EntryCar.Logger.Debug("Slot {SessionId} is now controlled by AI", EntryCar.SessionId); - if (AiControlled) + AiReset(); + _entryCarManager.BroadcastPacket(new CarConnected { - Logger.Debug("Slot {SessionId} is now controlled by AI", SessionId); - - AiReset(); - _entryCarManager.BroadcastPacket(new CarConnected + SessionId = EntryCar.SessionId, + Name = EntryCar.AiName + }); + if (_configuration.HideAiCars) + { + _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate { - SessionId = SessionId, - Name = AiName + SessionId = EntryCar.SessionId, + Visible = CSPCarVisibility.Invisible }); - if (_configuration.Extra.AiParams.HideAiCars) - { - _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate - { - SessionId = SessionId, - Visible = CSPCarVisibility.Invisible - }); - } } - else + } + else + { + EntryCar.Logger.Debug("Slot {SessionId} is no longer controlled by AI", EntryCar.SessionId); + if (_aiStates.Count > 0) { - Logger.Debug("Slot {SessionId} is no longer controlled by AI", SessionId); - if (_aiStates.Count > 0) - { - _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = SessionId }); - } + _entryCarManager.BroadcastPacket(new CarDisconnected { SessionId = EntryCar.SessionId }); + } - if (_configuration.Extra.AiParams.HideAiCars) + if (_configuration.HideAiCars) + { + _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate { - _entryCarManager.BroadcastPacket(new CSPCarVisibilityUpdate - { - SessionId = SessionId, - Visible = CSPCarVisibility.Visible - }); - } - - AiReset(); + SessionId = EntryCar.SessionId, + Visible = CSPCarVisibility.Visible + }); } + + AiReset(); } } @@ -355,4 +383,124 @@ private void AiReset() _aiStates.Clear(); _aiStates.Add(_aiStateFactory(this)); } + + public bool TryResetPosition() + { + if (_sessionManager.ServerTimeMilliseconds < _sessionManager.CurrentSession.StartTimeMilliseconds + 20_000 + || (_sessionManager.ServerTimeMilliseconds > _sessionManager.CurrentSession.EndTimeMilliseconds + && _sessionManager.CurrentSession.EndTimeMilliseconds > 0)) + return false; + + var (splinePointId, _) = _aiSpline.WorldToSpline(EntryCar.Status.Position); + + var splinePoint = _aiSpline.Points[splinePointId]; + + var position = splinePoint.Position; + var direction = - _aiSpline.Operations.GetForwardVector(splinePoint.NextId); + + EntryCar.Client?.SendCollisionUpdatePacket(false); + + _ = Task.Run(async () => + { + await Task.Delay(500); + + EntryCar.Client?.SendTeleportCarPacket(position, direction); + await Task.Delay(10000); + + EntryCar.Client?.SendCollisionUpdatePacket(true); + }); + + EntryCar.Logger.Information("Reset position for {Player} ({SessionId})",EntryCar.Client?.Name, EntryCar.Client?.SessionId); + return true; + } + + public bool GetPositionUpdateForCar(EntryCar toCar, out PositionUpdateOut positionUpdateOut) + { + CarStatus targetCarStatus; + var toTargetCar = toCar.TargetCar; + if (toTargetCar != null) + { + var toTargetCarAi = _trafficAi.GetAiCarBySessionId(toTargetCar.SessionId); + if (toTargetCar.AiControlled && toTargetCarAi.LastSeenAiState[toCar.SessionId] != null) + { + targetCarStatus = toTargetCarAi.LastSeenAiState[toCar.SessionId]!.Status; + } + else + { + targetCarStatus = toTargetCar.Status; + } + } + else + { + targetCarStatus = toCar.Status; + } + + CarStatus status; + if (EntryCar.AiControlled) + { + var aiState = GetBestStateForPlayer(targetCarStatus); + + if (aiState == null) + { + positionUpdateOut = default; + return false; + } + + if (LastSeenAiState[toCar.SessionId] != aiState + || LastSeenAiSpawn[toCar.SessionId] != aiState.SpawnCounter) + { + LastSeenAiState[toCar.SessionId] = aiState; + LastSeenAiSpawn[toCar.SessionId] = aiState.SpawnCounter; + + if (AiEnableColorChanges) + { + toCar.Client?.SendPacket(new CSPCarColorUpdate + { + SessionId = EntryCar.SessionId, + Color = aiState.Color + }); + } + } + + status = aiState.Status; + } + else + { + status = EntryCar.Status; + } + + float distanceSquared = Vector3.DistanceSquared(status.Position, targetCarStatus.Position); + if (EntryCar.TargetCar != null || distanceSquared > EntryCar.NetworkDistanceSquared) + { + if ((_sessionManager.ServerTimeMilliseconds - EntryCar.OtherCarsLastSentUpdateTime[toCar.SessionId]) < EntryCar.OutsideNetworkBubbleUpdateRateMs) + { + positionUpdateOut = default; + return false; + } + + EntryCar.OtherCarsLastSentUpdateTime[toCar.SessionId] = _sessionManager.ServerTimeMilliseconds; + } + + positionUpdateOut = new PositionUpdateOut(EntryCar.SessionId, + EntryCar.AiControlled ? AiPakSequenceIds[toCar.SessionId]++ : status.PakSequenceId, + (uint)(status.Timestamp - toCar.TimeOffset), + EntryCar.Ping, + status.Position, + status.Rotation, + status.Velocity, + status.TyreAngularSpeed[0], + status.TyreAngularSpeed[1], + status.TyreAngularSpeed[2], + status.TyreAngularSpeed[3], + status.SteerAngle, + status.WheelAngle, + status.EngineRpm, + status.Gear, + (_serverConfiguration.Extra.ForceLights || EntryCar.ForceLights) + ? status.StatusFlag | CarStatusFlags.LightsOn + : status.StatusFlag, + status.PerformanceDelta, + status.Gas); + return true; + } } diff --git a/TrafficAiPlugin/Splines/FastLaneParser.cs b/TrafficAiPlugin/Splines/FastLaneParser.cs index 704bd59e..2dbf86a8 100644 --- a/TrafficAiPlugin/Splines/FastLaneParser.cs +++ b/TrafficAiPlugin/Splines/FastLaneParser.cs @@ -10,12 +10,14 @@ namespace TrafficAIPlugin.Splines; public class FastLaneParser { - private readonly ACServerConfiguration _configuration; + private readonly ACServerConfiguration _serverConfiguration; + private readonly TrafficAiConfiguration _configuration; private ILogger _logger = Log.Logger; - public FastLaneParser(ACServerConfiguration configuration) + public FastLaneParser(ACServerConfiguration serverConfiguration, TrafficAiConfiguration configuration) { + _serverConfiguration = serverConfiguration; _configuration = configuration; } @@ -26,7 +28,7 @@ private void CheckConfig(TrafficConfiguration configuration) Log.Information("Loading AI spline by {Author}, version {Version}", configuration.Author, configuration.Version); } - if (!string.IsNullOrWhiteSpace(configuration.Track) && Path.GetFileName(_configuration.Server.Track) != configuration.Track) + if (!string.IsNullOrWhiteSpace(configuration.Track) && Path.GetFileName(_serverConfiguration.Server.Track) != configuration.Track) { throw new InvalidOperationException($"Mismatched AI spline, AI spline is for track {configuration.Track}"); } @@ -110,7 +112,7 @@ public MutableAiSpline FromFiles(string folder) throw new InvalidOperationException($"No AI splines found. Please put at least one AI spline fast_lane.ai(p) into {Path.GetFullPath(folder)}"); } - return new MutableAiSpline(splines, _configuration.Extra.AiParams.LaneWidthMeters, _configuration.Extra.AiParams.TwoWayTraffic, configuration, _logger); + return new MutableAiSpline(splines, _configuration.LaneWidthMeters, _configuration.TwoWayTraffic, configuration, _logger); } private SplinePoint[] FromFileV7(BinaryReader reader, int idOffset) @@ -209,7 +211,7 @@ private FastLane FromFile(Stream file, string name, int idOffset = 0) }; MovingAverage? avg = null; - if (_configuration.Extra.AiParams.SmoothCamber) + if (_configuration.SmoothCamber) { avg = new MovingAverage(5); } diff --git a/TrafficAiPlugin/Splines/MutableAiSpline.cs b/TrafficAiPlugin/Splines/MutableAiSpline.cs index 067d5f12..969201c3 100644 --- a/TrafficAiPlugin/Splines/MutableAiSpline.cs +++ b/TrafficAiPlugin/Splines/MutableAiSpline.cs @@ -2,6 +2,7 @@ using AssettoServer.Shared.Network.Packets.Outgoing; using Serilog; using Supercluster.KDTree; +using TrafficAIPlugin.Configuration; namespace TrafficAIPlugin.Splines; diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs index 4422c6a5..522f64c2 100644 --- a/TrafficAiPlugin/TrafficAi.cs +++ b/TrafficAiPlugin/TrafficAi.cs @@ -3,6 +3,7 @@ using AssettoServer.Server; using AssettoServer.Server.Configuration; using AssettoServer.Server.Plugin; +using AssettoServer.Shared.Network.Packets.Outgoing; using AssettoServer.Shared.Services; using AssettoServer.Utils; using Microsoft.Extensions.Hosting; @@ -15,16 +16,28 @@ public class TrafficAi : CriticalBackgroundService, IAssettoServerAutostart { private readonly TrafficAiConfiguration _configuration; private readonly ACServerConfiguration _serverConfiguration; + private readonly EntryCarManager _entryCarManager; + private readonly SessionManager _sessionManager; + private readonly Func _entryCarTrafficAiFactory; + + public readonly List Instances = []; public TrafficAi(TrafficAiConfiguration configuration, ACServerConfiguration serverConfiguration, - ACServer server, + EntryCarManager entryCarManager, + SessionManager sessionManager, + Func entryCarTrafficAiFactory, CSPClientMessageTypeManager cspClientMessageTypeManager, IHostApplicationLifetime applicationLifetime) : base(applicationLifetime) { _configuration = configuration; _serverConfiguration = serverConfiguration; + _entryCarManager = entryCarManager; + _sessionManager = sessionManager; + _entryCarTrafficAiFactory = entryCarTrafficAiFactory; + _configuration.ApplyConfigurationFixes(_serverConfiguration); + if (_configuration.EnableCarReset) { if (!_serverConfiguration.Extra.EnableClientMessages || _serverConfiguration.CSPTrackOptions.MinimumCSPVersion < CSPVersion.V0_2_3_p47) @@ -35,24 +48,55 @@ public TrafficAi(TrafficAiConfiguration configuration, cspClientMessageTypeManager.RegisterOnlineEvent((client, _) => { OnResetCar(client); }); } + _entryCarManager.ClientConnected += (sender, _) => + { + if (_configuration.HideAiCars) + { + sender.FirstUpdateSent += OnFirstUpdateSentHideCars; + } + sender.HandshakeAccepted += OnHandshakeAccepted; + }; + } - server.Update += MainLoop; + private void OnHandshakeAccepted(ACTcpClient sender, HandshakeAcceptedEventArgs args) + { + // Gracefully despawn AI cars + GetAiCarBySessionId(sender.SessionId).SetAiOverbooking(0); } - private void OnResetCar(ACTcpClient sender) + private void OnFirstUpdateSentHideCars(ACTcpClient sender, EventArgs args) { - if (_configuration.EnableCarReset) - sender.EntryCar.TryResetPosition(); + sender.SendPacket(new CSPCarVisibilityUpdate + { + SessionId = sender.SessionId, + Visible = sender.EntryCar.AiControlled ? CSPCarVisibility.Invisible : CSPCarVisibility.Visible + }); } - protected override Task ExecuteAsync(CancellationToken stoppingToken) + private void OnResetCar(ACTcpClient sender) { - Log.Debug("Sample plugin autostart called"); - return Task.CompletedTask; + if (_configuration.EnableCarReset) + GetAiCarBySessionId(sender.SessionId).TryResetPosition(); } - private void MainLoop(ACServer server, EventArgs args) + public EntryCarTrafficAi GetAiCarBySessionId(byte sessionId) + => Instances.First(x => x.EntryCar.SessionId == sessionId); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - + foreach (var car in _entryCarManager.EntryCars) + { + var entry = _serverConfiguration.EntryList.Cars[car.SessionId]; + + if (_configuration.AutoAssignTrafficCars && entry.Model.Contains("traffic")) + { + entry.AiMode = AiMode.Fixed; + } + + car.AiMode = entry.AiMode; + car.AiControlled = entry.AiMode != AiMode.None; + + Instances.Add(_entryCarTrafficAiFactory(car)); + } } } diff --git a/TrafficAiPlugin/TrafficAiCommandModule.cs b/TrafficAiPlugin/TrafficAiCommandModule.cs index b00bfd2e..ec3406b6 100644 --- a/TrafficAiPlugin/TrafficAiCommandModule.cs +++ b/TrafficAiPlugin/TrafficAiCommandModule.cs @@ -16,18 +16,23 @@ public class TrafficAiCommandModule : ACModuleBase private readonly ACServerConfiguration _serverConfiguration; private readonly TrafficAiConfiguration _configuration; private readonly EntryCarManager _entryCarManager; - - public TrafficAiCommandModule(ACServerConfiguration serverConfiguration, TrafficAiConfiguration configuration, EntryCarManager entryCarManager) + private readonly TrafficAi _trafficAi; + + public TrafficAiCommandModule(ACServerConfiguration serverConfiguration, + TrafficAiConfiguration configuration, + EntryCarManager entryCarManager, + TrafficAi trafficAi) { _serverConfiguration = serverConfiguration; _configuration = configuration; _entryCarManager = entryCarManager; + _trafficAi = trafficAi; } [Command("setaioverbooking")] public void SetAiOverbooking(int count) { - foreach (var aiCar in _entryCarManager.EntryCars.Where(car => car.AiControlled && car.Client == null)) + foreach (var aiCar in _trafficAi.Instances.Where(car => car.EntryCar is { AiControlled: true, Client: null })) { aiCar.SetAiOverbooking(count); } @@ -40,7 +45,7 @@ public void ResetCarAsync() if (_serverConfiguration.Extra is { EnableClientMessages: true, MinimumCSPVersion: >= CSPVersion.V0_2_3_p47 } && _configuration.EnableCarReset) { - Reply(Client!.EntryCar.TryResetPosition() + Reply(_trafficAi.GetAiCarBySessionId(Client!.SessionId).TryResetPosition() ? "Position successfully reset" : "Couldn't reset position"); } diff --git a/TrafficAiPlugin/TrafficAiModule.cs b/TrafficAiPlugin/TrafficAiModule.cs index b2ed90a6..fb8075c3 100644 --- a/TrafficAiPlugin/TrafficAiModule.cs +++ b/TrafficAiPlugin/TrafficAiModule.cs @@ -14,13 +14,12 @@ public class TrafficAiModule : AssettoServerModule protected override void Load(ContainerBuilder builder) { builder.RegisterType().AsSelf().As().SingleInstance(); - - + builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf().As().SingleInstance(); - builder.RegisterType().AsSelf().SingleInstance().AutoActivate(); + builder.RegisterType().AsSelf().SingleInstance().AutoActivate(); builder.RegisterType().As(); builder.RegisterType().As().SingleInstance(); diff --git a/TrafficAiPlugin/TrafficAiPlugin.csproj b/TrafficAiPlugin/TrafficAiPlugin.csproj index 7db1fff3..ad095835 100644 --- a/TrafficAiPlugin/TrafficAiPlugin.csproj +++ b/TrafficAiPlugin/TrafficAiPlugin.csproj @@ -9,7 +9,6 @@ embedded ..\out-$(RuntimeIdentifier)\plugins\$(MSBuildProjectName)\ $(MSBuildProjectDirectory)=$(MSBuildProjectName) - TrafficAIPlugin @@ -28,4 +27,11 @@ + + + + + + + diff --git a/TrafficAiPlugin/TrafficAiUpdater.cs b/TrafficAiPlugin/TrafficAiUpdater.cs new file mode 100644 index 00000000..1d63470c --- /dev/null +++ b/TrafficAiPlugin/TrafficAiUpdater.cs @@ -0,0 +1,100 @@ +using AssettoServer.Network.ClientMessages; +using AssettoServer.Network.Tcp; +using AssettoServer.Server; +using AssettoServer.Server.Configuration; +using AssettoServer.Server.Plugin; +using AssettoServer.Shared.Network.Packets.Outgoing; +using AssettoServer.Shared.Services; +using AssettoServer.Utils; +using Microsoft.Extensions.Hosting; +using Serilog; +using TrafficAiConfiguration = TrafficAIPlugin.Configuration.TrafficAiConfiguration; + +namespace TrafficAIPlugin; + +public class TrafficAiUpdater +{ + private readonly EntryCarManager _entryCarManager; + private readonly SessionManager _sessionManager; + private readonly TrafficAi _trafficAi; + + public TrafficAiUpdater( + EntryCarManager entryCarManager, + SessionManager sessionManager, + ACServer server, + TrafficAi trafficAi) + { + _entryCarManager = entryCarManager; + _sessionManager = sessionManager; + _trafficAi = trafficAi; + + server.Update += OnUpdate; + } + + private void OnUpdate(object sender, EventArgs args) + { + try + { + foreach (var instance in _trafficAi.Instances) + { + instance.AiUpdate(); + } + + Dictionary> positionUpdates = new(); + foreach (var entryCar in _entryCarManager.EntryCars) + { + positionUpdates[entryCar] = []; + } + + foreach (var fromCar in _trafficAi.Instances) + { + if (!fromCar.EntryCar.AiControlled) continue; + + foreach (var (_, toCar) in _entryCarManager.ConnectedCars) + { + var toClient = toCar.Client; + if (toCar == fromCar.EntryCar + || toClient == null || !toClient.HasSentFirstUpdate || !toClient.HasUdpEndpoint + || !fromCar.GetPositionUpdateForCar(toCar, out var update)) continue; + + if (toClient.SupportsCSPCustomUpdate) + { + positionUpdates[toCar].Add(update); + } + else + { + toClient.SendPacketUdp(in update); + } + } + } + + foreach (var (toCar, updates) in positionUpdates) + { + if (updates.Count == 0) continue; + + var toClient = toCar.Client; + if (toClient == null) continue; + + const int chunkSize = 20; + for (int i = 0; i < updates.Count; i += chunkSize) + { + if (toClient.SupportsCSPCustomUpdate) + { + var packet = new CSPPositionUpdate(new ArraySegment(updates.ToArray(), i, Math.Min(chunkSize, updates.Count - i))); + toClient.SendPacketUdp(in packet); + } + else + { + var packet = new BatchedPositionUpdate((uint)(_sessionManager.ServerTimeMilliseconds - toCar.TimeOffset), toCar.Ping, + new ArraySegment(updates.ToArray(), i, Math.Min(chunkSize, updates.Count - i))); + toClient.SendPacketUdp(in packet); + } + } + } + } + catch (Exception ex) + { + Log.Error(ex, "Error during ghost car update"); + } + } +} diff --git a/TrafficAiPlugin/ai_debug.lua b/TrafficAiPlugin/lua/ai_debug.lua similarity index 100% rename from TrafficAiPlugin/ai_debug.lua rename to TrafficAiPlugin/lua/ai_debug.lua From 1bed2e7484e56bac9e5e0fbdf3aa30f3cc32fcd4 Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:40:25 +0100 Subject: [PATCH 03/10] Moved ResetPacket to Plugin --- .../Packets}/RequestResetPacket.cs | 4 +++- TrafficAiPlugin/TrafficAi.cs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) rename {AssettoServer/Network/ClientMessages => TrafficAiPlugin/Packets}/RequestResetPacket.cs (68%) diff --git a/AssettoServer/Network/ClientMessages/RequestResetPacket.cs b/TrafficAiPlugin/Packets/RequestResetPacket.cs similarity index 68% rename from AssettoServer/Network/ClientMessages/RequestResetPacket.cs rename to TrafficAiPlugin/Packets/RequestResetPacket.cs index a62b0b16..85993e5e 100644 --- a/AssettoServer/Network/ClientMessages/RequestResetPacket.cs +++ b/TrafficAiPlugin/Packets/RequestResetPacket.cs @@ -1,4 +1,6 @@ -namespace AssettoServer.Network.ClientMessages; +using AssettoServer.Network.ClientMessages; + +namespace TrafficAiPlugin.Packets; [OnlineEvent(Key = "AS_RequestResetCar")] public class RequestResetPacket : OnlineEvent diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs index 522f64c2..b5e0fd9c 100644 --- a/TrafficAiPlugin/TrafficAi.cs +++ b/TrafficAiPlugin/TrafficAi.cs @@ -8,6 +8,7 @@ using AssettoServer.Utils; using Microsoft.Extensions.Hosting; using Serilog; +using TrafficAiPlugin.Packets; using TrafficAiConfiguration = TrafficAIPlugin.Configuration.TrafficAiConfiguration; namespace TrafficAIPlugin; From b3d463761ba5c2a6fc792601369dc1a0bacb754e Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Sat, 1 Mar 2025 02:27:00 +0100 Subject: [PATCH 04/10] Added configuration.json --- AutoModerationPlugin/AutoModerationPlugin.csproj | 5 ++++- ReplayPlugin/ReplayPlugin.csproj | 5 ++++- TrafficAiPlugin/TrafficAiPlugin.csproj | 11 ++++++++++- TrafficAiPlugin/configuration.json | 5 +++++ 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 TrafficAiPlugin/configuration.json diff --git a/AutoModerationPlugin/AutoModerationPlugin.csproj b/AutoModerationPlugin/AutoModerationPlugin.csproj index 398db292..15bccd43 100644 --- a/AutoModerationPlugin/AutoModerationPlugin.csproj +++ b/AutoModerationPlugin/AutoModerationPlugin.csproj @@ -25,7 +25,10 @@ false runtime - + + all + CopyConfigurationJson=false + diff --git a/ReplayPlugin/ReplayPlugin.csproj b/ReplayPlugin/ReplayPlugin.csproj index 90e4c3ff..8f9f5211 100644 --- a/ReplayPlugin/ReplayPlugin.csproj +++ b/ReplayPlugin/ReplayPlugin.csproj @@ -25,7 +25,10 @@ false runtime - + + all + CopyConfigurationJson=false + diff --git a/TrafficAiPlugin/TrafficAiPlugin.csproj b/TrafficAiPlugin/TrafficAiPlugin.csproj index ad095835..7959dd25 100644 --- a/TrafficAiPlugin/TrafficAiPlugin.csproj +++ b/TrafficAiPlugin/TrafficAiPlugin.csproj @@ -27,11 +27,20 @@ - + + + true + + + + + PreserveNewest + + diff --git a/TrafficAiPlugin/configuration.json b/TrafficAiPlugin/configuration.json new file mode 100644 index 00000000..db2bef81 --- /dev/null +++ b/TrafficAiPlugin/configuration.json @@ -0,0 +1,5 @@ +{ + "ExportedAssemblies": [ + "TrafficAiPlugin.dll" + ] +} From 26151faed45276b4e4902029b3af8d6de4ec221b Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Wed, 5 Mar 2025 01:43:27 +0100 Subject: [PATCH 05/10] Added a Shared lib for Interfaces AutoMod now picks up the spline correctly --- AssettoServer.sln | 6 ++++ AutoModerationPlugin/AutoModerationPlugin.cs | 4 +-- .../AutoModerationPlugin.csproj | 3 +- .../EntryCarAutoModeration.cs | 16 ++++----- ReplayPlugin/Data/ReplayFrameState.cs | 4 +-- ReplayPlugin/ReplayPlugin.cs | 8 ++--- ReplayPlugin/ReplayPlugin.csproj | 3 +- TrafficAiPlugin.Shared/IAiSpline.cs | 9 +++++ TrafficAiPlugin.Shared/IAiState.cs | 9 +++++ TrafficAiPlugin.Shared/IEntryCarTrafficAi.cs | 13 ++++++++ TrafficAiPlugin.Shared/ITrafficAi.cs | 7 ++++ .../TrafficAiPlugin.Shared.csproj | 33 +++++++++++++++++++ TrafficAiPlugin/AiBehavior.cs | 6 ++-- TrafficAiPlugin/AiSlotFilter.cs | 5 ++- TrafficAiPlugin/AiState.cs | 10 +++--- .../Configuration/CarSpecificOverrides.cs | 2 +- TrafficAiPlugin/Configuration/Indicator.cs | 2 +- .../Configuration/JunctionRecord.cs | 2 +- .../LaneCountSpecificOverrides.cs | 2 +- .../Configuration/LaneSpawnBehavior.cs | 2 +- TrafficAiPlugin/Configuration/Sphere.cs | 2 +- .../Configuration/SplineConfiguration.cs | 2 +- .../Configuration/TrafficAIConfiguration.cs | 2 +- .../TrafficAiConfigurationValidator.cs | 2 +- .../Configuration/TrafficConfiguration.cs | 2 +- TrafficAiPlugin/DynamicTrafficDensity.cs | 4 +-- TrafficAiPlugin/EntryCarTrafficAi.cs | 15 ++++----- .../Splines/AdjacentLaneDetector.cs | 2 +- TrafficAiPlugin/Splines/AiSpline.cs | 8 +++-- TrafficAiPlugin/Splines/AiSplineHeader.cs | 2 +- TrafficAiPlugin/Splines/AiSplineLocator.cs | 2 +- TrafficAiPlugin/Splines/AiSplineWriter.cs | 2 +- TrafficAiPlugin/Splines/FastLane.cs | 2 +- TrafficAiPlugin/Splines/FastLaneParser.cs | 4 +-- TrafficAiPlugin/Splines/JunctionEvaluator.cs | 2 +- TrafficAiPlugin/Splines/MutableAiSpline.cs | 4 +-- TrafficAiPlugin/Splines/SlowestAiStates.cs | 2 +- TrafficAiPlugin/Splines/SplineJunction.cs | 2 +- TrafficAiPlugin/Splines/SplinePoint.cs | 2 +- .../Splines/SplinePointOperations.cs | 2 +- TrafficAiPlugin/TrafficAi.cs | 20 ++++++++--- TrafficAiPlugin/TrafficAiCommandModule.cs | 4 +-- TrafficAiPlugin/TrafficAiModule.cs | 15 +++++---- TrafficAiPlugin/TrafficAiPlugin.csproj | 3 ++ TrafficAiPlugin/TrafficAiUpdater.cs | 12 ++----- TrafficAiPlugin/configuration.json | 2 +- 46 files changed, 175 insertions(+), 92 deletions(-) create mode 100644 TrafficAiPlugin.Shared/IAiSpline.cs create mode 100644 TrafficAiPlugin.Shared/IAiState.cs create mode 100644 TrafficAiPlugin.Shared/IEntryCarTrafficAi.cs create mode 100644 TrafficAiPlugin.Shared/ITrafficAi.cs create mode 100644 TrafficAiPlugin.Shared/TrafficAiPlugin.Shared.csproj diff --git a/AssettoServer.sln b/AssettoServer.sln index b0e91006..bfcd2d05 100644 --- a/AssettoServer.sln +++ b/AssettoServer.sln @@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReplayPlugin", "ReplayPlugi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafficAiPlugin", "TrafficAiPlugin\TrafficAiPlugin.csproj", "{F20A785C-686A-40A4-8821-28627BA6FA48}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrafficAiPlugin.Shared", "TrafficAiPlugin.Shared\TrafficAiPlugin.Shared.csproj", "{18EE29A8-AA8F-47AD-849B-AD5D04C5A069}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,6 +125,10 @@ Global {F20A785C-686A-40A4-8821-28627BA6FA48}.Debug|Any CPU.Build.0 = Debug|Any CPU {F20A785C-686A-40A4-8821-28627BA6FA48}.Release|Any CPU.ActiveCfg = Release|Any CPU {F20A785C-686A-40A4-8821-28627BA6FA48}.Release|Any CPU.Build.0 = Release|Any CPU + {18EE29A8-AA8F-47AD-849B-AD5D04C5A069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18EE29A8-AA8F-47AD-849B-AD5D04C5A069}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18EE29A8-AA8F-47AD-849B-AD5D04C5A069}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18EE29A8-AA8F-47AD-849B-AD5D04C5A069}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AutoModerationPlugin/AutoModerationPlugin.cs b/AutoModerationPlugin/AutoModerationPlugin.cs index 352257ae..44d49b65 100644 --- a/AutoModerationPlugin/AutoModerationPlugin.cs +++ b/AutoModerationPlugin/AutoModerationPlugin.cs @@ -8,7 +8,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Hosting; using Serilog; -using TrafficAIPlugin.Splines; +using TrafficAiPlugin.Shared; namespace AutoModerationPlugin; @@ -29,7 +29,7 @@ public AutoModerationPlugin(AutoModerationConfiguration configuration, CSPServerScriptProvider scriptProvider, Func entryCarAutoModerationFactory, IHostApplicationLifetime applicationLifetime, - AiSpline? aiSpline = null) : base(applicationLifetime) + IAiSpline? aiSpline = null) : base(applicationLifetime) { _configuration = configuration; _entryCarManager = entryCarManager; diff --git a/AutoModerationPlugin/AutoModerationPlugin.csproj b/AutoModerationPlugin/AutoModerationPlugin.csproj index 15bccd43..f92d5763 100644 --- a/AutoModerationPlugin/AutoModerationPlugin.csproj +++ b/AutoModerationPlugin/AutoModerationPlugin.csproj @@ -25,9 +25,8 @@ false runtime - + all - CopyConfigurationJson=false diff --git a/AutoModerationPlugin/EntryCarAutoModeration.cs b/AutoModerationPlugin/EntryCarAutoModeration.cs index e7ded132..7d52d3bd 100644 --- a/AutoModerationPlugin/EntryCarAutoModeration.cs +++ b/AutoModerationPlugin/EntryCarAutoModeration.cs @@ -7,9 +7,7 @@ using AssettoServer.Shared.Network.Packets.Outgoing; using AssettoServer.Shared.Network.Packets.Shared; using AutoModerationPlugin.Packets; -using Serilog; -using TrafficAIPlugin.Configuration; -using TrafficAIPlugin.Splines; +using TrafficAiPlugin.Shared; namespace AutoModerationPlugin; @@ -43,7 +41,7 @@ public class EntryCarAutoModeration private const double NauticalTwilight = -12.0 * Math.PI / 180.0; private readonly EntryCar _entryCar; - private readonly AiSpline? _aiSpline; + private readonly IAiSpline? _aiSpline; private readonly ACServerConfiguration _serverConfiguration; private readonly AutoModerationConfiguration _configuration; private readonly EntryCarManager _entryCarManager; @@ -57,8 +55,8 @@ public EntryCarAutoModeration(EntryCar entryCar, WeatherManager weatherManager, SessionManager sessionManager, ACServerConfiguration serverConfiguration, - TrafficAiConfiguration? trafficAiConfiguration = null, - AiSpline? aiSpline = null) + ITrafficAi? trafficAi = null, + IAiSpline? aiSpline = null) { _entryCar = entryCar; _configuration = configuration; @@ -73,8 +71,8 @@ public EntryCarAutoModeration(EntryCar entryCar, _entryCar.PositionUpdateReceived += OnPositionUpdateReceived; } - if (trafficAiConfiguration != null) - _laneRadiusSquared = MathF.Pow(trafficAiConfiguration.LaneWidthMeters / 2.0f * 1.25f, 2); + if (trafficAi != null) + _laneRadiusSquared = MathF.Pow(trafficAi.GetLaneWidthMeters() / 2.0f * 1.25f, 2); } private void OnPositionUpdateReceived(EntryCar sender, in PositionUpdateIn positionUpdate) @@ -245,7 +243,7 @@ private void UpdateWrongWayPenalty(ACTcpClient client) if (CurrentSplinePointId >= 0 && CurrentSplinePointDistanceSquared < _laneRadiusSquared && _entryCar.Status.Velocity.LengthSquared() > _configuration.WrongWayPenalty.MinimumSpeedMs * _configuration.WrongWayPenalty.MinimumSpeedMs - && Vector3.Dot(_aiSpline.Operations.GetForwardVector(CurrentSplinePointId), _entryCar.Status.Velocity) < 0) + && Vector3.Dot(_aiSpline.GetForwardVector(CurrentSplinePointId), _entryCar.Status.Velocity) < 0) { CurrentFlags |= Flags.WrongWay; diff --git a/ReplayPlugin/Data/ReplayFrameState.cs b/ReplayPlugin/Data/ReplayFrameState.cs index 33e924af..73eef69f 100644 --- a/ReplayPlugin/Data/ReplayFrameState.cs +++ b/ReplayPlugin/Data/ReplayFrameState.cs @@ -1,6 +1,6 @@ using AssettoServer.Shared.Model; using Microsoft.Extensions.ObjectPool; -using TrafficAIPlugin; +using TrafficAiPlugin.Shared; namespace ReplayPlugin.Data; @@ -8,7 +8,7 @@ public class ReplayFrameState : IResettable { public readonly List> PlayerCars = []; public readonly List> AiCars = []; - public readonly Dictionary AiStateMapping = []; + public readonly Dictionary AiStateMapping = []; public readonly Dictionary> AiFrameMapping = []; public bool TryReset() diff --git a/ReplayPlugin/ReplayPlugin.cs b/ReplayPlugin/ReplayPlugin.cs index 34d247bb..7ab74cfa 100644 --- a/ReplayPlugin/ReplayPlugin.cs +++ b/ReplayPlugin/ReplayPlugin.cs @@ -12,7 +12,7 @@ using ReplayPlugin.Data; using ReplayPlugin.Packets; using Serilog; -using TrafficAIPlugin; +using TrafficAiPlugin.Shared; namespace ReplayPlugin; @@ -25,7 +25,7 @@ public class ReplayPlugin : CriticalBackgroundService, IAssettoServerAutostart private readonly ReplayManager _replayManager; private readonly Summary _onUpdateTimer; private readonly EntryCarExtraDataManager _extraData; - private readonly TrafficAi? _trafficAi; + private readonly ITrafficAi? _trafficAi; public ReplayPlugin(IHostApplicationLifetime applicationLifetime, EntryCarManager entryCarManager, @@ -36,7 +36,7 @@ public ReplayPlugin(IHostApplicationLifetime applicationLifetime, CSPServerScriptProvider scriptProvider, CSPClientMessageTypeManager cspClientMessageTypeManager, EntryCarExtraDataManager extraData, - TrafficAi? trafficAi = null) : base(applicationLifetime) + ITrafficAi? trafficAi = null) : base(applicationLifetime) { _entryCarManager = entryCarManager; _weather = weather; @@ -91,7 +91,7 @@ private void Update() { aiStateId = (short)_state.AiCars.Count; _state.AiStateMapping.Add(aiState, aiStateId); - _state.AiCars.Add((aiState.EntryCarAi.EntryCar.SessionId, aiState.Status)); + _state.AiCars.Add((aiState.SessionId, aiState.Status)); } if (_state.AiFrameMapping.TryGetValue((byte)i, out var aiFrameMappingList)) diff --git a/ReplayPlugin/ReplayPlugin.csproj b/ReplayPlugin/ReplayPlugin.csproj index 8f9f5211..c2b43c03 100644 --- a/ReplayPlugin/ReplayPlugin.csproj +++ b/ReplayPlugin/ReplayPlugin.csproj @@ -25,9 +25,8 @@ false runtime - + all - CopyConfigurationJson=false diff --git a/TrafficAiPlugin.Shared/IAiSpline.cs b/TrafficAiPlugin.Shared/IAiSpline.cs new file mode 100644 index 00000000..4b5604ef --- /dev/null +++ b/TrafficAiPlugin.Shared/IAiSpline.cs @@ -0,0 +1,9 @@ +using System.Numerics; + +namespace TrafficAiPlugin.Shared; + +public interface IAiSpline +{ + public Vector3 GetForwardVector(int pointId); + public (int PointId, float DistanceSquared) WorldToSpline(Vector3 position); +} diff --git a/TrafficAiPlugin.Shared/IAiState.cs b/TrafficAiPlugin.Shared/IAiState.cs new file mode 100644 index 00000000..48572dcc --- /dev/null +++ b/TrafficAiPlugin.Shared/IAiState.cs @@ -0,0 +1,9 @@ +using AssettoServer.Shared.Model; + +namespace TrafficAiPlugin.Shared; + +public interface IAiState +{ + public CarStatus Status { get; } + public byte SessionId { get; } +} diff --git a/TrafficAiPlugin.Shared/IEntryCarTrafficAi.cs b/TrafficAiPlugin.Shared/IEntryCarTrafficAi.cs new file mode 100644 index 00000000..825baa9f --- /dev/null +++ b/TrafficAiPlugin.Shared/IEntryCarTrafficAi.cs @@ -0,0 +1,13 @@ +using AssettoServer.Server; + +namespace TrafficAiPlugin.Shared; + +public interface IEntryCarTrafficAi +{ + public IAiState?[] LastSeenAiState { get; } + public EntryCar EntryCar { get; } + + public void SetAiOverbooking(int count); + public bool TryResetPosition(); + public void AiUpdate(); +} diff --git a/TrafficAiPlugin.Shared/ITrafficAi.cs b/TrafficAiPlugin.Shared/ITrafficAi.cs new file mode 100644 index 00000000..6b26a063 --- /dev/null +++ b/TrafficAiPlugin.Shared/ITrafficAi.cs @@ -0,0 +1,7 @@ +namespace TrafficAiPlugin.Shared; + +public interface ITrafficAi +{ + public IEntryCarTrafficAi GetAiCarBySessionId(byte sessionId); + public float GetLaneWidthMeters(); +} diff --git a/TrafficAiPlugin.Shared/TrafficAiPlugin.Shared.csproj b/TrafficAiPlugin.Shared/TrafficAiPlugin.Shared.csproj new file mode 100644 index 00000000..e8e54e06 --- /dev/null +++ b/TrafficAiPlugin.Shared/TrafficAiPlugin.Shared.csproj @@ -0,0 +1,33 @@ + + + + net9.0 + enable + enable + true + false + embedded + ..\out-$(RuntimeIdentifier)\plugins\$(MSBuildProjectName)\ + $(MSBuildProjectDirectory)=$(MSBuildProjectName) + + + + false + ..\AssettoServer\bin\$(Configuration)\$(TargetFramework)\plugins\$(MSBuildProjectName) + + + + + false + runtime + + + false + runtime + + + + + + + diff --git a/TrafficAiPlugin/AiBehavior.cs b/TrafficAiPlugin/AiBehavior.cs index e06641e0..7d3e48f0 100644 --- a/TrafficAiPlugin/AiBehavior.cs +++ b/TrafficAiPlugin/AiBehavior.cs @@ -11,10 +11,10 @@ using Microsoft.Extensions.Hosting; using Prometheus; using Serilog; -using TrafficAIPlugin.Configuration; -using TrafficAIPlugin.Splines; +using TrafficAiPlugin.Configuration; +using TrafficAiPlugin.Splines; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; public class AiBehavior : CriticalBackgroundService, IAssettoServerAutostart { diff --git a/TrafficAiPlugin/AiSlotFilter.cs b/TrafficAiPlugin/AiSlotFilter.cs index a15fa052..449d065b 100644 --- a/TrafficAiPlugin/AiSlotFilter.cs +++ b/TrafficAiPlugin/AiSlotFilter.cs @@ -1,9 +1,8 @@ using AssettoServer.Server; -using AssettoServer.Server.Configuration; using AssettoServer.Server.OpenSlotFilters; -using TrafficAIPlugin.Configuration; +using TrafficAiPlugin.Configuration; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; public class AiSlotFilter : OpenSlotFilterBase { diff --git a/TrafficAiPlugin/AiState.cs b/TrafficAiPlugin/AiState.cs index 44f5b3f4..aa450dc4 100644 --- a/TrafficAiPlugin/AiState.cs +++ b/TrafficAiPlugin/AiState.cs @@ -9,13 +9,15 @@ using JPBotelho; using Serilog; using SunCalcNet.Model; -using TrafficAIPlugin.Configuration; -using TrafficAIPlugin.Splines; +using TrafficAiPlugin.Configuration; +using TrafficAiPlugin.Shared; +using TrafficAiPlugin.Splines; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; -public class AiState : IDisposable +public class AiState : IAiState, IDisposable { + public byte SessionId => EntryCarAi.EntryCar.SessionId; public CarStatus Status { get; } = new(); public bool Initialized { get; private set; } diff --git a/TrafficAiPlugin/Configuration/CarSpecificOverrides.cs b/TrafficAiPlugin/Configuration/CarSpecificOverrides.cs index 84311859..e041cd10 100644 --- a/TrafficAiPlugin/Configuration/CarSpecificOverrides.cs +++ b/TrafficAiPlugin/Configuration/CarSpecificOverrides.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class CarSpecificOverrides diff --git a/TrafficAiPlugin/Configuration/Indicator.cs b/TrafficAiPlugin/Configuration/Indicator.cs index f14cb555..21d1a0d2 100644 --- a/TrafficAiPlugin/Configuration/Indicator.cs +++ b/TrafficAiPlugin/Configuration/Indicator.cs @@ -1,4 +1,4 @@ -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; public enum Indicator { diff --git a/TrafficAiPlugin/Configuration/JunctionRecord.cs b/TrafficAiPlugin/Configuration/JunctionRecord.cs index 0edebb85..d484ec71 100644 --- a/TrafficAiPlugin/Configuration/JunctionRecord.cs +++ b/TrafficAiPlugin/Configuration/JunctionRecord.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class JunctionRecord diff --git a/TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs b/TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs index 1d95eedf..65c6acc2 100644 --- a/TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs +++ b/TrafficAiPlugin/Configuration/LaneCountSpecificOverrides.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class LaneCountSpecificOverrides diff --git a/TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs b/TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs index 64a6ce17..1deaffa4 100644 --- a/TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs +++ b/TrafficAiPlugin/Configuration/LaneSpawnBehavior.cs @@ -1,4 +1,4 @@ -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; public enum LaneSpawnBehavior { diff --git a/TrafficAiPlugin/Configuration/Sphere.cs b/TrafficAiPlugin/Configuration/Sphere.cs index 669bb5f1..5421eb57 100644 --- a/TrafficAiPlugin/Configuration/Sphere.cs +++ b/TrafficAiPlugin/Configuration/Sphere.cs @@ -1,7 +1,7 @@ using System.Numerics; using JetBrains.Annotations; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class Sphere diff --git a/TrafficAiPlugin/Configuration/SplineConfiguration.cs b/TrafficAiPlugin/Configuration/SplineConfiguration.cs index f047b735..abf6235a 100644 --- a/TrafficAiPlugin/Configuration/SplineConfiguration.cs +++ b/TrafficAiPlugin/Configuration/SplineConfiguration.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class SplineConfiguration diff --git a/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs b/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs index 831c1e97..54af891c 100644 --- a/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs +++ b/TrafficAiPlugin/Configuration/TrafficAIConfiguration.cs @@ -4,7 +4,7 @@ using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; #pragma warning disable CS0657 [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] diff --git a/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs b/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs index 6894e472..82ad6052 100644 --- a/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs +++ b/TrafficAiPlugin/Configuration/TrafficAiConfigurationValidator.cs @@ -1,7 +1,7 @@ using FluentValidation; using JetBrains.Annotations; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; // Use FluentValidation to validate plugin configuration [UsedImplicitly] diff --git a/TrafficAiPlugin/Configuration/TrafficConfiguration.cs b/TrafficAiPlugin/Configuration/TrafficConfiguration.cs index 7c67d48c..c5a64789 100644 --- a/TrafficAiPlugin/Configuration/TrafficConfiguration.cs +++ b/TrafficAiPlugin/Configuration/TrafficConfiguration.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace TrafficAIPlugin.Configuration; +namespace TrafficAiPlugin.Configuration; [UsedImplicitly(ImplicitUseKindFlags.Assign, ImplicitUseTargetFlags.WithMembers)] public class TrafficConfiguration diff --git a/TrafficAiPlugin/DynamicTrafficDensity.cs b/TrafficAiPlugin/DynamicTrafficDensity.cs index acddf3fc..18063b85 100644 --- a/TrafficAiPlugin/DynamicTrafficDensity.cs +++ b/TrafficAiPlugin/DynamicTrafficDensity.cs @@ -4,9 +4,9 @@ using AssettoServer.Utils; using Microsoft.Extensions.Hosting; using Serilog; -using TrafficAIPlugin.Configuration; +using TrafficAiPlugin.Configuration; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; public class DynamicTrafficDensity : CriticalBackgroundService { diff --git a/TrafficAiPlugin/EntryCarTrafficAi.cs b/TrafficAiPlugin/EntryCarTrafficAi.cs index 0fdfa1ea..7b64b942 100644 --- a/TrafficAiPlugin/EntryCarTrafficAi.cs +++ b/TrafficAiPlugin/EntryCarTrafficAi.cs @@ -6,18 +6,18 @@ using AssettoServer.Shared.Model; using AssettoServer.Shared.Network.Packets.Outgoing; using AssettoServer.Shared.Network.Packets.Shared; -using Grpc.Core; -using TrafficAIPlugin.Configuration; -using TrafficAIPlugin.Splines; +using TrafficAiPlugin.Configuration; +using TrafficAiPlugin.Shared; +using TrafficAiPlugin.Splines; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; -public class EntryCarTrafficAi +public class EntryCarTrafficAi : IEntryCarTrafficAi { public int TargetAiStateCount { get; private set; } = 1; public byte[] LastSeenAiSpawn { get; } public byte[] AiPakSequenceIds { get; } - public AiState?[] LastSeenAiState { get; } + public IAiState?[] LastSeenAiState { get; } public bool AiEnableColorChanges => EntryCar.DriverOptionsFlags.HasFlag(DriverOptionsFlags.AllowColorChange); public int AiIdleEngineRpm { get; set; } = 800; public int AiMaxEngineRpm { get; set; } = 3000; @@ -46,7 +46,7 @@ public class EntryCarTrafficAi private readonly Func _aiStateFactory; private readonly TrafficAi _trafficAi; - public readonly EntryCar EntryCar; + public EntryCar EntryCar { get; } private readonly ACServerConfiguration _serverConfiguration; private readonly TrafficAiConfiguration _configuration; @@ -71,7 +71,6 @@ public EntryCarTrafficAi(EntryCar entryCar, _aiStateFactory = aiStateFactory; _trafficAi = trafficAi; _aiSpline = aiSpline; - AiPakSequenceIds = new byte[entryCarManager.EntryCars.Length]; LastSeenAiState = new AiState[entryCarManager.EntryCars.Length]; diff --git a/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs b/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs index 686548e5..e27dce8c 100644 --- a/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs +++ b/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs @@ -2,7 +2,7 @@ using Serilog; using SerilogTimings; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public static class AdjacentLaneDetector { diff --git a/TrafficAiPlugin/Splines/AiSpline.cs b/TrafficAiPlugin/Splines/AiSpline.cs index 52c5af15..68a09647 100644 --- a/TrafficAiPlugin/Splines/AiSpline.cs +++ b/TrafficAiPlugin/Splines/AiSpline.cs @@ -7,10 +7,11 @@ using DotNext.Runtime.InteropServices; using Serilog; using Supercluster.KDTree; +using TrafficAiPlugin.Shared; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; -public class AiSpline : IDisposable +public class AiSpline : IAiSpline, IDisposable { public const int SupportedVersion = 1; @@ -100,4 +101,7 @@ public void Dispose() _fileAccessor.Dispose(); _file.Dispose(); } + + public Vector3 GetForwardVector(int pointId) + => Operations.GetForwardVector(pointId); } diff --git a/TrafficAiPlugin/Splines/AiSplineHeader.cs b/TrafficAiPlugin/Splines/AiSplineHeader.cs index f4d9b64f..b650084c 100644 --- a/TrafficAiPlugin/Splines/AiSplineHeader.cs +++ b/TrafficAiPlugin/Splines/AiSplineHeader.cs @@ -1,4 +1,4 @@ -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public struct AiSplineHeader { diff --git a/TrafficAiPlugin/Splines/AiSplineLocator.cs b/TrafficAiPlugin/Splines/AiSplineLocator.cs index 5a60ab68..1c06922d 100644 --- a/TrafficAiPlugin/Splines/AiSplineLocator.cs +++ b/TrafficAiPlugin/Splines/AiSplineLocator.cs @@ -3,7 +3,7 @@ using Serilog; using SerilogTimings; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class AiSplineLocator { diff --git a/TrafficAiPlugin/Splines/AiSplineWriter.cs b/TrafficAiPlugin/Splines/AiSplineWriter.cs index 7225fa1b..d9f412f5 100644 --- a/TrafficAiPlugin/Splines/AiSplineWriter.cs +++ b/TrafficAiPlugin/Splines/AiSplineWriter.cs @@ -1,7 +1,7 @@ using AssettoServer.Utils; using Serilog; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class AiSplineWriter { diff --git a/TrafficAiPlugin/Splines/FastLane.cs b/TrafficAiPlugin/Splines/FastLane.cs index 7f7e8a18..f71223fa 100644 --- a/TrafficAiPlugin/Splines/FastLane.cs +++ b/TrafficAiPlugin/Splines/FastLane.cs @@ -1,4 +1,4 @@ -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class FastLane { diff --git a/TrafficAiPlugin/Splines/FastLaneParser.cs b/TrafficAiPlugin/Splines/FastLaneParser.cs index 2dbf86a8..9095a266 100644 --- a/TrafficAiPlugin/Splines/FastLaneParser.cs +++ b/TrafficAiPlugin/Splines/FastLaneParser.cs @@ -3,10 +3,10 @@ using AssettoServer.Server.Configuration; using AssettoServer.Utils; using Serilog; -using TrafficAIPlugin.Configuration; +using TrafficAiPlugin.Configuration; using YamlDotNet.Serialization; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class FastLaneParser { diff --git a/TrafficAiPlugin/Splines/JunctionEvaluator.cs b/TrafficAiPlugin/Splines/JunctionEvaluator.cs index 10405583..12b52269 100644 --- a/TrafficAiPlugin/Splines/JunctionEvaluator.cs +++ b/TrafficAiPlugin/Splines/JunctionEvaluator.cs @@ -1,6 +1,6 @@ using System.Collections.Concurrent; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class JunctionEvaluator { diff --git a/TrafficAiPlugin/Splines/MutableAiSpline.cs b/TrafficAiPlugin/Splines/MutableAiSpline.cs index 969201c3..b0e52310 100644 --- a/TrafficAiPlugin/Splines/MutableAiSpline.cs +++ b/TrafficAiPlugin/Splines/MutableAiSpline.cs @@ -2,9 +2,9 @@ using AssettoServer.Shared.Network.Packets.Outgoing; using Serilog; using Supercluster.KDTree; -using TrafficAIPlugin.Configuration; +using TrafficAiPlugin.Configuration; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class MutableAiSpline { diff --git a/TrafficAiPlugin/Splines/SlowestAiStates.cs b/TrafficAiPlugin/Splines/SlowestAiStates.cs index 7a218afc..059c50cc 100644 --- a/TrafficAiPlugin/Splines/SlowestAiStates.cs +++ b/TrafficAiPlugin/Splines/SlowestAiStates.cs @@ -1,4 +1,4 @@ -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public class SlowestAiStates { diff --git a/TrafficAiPlugin/Splines/SplineJunction.cs b/TrafficAiPlugin/Splines/SplineJunction.cs index 99b69bdd..fd48cbb6 100644 --- a/TrafficAiPlugin/Splines/SplineJunction.cs +++ b/TrafficAiPlugin/Splines/SplineJunction.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using AssettoServer.Shared.Network.Packets.Outgoing; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct SplineJunction diff --git a/TrafficAiPlugin/Splines/SplinePoint.cs b/TrafficAiPlugin/Splines/SplinePoint.cs index c1cad9d7..e7d7b127 100644 --- a/TrafficAiPlugin/Splines/SplinePoint.cs +++ b/TrafficAiPlugin/Splines/SplinePoint.cs @@ -1,7 +1,7 @@ using System.Numerics; using System.Runtime.InteropServices; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct SplinePoint diff --git a/TrafficAiPlugin/Splines/SplinePointOperations.cs b/TrafficAiPlugin/Splines/SplinePointOperations.cs index 94ef69a4..07d37ce7 100644 --- a/TrafficAiPlugin/Splines/SplinePointOperations.cs +++ b/TrafficAiPlugin/Splines/SplinePointOperations.cs @@ -2,7 +2,7 @@ using AssettoServer.Utils; using Serilog; -namespace TrafficAIPlugin.Splines; +namespace TrafficAiPlugin.Splines; public readonly ref struct SplinePointOperations { diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs index b5e0fd9c..a4fdc703 100644 --- a/TrafficAiPlugin/TrafficAi.cs +++ b/TrafficAiPlugin/TrafficAi.cs @@ -7,13 +7,14 @@ using AssettoServer.Shared.Services; using AssettoServer.Utils; using Microsoft.Extensions.Hosting; -using Serilog; using TrafficAiPlugin.Packets; -using TrafficAiConfiguration = TrafficAIPlugin.Configuration.TrafficAiConfiguration; +using TrafficAiPlugin.Shared; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; -public class TrafficAi : CriticalBackgroundService, IAssettoServerAutostart +using TrafficAiConfiguration = Configuration.TrafficAiConfiguration; + +public class TrafficAi : CriticalBackgroundService, IAssettoServerAutostart, ITrafficAi { private readonly TrafficAiConfiguration _configuration; private readonly ACServerConfiguration _serverConfiguration; @@ -80,8 +81,17 @@ private void OnResetCar(ACTcpClient sender) GetAiCarBySessionId(sender.SessionId).TryResetPosition(); } - public EntryCarTrafficAi GetAiCarBySessionId(byte sessionId) + // public EntryCarTrafficAi GetAiCarBySessionId(byte sessionId) + // => Instances.First(x => x.EntryCar.SessionId == sessionId); + + IEntryCarTrafficAi ITrafficAi.GetAiCarBySessionId(byte sessionId) + => GetAiCarBySessionId(sessionId); + + internal EntryCarTrafficAi GetAiCarBySessionId(byte sessionId) => Instances.First(x => x.EntryCar.SessionId == sessionId); + + public float GetLaneWidthMeters() + => _configuration.LaneWidthMeters; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { diff --git a/TrafficAiPlugin/TrafficAiCommandModule.cs b/TrafficAiPlugin/TrafficAiCommandModule.cs index ec3406b6..5383df98 100644 --- a/TrafficAiPlugin/TrafficAiCommandModule.cs +++ b/TrafficAiPlugin/TrafficAiCommandModule.cs @@ -5,9 +5,9 @@ using AssettoServer.Utils; using JetBrains.Annotations; using Qmmands; -using TrafficAIPlugin.Configuration; +using TrafficAiPlugin.Configuration; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; [RequireAdmin] [UsedImplicitly(ImplicitUseKindFlags.Access, ImplicitUseTargetFlags.WithMembers)] diff --git a/TrafficAiPlugin/TrafficAiModule.cs b/TrafficAiPlugin/TrafficAiModule.cs index fb8075c3..9b4389c8 100644 --- a/TrafficAiPlugin/TrafficAiModule.cs +++ b/TrafficAiPlugin/TrafficAiModule.cs @@ -3,20 +3,21 @@ using AssettoServer.Server.Plugin; using Autofac; using Microsoft.Extensions.Hosting; -using TrafficAIPlugin.Configuration; -using TrafficAIPlugin.Splines; +using TrafficAiPlugin.Configuration; +using TrafficAiPlugin.Shared; +using TrafficAiPlugin.Splines; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; public class TrafficAiModule : AssettoServerModule { protected override void Load(ContainerBuilder builder) { - builder.RegisterType().AsSelf().As().SingleInstance(); - builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf().As().As().SingleInstance(); + builder.RegisterType().AsSelf().As(); - builder.RegisterType().AsSelf(); + builder.RegisterType().AsSelf().As(); builder.RegisterType().AsSelf().As().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance().AutoActivate(); @@ -27,7 +28,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); builder.RegisterType().AsSelf(); - builder.Register((AiSplineLocator locator) => locator.Locate()).AsSelf().SingleInstance(); + builder.Register((AiSplineLocator locator) => locator.Locate()).AsSelf().As().SingleInstance(); } public override object? ReferenceConfiguration => new TrafficAiConfiguration diff --git a/TrafficAiPlugin/TrafficAiPlugin.csproj b/TrafficAiPlugin/TrafficAiPlugin.csproj index 7959dd25..93c3bcf0 100644 --- a/TrafficAiPlugin/TrafficAiPlugin.csproj +++ b/TrafficAiPlugin/TrafficAiPlugin.csproj @@ -25,6 +25,9 @@ false runtime + + all + diff --git a/TrafficAiPlugin/TrafficAiUpdater.cs b/TrafficAiPlugin/TrafficAiUpdater.cs index 1d63470c..25d55eb3 100644 --- a/TrafficAiPlugin/TrafficAiUpdater.cs +++ b/TrafficAiPlugin/TrafficAiUpdater.cs @@ -1,16 +1,8 @@ -using AssettoServer.Network.ClientMessages; -using AssettoServer.Network.Tcp; -using AssettoServer.Server; -using AssettoServer.Server.Configuration; -using AssettoServer.Server.Plugin; +using AssettoServer.Server; using AssettoServer.Shared.Network.Packets.Outgoing; -using AssettoServer.Shared.Services; -using AssettoServer.Utils; -using Microsoft.Extensions.Hosting; using Serilog; -using TrafficAiConfiguration = TrafficAIPlugin.Configuration.TrafficAiConfiguration; -namespace TrafficAIPlugin; +namespace TrafficAiPlugin; public class TrafficAiUpdater { diff --git a/TrafficAiPlugin/configuration.json b/TrafficAiPlugin/configuration.json index db2bef81..60c483d8 100644 --- a/TrafficAiPlugin/configuration.json +++ b/TrafficAiPlugin/configuration.json @@ -1,5 +1,5 @@ { "ExportedAssemblies": [ - "TrafficAiPlugin.dll" + "TrafficAiPlugin.Shared.dll" ] } From 5233abcc107d05b2d6c3125d951b852bcd35c416 Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Wed, 5 Mar 2025 02:14:12 +0100 Subject: [PATCH 06/10] Moved SplinePoint and SplinePointOperations to enable usage of all Operations through injection of the Spline --- AutoModerationPlugin/EntryCarAutoModeration.cs | 2 +- TrafficAiPlugin.Shared/IAiSpline.cs | 3 ++- .../Splines/SplinePoint.cs | 2 +- .../Splines/SplinePointOperations.cs | 2 +- TrafficAiPlugin/AiState.cs | 1 + TrafficAiPlugin/Splines/AdjacentLaneDetector.cs | 1 + TrafficAiPlugin/Splines/AiSpline.cs | 1 + TrafficAiPlugin/Splines/FastLane.cs | 4 +++- TrafficAiPlugin/Splines/FastLaneParser.cs | 1 + TrafficAiPlugin/Splines/MutableAiSpline.cs | 1 + 10 files changed, 13 insertions(+), 5 deletions(-) rename {TrafficAiPlugin => TrafficAiPlugin.Shared}/Splines/SplinePoint.cs (91%) rename {TrafficAiPlugin => TrafficAiPlugin.Shared}/Splines/SplinePointOperations.cs (98%) diff --git a/AutoModerationPlugin/EntryCarAutoModeration.cs b/AutoModerationPlugin/EntryCarAutoModeration.cs index 7d52d3bd..77389164 100644 --- a/AutoModerationPlugin/EntryCarAutoModeration.cs +++ b/AutoModerationPlugin/EntryCarAutoModeration.cs @@ -243,7 +243,7 @@ private void UpdateWrongWayPenalty(ACTcpClient client) if (CurrentSplinePointId >= 0 && CurrentSplinePointDistanceSquared < _laneRadiusSquared && _entryCar.Status.Velocity.LengthSquared() > _configuration.WrongWayPenalty.MinimumSpeedMs * _configuration.WrongWayPenalty.MinimumSpeedMs - && Vector3.Dot(_aiSpline.GetForwardVector(CurrentSplinePointId), _entryCar.Status.Velocity) < 0) + && Vector3.Dot(_aiSpline.Operations.GetForwardVector(CurrentSplinePointId), _entryCar.Status.Velocity) < 0) { CurrentFlags |= Flags.WrongWay; diff --git a/TrafficAiPlugin.Shared/IAiSpline.cs b/TrafficAiPlugin.Shared/IAiSpline.cs index 4b5604ef..4d7be0a7 100644 --- a/TrafficAiPlugin.Shared/IAiSpline.cs +++ b/TrafficAiPlugin.Shared/IAiSpline.cs @@ -1,9 +1,10 @@ using System.Numerics; +using TrafficAiPlugin.Shared.Splines; namespace TrafficAiPlugin.Shared; public interface IAiSpline { - public Vector3 GetForwardVector(int pointId); + public SplinePointOperations Operations { get; } public (int PointId, float DistanceSquared) WorldToSpline(Vector3 position); } diff --git a/TrafficAiPlugin/Splines/SplinePoint.cs b/TrafficAiPlugin.Shared/Splines/SplinePoint.cs similarity index 91% rename from TrafficAiPlugin/Splines/SplinePoint.cs rename to TrafficAiPlugin.Shared/Splines/SplinePoint.cs index e7d7b127..6db01aa5 100644 --- a/TrafficAiPlugin/Splines/SplinePoint.cs +++ b/TrafficAiPlugin.Shared/Splines/SplinePoint.cs @@ -1,7 +1,7 @@ using System.Numerics; using System.Runtime.InteropServices; -namespace TrafficAiPlugin.Splines; +namespace TrafficAiPlugin.Shared.Splines; [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct SplinePoint diff --git a/TrafficAiPlugin/Splines/SplinePointOperations.cs b/TrafficAiPlugin.Shared/Splines/SplinePointOperations.cs similarity index 98% rename from TrafficAiPlugin/Splines/SplinePointOperations.cs rename to TrafficAiPlugin.Shared/Splines/SplinePointOperations.cs index 07d37ce7..b83e897a 100644 --- a/TrafficAiPlugin/Splines/SplinePointOperations.cs +++ b/TrafficAiPlugin.Shared/Splines/SplinePointOperations.cs @@ -2,7 +2,7 @@ using AssettoServer.Utils; using Serilog; -namespace TrafficAiPlugin.Splines; +namespace TrafficAiPlugin.Shared.Splines; public readonly ref struct SplinePointOperations { diff --git a/TrafficAiPlugin/AiState.cs b/TrafficAiPlugin/AiState.cs index aa450dc4..32a9d849 100644 --- a/TrafficAiPlugin/AiState.cs +++ b/TrafficAiPlugin/AiState.cs @@ -11,6 +11,7 @@ using SunCalcNet.Model; using TrafficAiPlugin.Configuration; using TrafficAiPlugin.Shared; +using TrafficAiPlugin.Shared.Splines; using TrafficAiPlugin.Splines; namespace TrafficAiPlugin; diff --git a/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs b/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs index e27dce8c..f2b52bf3 100644 --- a/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs +++ b/TrafficAiPlugin/Splines/AdjacentLaneDetector.cs @@ -1,6 +1,7 @@ using System.Numerics; using Serilog; using SerilogTimings; +using TrafficAiPlugin.Shared.Splines; namespace TrafficAiPlugin.Splines; diff --git a/TrafficAiPlugin/Splines/AiSpline.cs b/TrafficAiPlugin/Splines/AiSpline.cs index 68a09647..ff44a72d 100644 --- a/TrafficAiPlugin/Splines/AiSpline.cs +++ b/TrafficAiPlugin/Splines/AiSpline.cs @@ -8,6 +8,7 @@ using Serilog; using Supercluster.KDTree; using TrafficAiPlugin.Shared; +using TrafficAiPlugin.Shared.Splines; namespace TrafficAiPlugin.Splines; diff --git a/TrafficAiPlugin/Splines/FastLane.cs b/TrafficAiPlugin/Splines/FastLane.cs index f71223fa..d2aeb0ec 100644 --- a/TrafficAiPlugin/Splines/FastLane.cs +++ b/TrafficAiPlugin/Splines/FastLane.cs @@ -1,4 +1,6 @@ -namespace TrafficAiPlugin.Splines; +using TrafficAiPlugin.Shared.Splines; + +namespace TrafficAiPlugin.Splines; public class FastLane { diff --git a/TrafficAiPlugin/Splines/FastLaneParser.cs b/TrafficAiPlugin/Splines/FastLaneParser.cs index 9095a266..51a3c245 100644 --- a/TrafficAiPlugin/Splines/FastLaneParser.cs +++ b/TrafficAiPlugin/Splines/FastLaneParser.cs @@ -4,6 +4,7 @@ using AssettoServer.Utils; using Serilog; using TrafficAiPlugin.Configuration; +using TrafficAiPlugin.Shared.Splines; using YamlDotNet.Serialization; namespace TrafficAiPlugin.Splines; diff --git a/TrafficAiPlugin/Splines/MutableAiSpline.cs b/TrafficAiPlugin/Splines/MutableAiSpline.cs index b0e52310..2b52827e 100644 --- a/TrafficAiPlugin/Splines/MutableAiSpline.cs +++ b/TrafficAiPlugin/Splines/MutableAiSpline.cs @@ -3,6 +3,7 @@ using Serilog; using Supercluster.KDTree; using TrafficAiPlugin.Configuration; +using TrafficAiPlugin.Shared.Splines; namespace TrafficAiPlugin.Splines; From 713e0ab5e27d3183efd89b75663569650c511046 Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:48:18 +0200 Subject: [PATCH 07/10] Resolved more conflicts --- FastTravelPlugin/FastTravelPlugin.cs | 6 +++--- FastTravelPlugin/FastTravelPlugin.csproj | 3 +++ TrafficAiPlugin.Shared/IAiSpline.cs | 1 + TrafficAiPlugin/EntryCarTrafficAi.cs | 6 ++---- TrafficAiPlugin/TrafficAi.cs | 4 ++-- TrafficAiPlugin/TrafficAiCommandModule.cs | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/FastTravelPlugin/FastTravelPlugin.cs b/FastTravelPlugin/FastTravelPlugin.cs index 3d195993..1575260d 100644 --- a/FastTravelPlugin/FastTravelPlugin.cs +++ b/FastTravelPlugin/FastTravelPlugin.cs @@ -3,26 +3,26 @@ using System.Text.Json; using AssettoServer.Network.Tcp; using AssettoServer.Server; -using AssettoServer.Server.Ai.Splines; using AssettoServer.Server.Configuration; using AssettoServer.Server.Plugin; using AssettoServer.Shared.Services; using AssettoServer.Utils; using FastTravelPlugin.Packets; using Microsoft.Extensions.Hosting; +using TrafficAiPlugin.Shared; namespace FastTravelPlugin; public class FastTravelPlugin : CriticalBackgroundService, IAssettoServerAutostart { - private readonly AiSpline _aiSpline; + private readonly IAiSpline _aiSpline; public FastTravelPlugin(FastTravelConfiguration configuration, ACServerConfiguration serverConfiguration, CSPServerScriptProvider scriptProvider, CSPClientMessageTypeManager cspClientMessageTypeManager, IHostApplicationLifetime applicationLifetime, - AiSpline? aiSpline = null) : base(applicationLifetime) + IAiSpline? aiSpline = null) : base(applicationLifetime) { _aiSpline = aiSpline ?? throw new ConfigurationException("FastTravelPlugin does not work with AI traffic disabled"); diff --git a/FastTravelPlugin/FastTravelPlugin.csproj b/FastTravelPlugin/FastTravelPlugin.csproj index 981a5c71..59465c75 100644 --- a/FastTravelPlugin/FastTravelPlugin.csproj +++ b/FastTravelPlugin/FastTravelPlugin.csproj @@ -25,6 +25,9 @@ false runtime + + all + diff --git a/TrafficAiPlugin.Shared/IAiSpline.cs b/TrafficAiPlugin.Shared/IAiSpline.cs index 4d7be0a7..ad04e881 100644 --- a/TrafficAiPlugin.Shared/IAiSpline.cs +++ b/TrafficAiPlugin.Shared/IAiSpline.cs @@ -6,5 +6,6 @@ namespace TrafficAiPlugin.Shared; public interface IAiSpline { public SplinePointOperations Operations { get; } + public ReadOnlySpan Points { get; } public (int PointId, float DistanceSquared) WorldToSpline(Vector3 position); } diff --git a/TrafficAiPlugin/EntryCarTrafficAi.cs b/TrafficAiPlugin/EntryCarTrafficAi.cs index 7b64b942..390229d4 100644 --- a/TrafficAiPlugin/EntryCarTrafficAi.cs +++ b/TrafficAiPlugin/EntryCarTrafficAi.cs @@ -397,16 +397,14 @@ public bool TryResetPosition() var position = splinePoint.Position; var direction = - _aiSpline.Operations.GetForwardVector(splinePoint.NextId); - EntryCar.Client?.SendCollisionUpdatePacket(false); + EntryCar.SetCollisions(false); _ = Task.Run(async () => { await Task.Delay(500); - EntryCar.Client?.SendTeleportCarPacket(position, direction); await Task.Delay(10000); - - EntryCar.Client?.SendCollisionUpdatePacket(true); + EntryCar.SetCollisions(true); }); EntryCar.Logger.Information("Reset position for {Player} ({SessionId})",EntryCar.Client?.Name, EntryCar.Client?.SessionId); diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs index a4fdc703..4dc88400 100644 --- a/TrafficAiPlugin/TrafficAi.cs +++ b/TrafficAiPlugin/TrafficAi.cs @@ -42,10 +42,10 @@ public TrafficAi(TrafficAiConfiguration configuration, if (_configuration.EnableCarReset) { - if (!_serverConfiguration.Extra.EnableClientMessages || _serverConfiguration.CSPTrackOptions.MinimumCSPVersion < CSPVersion.V0_2_3_p47) + if (!_serverConfiguration.Extra.EnableClientMessages || _serverConfiguration.CSPTrackOptions.MinimumCSPVersion < CSPVersion.V0_2_8) { throw new ConfigurationException( - "Reset car: Minimum required CSP version of 0.2.3-preview47 (2796); Requires enabled client messages; Requires working AI spline"); + "Reset car: Minimum required CSP version of v0.2.8 (3424); Requires enabled client messages; Requires working AI spline"); } cspClientMessageTypeManager.RegisterOnlineEvent((client, _) => { OnResetCar(client); }); } diff --git a/TrafficAiPlugin/TrafficAiCommandModule.cs b/TrafficAiPlugin/TrafficAiCommandModule.cs index 5383df98..fa6aa7b7 100644 --- a/TrafficAiPlugin/TrafficAiCommandModule.cs +++ b/TrafficAiPlugin/TrafficAiCommandModule.cs @@ -42,7 +42,7 @@ public void SetAiOverbooking(int count) [Command("resetcar"), RequireConnectedPlayer] public void ResetCarAsync() { - if (_serverConfiguration.Extra is { EnableClientMessages: true, MinimumCSPVersion: >= CSPVersion.V0_2_3_p47 } && + if (_serverConfiguration.Extra is { EnableClientMessages: true, MinimumCSPVersion: >= CSPVersion.V0_2_8 } && _configuration.EnableCarReset) { Reply(_trafficAi.GetAiCarBySessionId(Client!.SessionId).TryResetPosition() From 18568b6ab5389654b04e88938f27cb27483081ed Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:52:17 +0200 Subject: [PATCH 08/10] Should now be in sync with master --- TrafficAiPlugin/TrafficAi.cs | 4 ++-- TrafficAiPlugin/lua/resetcar.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs index 4dc88400..29882fed 100644 --- a/TrafficAiPlugin/TrafficAi.cs +++ b/TrafficAiPlugin/TrafficAi.cs @@ -77,8 +77,8 @@ private void OnFirstUpdateSentHideCars(ACTcpClient sender, EventArgs args) private void OnResetCar(ACTcpClient sender) { - if (_configuration.EnableCarReset) - GetAiCarBySessionId(sender.SessionId).TryResetPosition(); + if (!_configuration.EnableCarReset) return; + GetAiCarBySessionId(sender.SessionId).TryResetPosition(); } // public EntryCarTrafficAi GetAiCarBySessionId(byte sessionId) diff --git a/TrafficAiPlugin/lua/resetcar.lua b/TrafficAiPlugin/lua/resetcar.lua index 796274cb..a3aaed4b 100644 --- a/TrafficAiPlugin/lua/resetcar.lua +++ b/TrafficAiPlugin/lua/resetcar.lua @@ -7,4 +7,4 @@ local requestResetCarEvent = ac.OnlineEvent({ end) local resetCarControl = ac.ControlButton('__EXT_CMD_RESET', nil) -resetCarControl:onPressed(function() requestResetCarEvent({dummy=0}) end) +resetCarControl:onPressed(function() requestResetCarEvent({}) end) From f901e714abbb1113ce36013e54794f748df64785 Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:06:25 +0200 Subject: [PATCH 09/10] Use more recent position for car reset --- TrafficAiPlugin/EntryCarTrafficAi.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/TrafficAiPlugin/EntryCarTrafficAi.cs b/TrafficAiPlugin/EntryCarTrafficAi.cs index 390229d4..586217dc 100644 --- a/TrafficAiPlugin/EntryCarTrafficAi.cs +++ b/TrafficAiPlugin/EntryCarTrafficAi.cs @@ -389,19 +389,20 @@ public bool TryResetPosition() || (_sessionManager.ServerTimeMilliseconds > _sessionManager.CurrentSession.EndTimeMilliseconds && _sessionManager.CurrentSession.EndTimeMilliseconds > 0)) return false; - - var (splinePointId, _) = _aiSpline.WorldToSpline(EntryCar.Status.Position); - - var splinePoint = _aiSpline.Points[splinePointId]; - - var position = splinePoint.Position; - var direction = - _aiSpline.Operations.GetForwardVector(splinePoint.NextId); EntryCar.SetCollisions(false); _ = Task.Run(async () => { - await Task.Delay(500); + await Task.Delay(250); + + var (splinePointId, _) = _aiSpline.WorldToSpline(EntryCar.Status.Position); + + var splinePoint = _aiSpline.Points[splinePointId]; + + var position = splinePoint.Position; + var direction = - _aiSpline.Operations.GetForwardVector(splinePoint.NextId); + EntryCar.Client?.SendTeleportCarPacket(position, direction); await Task.Delay(10000); EntryCar.SetCollisions(true); From 7650ae3046365a047484c3711d5de6b89c15d233 Mon Sep 17 00:00:00 2001 From: thisguyStan <53304086+thisguyStan@users.noreply.github.com> Date: Tue, 1 Jul 2025 02:11:10 +0200 Subject: [PATCH 10/10] Sync fixes --- TrafficAiPlugin/DynamicTrafficDensity.cs | 2 +- TrafficAiPlugin/TrafficAi.cs | 10 +++------- TrafficAiPlugin/TrafficAiModule.cs | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/TrafficAiPlugin/DynamicTrafficDensity.cs b/TrafficAiPlugin/DynamicTrafficDensity.cs index bd8cb64f..0556f2db 100644 --- a/TrafficAiPlugin/DynamicTrafficDensity.cs +++ b/TrafficAiPlugin/DynamicTrafficDensity.cs @@ -38,7 +38,7 @@ private float GetDensity(double hourOfDay) public override Task StartAsync(CancellationToken cancellationToken) { - if (_configuration.HourlyTrafficDensity == null) return; + if (_configuration.HourlyTrafficDensity == null) return Task.CompletedTask; if (_serverConfiguration.Server.TimeOfDayMultiplier == 0 ) { diff --git a/TrafficAiPlugin/TrafficAi.cs b/TrafficAiPlugin/TrafficAi.cs index 29882fed..f11d1e39 100644 --- a/TrafficAiPlugin/TrafficAi.cs +++ b/TrafficAiPlugin/TrafficAi.cs @@ -1,10 +1,7 @@ -using AssettoServer.Network.ClientMessages; -using AssettoServer.Network.Tcp; +using AssettoServer.Network.Tcp; using AssettoServer.Server; using AssettoServer.Server.Configuration; -using AssettoServer.Server.Plugin; using AssettoServer.Shared.Network.Packets.Outgoing; -using AssettoServer.Shared.Services; using AssettoServer.Utils; using Microsoft.Extensions.Hosting; using TrafficAiPlugin.Packets; @@ -14,7 +11,7 @@ namespace TrafficAiPlugin; using TrafficAiConfiguration = Configuration.TrafficAiConfiguration; -public class TrafficAi : CriticalBackgroundService, IAssettoServerAutostart, ITrafficAi +public class TrafficAi : BackgroundService, ITrafficAi { private readonly TrafficAiConfiguration _configuration; private readonly ACServerConfiguration _serverConfiguration; @@ -29,8 +26,7 @@ public TrafficAi(TrafficAiConfiguration configuration, EntryCarManager entryCarManager, SessionManager sessionManager, Func entryCarTrafficAiFactory, - CSPClientMessageTypeManager cspClientMessageTypeManager, - IHostApplicationLifetime applicationLifetime) : base(applicationLifetime) + CSPClientMessageTypeManager cspClientMessageTypeManager) { _configuration = configuration; _serverConfiguration = serverConfiguration; diff --git a/TrafficAiPlugin/TrafficAiModule.cs b/TrafficAiPlugin/TrafficAiModule.cs index 9b4389c8..ee036a5e 100644 --- a/TrafficAiPlugin/TrafficAiModule.cs +++ b/TrafficAiPlugin/TrafficAiModule.cs @@ -14,12 +14,12 @@ public class TrafficAiModule : AssettoServerModule protected override void Load(ContainerBuilder builder) { - builder.RegisterType().AsSelf().As().As().SingleInstance(); + builder.RegisterType().AsSelf().As().As().SingleInstance(); builder.RegisterType().AsSelf().As(); builder.RegisterType().AsSelf().As(); - builder.RegisterType().AsSelf().As().SingleInstance(); + builder.RegisterType().AsSelf().As().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance().AutoActivate(); builder.RegisterType().As();