From 28f9f90d081d0b570f04ea12b5fe4247a990a089 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Fri, 7 Feb 2025 00:45:58 -0800 Subject: [PATCH 01/22] First version of extended sequences implemented --- DLSv2/Core/DLSModel.cs | 41 +++++++++++++++++----- DLSv2/Core/ManagedVehicle.cs | 58 +++++++++++++++++++++++++++++++- DLSv2/DLSv2.csproj | 1 + DLSv2/EntryPoint.cs | 6 ++++ DLSv2/Threads/SequenceManager.cs | 25 ++++++++++++++ DLSv2/Utils/DLSExtensions.cs | 50 ++++++++++++++++----------- 6 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 DLSv2/Threads/SequenceManager.cs diff --git a/DLSv2/Core/DLSModel.cs b/DLSv2/Core/DLSModel.cs index 640c57a..bea4a6e 100644 --- a/DLSv2/Core/DLSModel.cs +++ b/DLSv2/Core/DLSModel.cs @@ -119,10 +119,10 @@ public SequenceItem[] Sequences get => sequences; set { + if (SirenSettings == null) SirenSettings = new SirenSetting(); + foreach (SequenceItem item in value) { - if (SirenSettings == null) SirenSettings = new SirenSetting(); - // Parse siren ID into one or more integers foreach (string id in item.IDs.Split(',')) { @@ -130,22 +130,24 @@ public SequenceItem[] Sequences switch (id) { case "leftHeadLight": - SirenSettings.LeftHeadLightSequencer = new SequencerWrapper(item.Sequence); + SirenSettings.LeftHeadLightSequencer = new SequencerWrapper(item.StandardSequence); continue; case "rightHeadLight": - SirenSettings.RightHeadLightSequencer = new SequencerWrapper(item.Sequence); + SirenSettings.RightHeadLightSequencer = new SequencerWrapper(item.StandardSequence); continue; case "leftTailLight": - SirenSettings.LeftTailLightSequencer = new SequencerWrapper(item.Sequence); + SirenSettings.LeftTailLightSequencer = new SequencerWrapper(item.StandardSequence); continue; case "rightTailLight": - SirenSettings.RightTailLightSequencer = new SequencerWrapper(item.Sequence); + SirenSettings.RightTailLightSequencer = new SequencerWrapper(item.StandardSequence); continue; } if (int.TryParse(id.Trim(), out int ID)) { - SirenEntry siren = new SirenEntry(ID) { Flashiness = new LightDetailEntry { Sequence = new Sequencer(item.Sequence) } }; + if (item.IsExtended) ExtendedSequences.Add(ID, item.Sequence); + + SirenEntry siren = new SirenEntry(ID) { Flashiness = new LightDetailEntry { Sequence = new Sequencer(item.StandardSequence) } }; SirenSettings.SirenList.Add(siren); } else { @@ -159,6 +161,10 @@ public SequenceItem[] Sequences } private SequenceItem[] sequences; + [XmlIgnore] + // key = siren ID, value = extended sequence string + internal Dictionary ExtendedSequences = new(); + public override string ToString() => Name; } @@ -239,7 +245,26 @@ public class SequenceItem public string IDs; [XmlAttribute("sequence")] - public string Sequence; + public string Sequence + { + get => sequence; + set + { + // If initial length is less than 32 bits, repeat the sequence until it's over the min length + string seq = value; + while(seq.Length < 32) + { + seq += value; + } + sequence = seq; + } + } + + private string sequence; + + public bool IsExtended => Sequence.Length > 32; + + public string StandardSequence => Sequence.Substring(0, 32); } public class LightControlGroup : BaseControlGroup diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index 9a2e0de..b9c5e6c 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -20,6 +20,9 @@ public ManagedVehicle(Vehicle vehicle) Vehicle = vehicle; VehicleHandle = vehicle.Handle; + sirenInstance = new SirenInstance(vehicle); + + $"Detected DLS vehicle {VehicleHandle.ToString("X")}".ToLog(LogLevel.DEBUG); Entrypoint.DLSModels.TryGetValue(vehicle.Model, out var _dlsModel); if (_dlsModel == null) return; @@ -97,10 +100,13 @@ public ManagedVehicle(Vehicle vehicle) private void SetIsPlayerOwned(Vehicle v, bool isPlayerOwned) { + if (v != Vehicle) return; + if (isPlayerOwned) { v.DisableSirenSounds(); DefaultMode.EnabledByTrigger = false; + $"Vehicle {v.Handle.Value.ToString("X")} ({v.Model.Name}) set to player owned".ToLog(LogLevel.DEBUG); } else { bool silent = v.IsSirenSilent; @@ -108,6 +114,7 @@ private void SetIsPlayerOwned(Vehicle v, bool isPlayerOwned) v.EnableSirenSounds(); DefaultMode.EnabledByTrigger = true; v.IsSirenSilent = silent; + $"Vehicle {v.Handle.Value.ToString("X")} ({v.Model.Name}) set to non-player owned".ToLog(LogLevel.DEBUG); } UpdateLights(); @@ -119,10 +126,11 @@ private void SetIsPlayerOwned(Vehicle v, bool isPlayerOwned) public Vehicle Vehicle { get; } public DLSModel dlsModel { get; } public uint VehicleHandle { get; } + private SirenInstance sirenInstance; public Dictionary ManagedExtras = new Dictionary(); // Managed Extras - ID, original state public Dictionary ManagedPaint = new Dictionary(); // Managed Paint Settings - Paint index, original color code - private bool areLightsOn; public Animation ActiveAnim; + private bool areLightsOn; /// /// Lights @@ -562,6 +570,54 @@ public void UpdateAudio() PlayMode(audioMode); } } + + private int lastSeqChangedBeat = -1; + public void ProcessSequences() + { + // Only process if lights are on + if (!LightsOn) return; + + // Only process if starting beat 0 + if (!(sirenInstance.CurrentSirenBeat % 16 == 0 && sirenInstance.CurrentSirenBeat != lastSeqChangedBeat && sirenInstance.TotalSirenBeats > 0)) return; + lastSeqChangedBeat = sirenInstance.CurrentSirenBeat; + + + var eL = Vehicle.GetDLSEmergencyLighting(); + + foreach (var mode in LightModes.Values) + { + if (mode.Enabled && mode.BaseMode.ExtendedSequences.Count > 0) + { + foreach (var seqItem in mode.BaseMode.ExtendedSequences) + { + if (seqItem.Key > eL.Lights.Length) return; + + string seq = seqItem.Value; + int l = seq.Length; + seq += seq + seq; + string newSeq; + + // update on beat 16 to avoid flickering when the first bit changes + if (sirenInstance.CurrentSirenBeat == 16) + { + int a = ((sirenInstance.TotalSirenBeats - 16) % l); + int b = a + 16; + int c = a + 32; + // Game.LogTrivialDebug($"a = {a}, b = {b}, c = {c}"); + if (c < 0 || b > seq.Length) continue; + newSeq = seq.Substring(c, 16) + seq.Substring(b, 16); + } else + { + int a = (sirenInstance.TotalSirenBeats % l); + newSeq = seq.Substring(a, 32); + } + + eL.Lights[seqItem.Key - 1].FlashinessSequence = newSeq; + // Game.LogTrivialDebug($"[{sirenInstance.CurrentSirenBeat} : {sirenInstance.TotalSirenBeats}] Siren {seqItem.Key} = {newSeq}"); + } + } + } + } public void PlayMode(AudioMode mode) { diff --git a/DLSv2/DLSv2.csproj b/DLSv2/DLSv2.csproj index d049c9c..3683b0e 100644 --- a/DLSv2/DLSv2.csproj +++ b/DLSv2/DLSv2.csproj @@ -58,6 +58,7 @@ + diff --git a/DLSv2/EntryPoint.cs b/DLSv2/EntryPoint.cs index 47ffb7b..8596615 100644 --- a/DLSv2/EntryPoint.cs +++ b/DLSv2/EntryPoint.cs @@ -77,6 +77,12 @@ public static void Main() GameFiber.StartNew(AiManager.MonitorProcess, "DLS - AI Manager Monitor"); "Loaded: DLS - AI Manager".ToLog(); + // Creates Extended Sequence manager + "Loading: DLS - Sequence Manager".ToLog(); + GameFiber.StartNew(SequenceManager.Process, "DLS - Sequence Manager"); + "Loaded: DLS - Sequence Manager".ToLog(); + + //If extra patch is enabled if (Settings.EXTRAPATCH) { diff --git a/DLSv2/Threads/SequenceManager.cs b/DLSv2/Threads/SequenceManager.cs new file mode 100644 index 0000000..c688a4f --- /dev/null +++ b/DLSv2/Threads/SequenceManager.cs @@ -0,0 +1,25 @@ +using DLSv2.Core; +using Rage; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DLSv2.Threads +{ + internal class SequenceManager + { + internal static void Process() + { + while (true) + { + foreach (ManagedVehicle mv in Entrypoint.ManagedVehicles.Values) + { + if (mv.Vehicle) mv.ProcessSequences(); + } + GameFiber.Yield(); + } + } + } +} diff --git a/DLSv2/Utils/DLSExtensions.cs b/DLSv2/Utils/DLSExtensions.cs index b1a2fb0..336f698 100644 --- a/DLSv2/Utils/DLSExtensions.cs +++ b/DLSv2/Utils/DLSExtensions.cs @@ -131,40 +131,53 @@ internal static SirenSetting GetDefaultSirenSetting(this Vehicle veh) }; } - public static void ApplyLightModes(this ManagedVehicle managedVehicle, List modes) + public static EmergencyLighting GetDLSEmergencyLighting(this Vehicle vehicle) { // Safety checks - if (managedVehicle == null) return; - var vehicle = managedVehicle.Vehicle; - if (!vehicle) return; + if (!vehicle) return null; EmergencyLighting eL; - var key = vehicle.Handle; + uint key = vehicle.Handle.Value; + string name = "DLS_" + key.ToString("X"); if (Entrypoint.ELUsedPool.TryGetValue(key, out var elFromPool)) + { eL = elFromPool; + } else if (Entrypoint.ELAvailablePool.Count > 0) { eL = Entrypoint.ELAvailablePool[0]; Entrypoint.ELAvailablePool.Remove(eL); - eL.Name = "DLS_" + key; + eL.Name = name; ("Allocated \"" + eL.Name + "\" (now \"" + key + "\") EL from Available Pool").ToLog(LogLevel.DEBUG); } + else if (EmergencyLighting.GetByName(name) != null) + { + eL = EmergencyLighting.GetByName(name); + ("Allocated \"" + eL.Name + "\" EL from Game Memory").ToLog(LogLevel.DEBUG); + } else { - if (EmergencyLighting.GetByName("DLS_" + key) != null) - { - eL = EmergencyLighting.GetByName("DLS_" + key); - ("Allocated \"" + eL.Name + "\" EL from Game Memory").ToLog(LogLevel.DEBUG); - } - else - { - eL = new EmergencyLighting(); - eL.Name = "DLS_" + key; - ("Created \"" + eL.Name + "\" EL").ToLog(LogLevel.DEBUG); - } + eL = new EmergencyLighting(); + eL.Name = name; + ("Created \"" + eL.Name + "\" EL").ToLog(LogLevel.DEBUG); } + if (!Entrypoint.ELUsedPool.ContainsKey(key)) + Entrypoint.ELUsedPool.Add(key, eL); + + return eL; + } + + public static void ApplyLightModes(this ManagedVehicle managedVehicle, List modes) + { + // Safety checks + if (managedVehicle == null) return; + var vehicle = managedVehicle.Vehicle; + if (!vehicle) return; + + EmergencyLighting eL = vehicle.GetDLSEmergencyLighting(); + SirenApply.ApplySirenSettingsToEmergencyLighting(managedVehicle.EmptyMode.SirenSettings, eL); var shouldYield = false; @@ -279,9 +292,6 @@ public static void ApplyLightModes(this ManagedVehicle managedVehicle, List Date: Fri, 7 Feb 2025 00:46:40 -0800 Subject: [PATCH 02/22] Misc performance improvements --- DLSv2/Conditions/VehicleStatusConditions.cs | 3 +++ DLSv2/Core/SirenSetting.cs | 1 + DLSv2/Threads/ControlsManager.cs | 9 ++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/DLSv2/Conditions/VehicleStatusConditions.cs b/DLSv2/Conditions/VehicleStatusConditions.cs index 68fc263..3f5bcfe 100644 --- a/DLSv2/Conditions/VehicleStatusConditions.cs +++ b/DLSv2/Conditions/VehicleStatusConditions.cs @@ -11,6 +11,7 @@ namespace DLSv2.Conditions; public class DriverCondition : VehicleCondition { + protected override uint UpdateWait => 100; [XmlAttribute("has_driver")] public bool HasDriver { get; set; } = true; @@ -143,6 +144,8 @@ public class VehicleOwnerCondition : VehicleCondition public class AtTrafficLightCondition : VehicleCondition { + protected override uint UpdateWait => 100; + [XmlAttribute("stopped_at_light")] public bool IsAtTrafficLight { get; set; } diff --git a/DLSv2/Core/SirenSetting.cs b/DLSv2/Core/SirenSetting.cs index c695d3e..c20e45d 100644 --- a/DLSv2/Core/SirenSetting.cs +++ b/DLSv2/Core/SirenSetting.cs @@ -248,6 +248,7 @@ public string Sequence } } +// Only used to embed taillight and headlight sequencers directly into raw carcols format [DebuggerDisplay("{Sequencer.Sequence} = {Sequencer.SequenceRaw}")] public class SequencerWrapper { diff --git a/DLSv2/Threads/ControlsManager.cs b/DLSv2/Threads/ControlsManager.cs index c5c4d23..e237f68 100644 --- a/DLSv2/Threads/ControlsManager.cs +++ b/DLSv2/Threads/ControlsManager.cs @@ -54,6 +54,9 @@ public override string ToString() // was the key combo held down on the last tick private bool wasHeld; + // is a controller available + private bool isControllerAvailable; + public ControlsInput(string name) { Name = name; @@ -81,6 +84,7 @@ public void Process() { bool isHeld = IsHeldDown; bool isPressed = IsJustPressed; + isControllerAvailable = Game.IsControllerConnected; if (!ControlsManager.KeysLocked || Name == "LOCKALL") { @@ -115,6 +119,7 @@ private bool IsKeyAvailable() private bool IsButtonAvailable() { return + isControllerAvailable && Button != ControllerButtons.None && !isAnyOtherModifierButtonPressed() && (ButtonModifier == ControllerButtons.None || Game.IsControllerButtonDownRightNow(ButtonModifier)); @@ -123,7 +128,7 @@ private bool IsButtonAvailable() public bool IsJustPressed => IsKeyJustPressed() || IsButtonJustPressed(); - private bool IsKeyJustPressed() => IsKeyAvailable() && Game.IsKeyDown(Key); + private bool IsKeyJustPressed() => IsKeyAvailable() && IsKeyPressedNow(Key) && Game.IsKeyDown(Key); private bool IsButtonJustPressed() => IsButtonAvailable() && Game.IsControllerButtonDown(Button); @@ -213,6 +218,8 @@ public static void Process() { GameFiber.Yield(); + if (!PlayerManager.registeredKeys) return; + var keyboardState = Game.GetKeyboardState(); if (Game.IsPaused || keyboardState == null) continue; From b512ea67589e895e6780a2c82b05df4b69000475 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Fri, 7 Feb 2025 00:52:17 -0800 Subject: [PATCH 03/22] fixed oops --- DLSv2/Threads/ControlsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLSv2/Threads/ControlsManager.cs b/DLSv2/Threads/ControlsManager.cs index e237f68..1872082 100644 --- a/DLSv2/Threads/ControlsManager.cs +++ b/DLSv2/Threads/ControlsManager.cs @@ -218,7 +218,7 @@ public static void Process() { GameFiber.Yield(); - if (!PlayerManager.registeredKeys) return; + if (!PlayerManager.registeredKeys) continue; var keyboardState = Game.GetKeyboardState(); From e8b83421b8fedf1c29005d65fac8ed6905eeff4d Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Fri, 7 Feb 2025 01:45:26 -0800 Subject: [PATCH 04/22] deconflict non-extended sequences --- DLSv2/Core/ManagedVehicle.cs | 62 +++++++++++++++++------------------- DLSv2/Utils/DLSExtensions.cs | 16 +++++++++- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index b9c5e6c..5a131b3 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -126,7 +126,11 @@ private void SetIsPlayerOwned(Vehicle v, bool isPlayerOwned) public Vehicle Vehicle { get; } public DLSModel dlsModel { get; } public uint VehicleHandle { get; } - private SirenInstance sirenInstance; + public EmergencyLighting eL { get; private set; } + + public SirenInstance sirenInstance; + public Dictionary extendedSequences = new(); + public Dictionary ManagedExtras = new Dictionary(); // Managed Extras - ID, original state public Dictionary ManagedPaint = new Dictionary(); // Managed Paint Settings - Paint index, original color code public Animation ActiveAnim; @@ -448,6 +452,9 @@ public void UpdateLights() { if (!Vehicle) return; + // Ensure correct emergency lighting instance is set + eL = Vehicle.GetDLSEmergencyLighting(); + // Start with no modes activated List modes = new(); @@ -581,41 +588,32 @@ public void ProcessSequences() if (!(sirenInstance.CurrentSirenBeat % 16 == 0 && sirenInstance.CurrentSirenBeat != lastSeqChangedBeat && sirenInstance.TotalSirenBeats > 0)) return; lastSeqChangedBeat = sirenInstance.CurrentSirenBeat; - - var eL = Vehicle.GetDLSEmergencyLighting(); - - foreach (var mode in LightModes.Values) + foreach (var seqItem in extendedSequences) { - if (mode.Enabled && mode.BaseMode.ExtendedSequences.Count > 0) - { - foreach (var seqItem in mode.BaseMode.ExtendedSequences) - { - if (seqItem.Key > eL.Lights.Length) return; + if (seqItem.Key > eL.Lights.Length) return; - string seq = seqItem.Value; - int l = seq.Length; - seq += seq + seq; - string newSeq; + string seq = seqItem.Value; + int l = seq.Length; + seq += seq + seq; + string newSeq; - // update on beat 16 to avoid flickering when the first bit changes - if (sirenInstance.CurrentSirenBeat == 16) - { - int a = ((sirenInstance.TotalSirenBeats - 16) % l); - int b = a + 16; - int c = a + 32; - // Game.LogTrivialDebug($"a = {a}, b = {b}, c = {c}"); - if (c < 0 || b > seq.Length) continue; - newSeq = seq.Substring(c, 16) + seq.Substring(b, 16); - } else - { - int a = (sirenInstance.TotalSirenBeats % l); - newSeq = seq.Substring(a, 32); - } - - eL.Lights[seqItem.Key - 1].FlashinessSequence = newSeq; - // Game.LogTrivialDebug($"[{sirenInstance.CurrentSirenBeat} : {sirenInstance.TotalSirenBeats}] Siren {seqItem.Key} = {newSeq}"); - } + // update on beat 16 to avoid flickering when the first bit changes + if (sirenInstance.CurrentSirenBeat == 16) + { + int a = ((sirenInstance.TotalSirenBeats - 16) % l); + int b = a + 16; + int c = a + 32; + // Game.LogTrivialDebug($"a = {a}, b = {b}, c = {c}"); + if (c < 0 || b > seq.Length) continue; + newSeq = seq.Substring(c, 16) + seq.Substring(b, 16); + } else + { + int a = (sirenInstance.TotalSirenBeats % l); + newSeq = seq.Substring(a, 32); } + + eL.Lights[seqItem.Key - 1].FlashinessSequence = newSeq; + // Game.LogTrivialDebug($"[{sirenInstance.CurrentSirenBeat} : {sirenInstance.TotalSirenBeats}] Siren {seqItem.Key} = {newSeq}"); } } diff --git a/DLSv2/Utils/DLSExtensions.cs b/DLSv2/Utils/DLSExtensions.cs index 336f698..b9b76b9 100644 --- a/DLSv2/Utils/DLSExtensions.cs +++ b/DLSv2/Utils/DLSExtensions.cs @@ -176,7 +176,9 @@ public static void ApplyLightModes(this ManagedVehicle managedVehicle, List Date: Fri, 7 Feb 2025 01:53:09 -0800 Subject: [PATCH 05/22] Update version --- DLSv2/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DLSv2/Properties/AssemblyInfo.cs b/DLSv2/Properties/AssemblyInfo.cs index 8b3daa7..0bfb115 100644 --- a/DLSv2/Properties/AssemblyInfo.cs +++ b/DLSv2/Properties/AssemblyInfo.cs @@ -14,5 +14,5 @@ [assembly: Guid("64295692-3277-4187-bccf-0af4cb1f9350")] -[assembly: AssemblyVersion("2.0.0.2")] -[assembly: AssemblyFileVersion("2.0.0.2")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.1.0")] +[assembly: AssemblyFileVersion("2.0.1.0")] \ No newline at end of file From 9cf940c9d25364ae3513932175d145eaee29cce5 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Thu, 20 Feb 2025 23:37:09 -0800 Subject: [PATCH 06/22] Added sequencer condition --- DLSv2/Conditions/SequenceConditions.cs | 31 ++++++++++++++++++++++++++ DLSv2/DLSv2.csproj | 1 + DLSv2/Utils/Game/SirenInstance.cs | 20 +++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 DLSv2/Conditions/SequenceConditions.cs diff --git a/DLSv2/Conditions/SequenceConditions.cs b/DLSv2/Conditions/SequenceConditions.cs new file mode 100644 index 0000000..16e8671 --- /dev/null +++ b/DLSv2/Conditions/SequenceConditions.cs @@ -0,0 +1,31 @@ +using System.Xml.Serialization; + +namespace DLSv2.Conditions; + +using Core; +using System.Linq; + +public class SequenceBeatCondition : VehicleCondition +{ + [XmlAttribute("sequence")] + public string Sequence + { + get => string.Concat(boolSequence.Select(x => x ? '1' : '0')); + + set + { + boolSequence = value.Where(c => (c == '1' || c == '0')).Select(c => c == '1').ToArray(); + } + } + + internal bool[] boolSequence; + + protected override bool Evaluate(ManagedVehicle veh) + { + return + boolSequence.Length > 0 + && veh.sirenInstance.TotalSirenBeats >= 0 + && boolSequence[veh.sirenInstance.TotalSirenBeats % boolSequence.Length]; + } +} + diff --git a/DLSv2/DLSv2.csproj b/DLSv2/DLSv2.csproj index 3683b0e..8318585 100644 --- a/DLSv2/DLSv2.csproj +++ b/DLSv2/DLSv2.csproj @@ -54,6 +54,7 @@ + diff --git a/DLSv2/Utils/Game/SirenInstance.cs b/DLSv2/Utils/Game/SirenInstance.cs index fbb4864..f4deb61 100644 --- a/DLSv2/Utils/Game/SirenInstance.cs +++ b/DLSv2/Utils/Game/SirenInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Runtime.InteropServices; using Rage; using Rage.Attributes; @@ -118,6 +119,25 @@ private static void Command_SetSirenOnTime(uint time) s.SetSirenOnTime(time); Game.DisplayNotification($"Reset siren on time to {time}"); } + + [ConsoleCommand(Name = "DebugActiveSirens")] + private static void Command_DebugActiveSirens() + { + Vehicle v = Game.LocalPlayer.Character.CurrentVehicle; + if (!v) return; + + var s = new SirenInstance(v); + while(v) + { + if (v.IsSirenOn && s.CurrentSirenBeat >= 0) + { + int i = s.CurrentSirenBeat; + string info = string.Concat(v.EmergencyLighting.Lights.Select(l => l.FlashinessSequence[i] == '1' ? "~r~x~w~" : "~c~x~w~")); + Game.DisplaySubtitle(info, 100); + } + GameFiber.Yield(); + } + } #endif } } From a61c04589a65a7378ad595e6b7ffad54e293fd28 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Fri, 21 Feb 2025 00:55:42 -0800 Subject: [PATCH 07/22] Fixed and optimizations to better support sequence condition - Consolidated multiple update threads into one vehicle manager thread - Modified update lights to only get called at most once per tick, instead of calling multiple times if multiple conditions changed at the same time - Significant updates to apply modes and apply siren settings to improve efficiency - Updated texturename to not require hash/lookup every time it's called --- DLSv2/Core/DLSModel.cs | 23 ++++++--- DLSv2/Core/ManagedVehicle.cs | 34 ++++++++---- DLSv2/Core/SirenApply.cs | 88 ++++++++++++++++---------------- DLSv2/Core/SirenSetting.cs | 28 ++++++++++ DLSv2/DLSv2.csproj | 4 +- DLSv2/EntryPoint.cs | 19 ++----- DLSv2/Properties/AssemblyInfo.cs | 4 +- DLSv2/Threads/AIManager.cs | 17 ------ DLSv2/Threads/CleanupManager.cs | 49 ------------------ DLSv2/Threads/SequenceManager.cs | 25 --------- DLSv2/Threads/TriggersManager.cs | 20 -------- DLSv2/Threads/VehicleManager.cs | 56 ++++++++++++++++++++ DLSv2/Utils/DLSExtensions.cs | 43 +++++++++++----- 13 files changed, 206 insertions(+), 204 deletions(-) delete mode 100644 DLSv2/Threads/CleanupManager.cs delete mode 100644 DLSv2/Threads/SequenceManager.cs delete mode 100644 DLSv2/Threads/TriggersManager.cs create mode 100644 DLSv2/Threads/VehicleManager.cs diff --git a/DLSv2/Core/DLSModel.cs b/DLSv2/Core/DLSModel.cs index bea4a6e..eefa260 100644 --- a/DLSv2/Core/DLSModel.cs +++ b/DLSv2/Core/DLSModel.cs @@ -5,6 +5,7 @@ namespace DLSv2.Core; +using Rage; using Utils; [XmlRoot("Model")] @@ -110,7 +111,7 @@ public class LightMode : BaseMode public List PaintJobs = new(); [XmlElement("SirenSettings", IsNullable = true)] - public SirenSetting SirenSettings = new(); + public SirenSetting SirenSettings; [XmlArray("Sequences", IsNullable = true)] [XmlArrayItem("Item")] @@ -119,26 +120,33 @@ public SequenceItem[] Sequences get => sequences; set { - if (SirenSettings == null) SirenSettings = new SirenSetting(); + foreach (SequenceItem item in value) { + // Convert ID string to individual siren IDs + var ids = item.IDs == "all" ? Enumerable.Range(1, EmergencyLighting.MaxLights).Select(x => x.ToString()) : item.IDs.Split(','); + // Parse siren ID into one or more integers - foreach (string id in item.IDs.Split(',')) + foreach (string id in ids) { // If siren ID string is a head/tail light sequencer, set and continue to next item switch (id) { case "leftHeadLight": + if (SirenSettings == null) SirenSettings = new SirenSetting(); SirenSettings.LeftHeadLightSequencer = new SequencerWrapper(item.StandardSequence); continue; case "rightHeadLight": + if (SirenSettings == null) SirenSettings = new SirenSetting(); SirenSettings.RightHeadLightSequencer = new SequencerWrapper(item.StandardSequence); continue; case "leftTailLight": + if (SirenSettings == null) SirenSettings = new SirenSetting(); SirenSettings.LeftTailLightSequencer = new SequencerWrapper(item.StandardSequence); continue; case "rightTailLight": + if (SirenSettings == null) SirenSettings = new SirenSetting(); SirenSettings.RightTailLightSequencer = new SequencerWrapper(item.StandardSequence); continue; } @@ -146,9 +154,9 @@ public SequenceItem[] Sequences if (int.TryParse(id.Trim(), out int ID)) { if (item.IsExtended) ExtendedSequences.Add(ID, item.Sequence); - - SirenEntry siren = new SirenEntry(ID) { Flashiness = new LightDetailEntry { Sequence = new Sequencer(item.StandardSequence) } }; - SirenSettings.SirenList.Add(siren); + else StandardSequences.Add(ID, item.Sequence); + // SirenEntry siren = new SirenEntry(ID) { Flashiness = new LightDetailEntry { Sequence = new Sequencer(item.StandardSequence) } }; + // SirenSettings.SirenList.Add(siren); } else { $"Mode {Name} siren id {id} is invalid".ToLog(LogLevel.ERROR); @@ -165,6 +173,9 @@ public SequenceItem[] Sequences // key = siren ID, value = extended sequence string internal Dictionary ExtendedSequences = new(); + [XmlIgnore] + internal Dictionary StandardSequences = new(); + public override string ToString() => Name; } diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index 5a131b3..5c72822 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -53,14 +53,18 @@ public ManagedVehicle(Vehicle vehicle) triggersAndRequirements.GetInstance(this).OnInstanceTriggered += (sender, condition, state) => { LightModes[mode.Name].EnabledByTrigger = state; - UpdateLights(); + // Game.LogTrivialDebug($"trigger changed {mode.Name} to {state} by {condition}"); + // UpdateLights(); + lightsNeedUpdate = true; }; // if requirements become false, turn off the mode mode.Requirements.GetInstance(this).OnInstanceTriggered += (sender, condition, state) => { if (!state) LightModes[mode.Name].EnabledByTrigger = false; - UpdateLights(); + // Game.LogTrivialDebug($"requirements changed {mode.Name} to {state} by {condition}"); + // UpdateLights(); + lightsNeedUpdate = true; }; } @@ -448,9 +452,11 @@ public void RegisterInputs() } } + internal bool lightsNeedUpdate = false; public void UpdateLights() { if (!Vehicle) return; + lightsNeedUpdate = false; // Ensure correct emergency lighting instance is set eL = Vehicle.GetDLSEmergencyLighting(); @@ -510,6 +516,8 @@ public void UpdateLights() // Sets EL with appropriate modes this.ApplyLightModes(modes); + + // Game.LogTrivialDebug("Updated lights"); } public void UpdateAudio() @@ -579,15 +587,17 @@ public void UpdateAudio() } private int lastSeqChangedBeat = -1; - public void ProcessSequences() + public void ProcessExtendedSequences(bool force = false) { - // Only process if lights are on - if (!LightsOn) return; + // Only process if starting on beat 0, beat 16, or forced + if (sirenInstance.TotalSirenBeats <= 0) return; + if (!force && sirenInstance.CurrentSirenBeat % 16 != 0) return; + if (!force && sirenInstance.CurrentSirenBeat == lastSeqChangedBeat) return; - // Only process if starting beat 0 - if (!(sirenInstance.CurrentSirenBeat % 16 == 0 && sirenInstance.CurrentSirenBeat != lastSeqChangedBeat && sirenInstance.TotalSirenBeats > 0)) return; lastSeqChangedBeat = sirenInstance.CurrentSirenBeat; + // if (force) Game.LogTrivialDebug("force process extended sequences"); + foreach (var seqItem in extendedSequences) { if (seqItem.Key > eL.Lights.Length) return; @@ -597,10 +607,11 @@ public void ProcessSequences() seq += seq + seq; string newSeq; + int a = ((sirenInstance.TotalSirenBeats - sirenInstance.CurrentSirenBeat) % l); + // update on beat 16 to avoid flickering when the first bit changes - if (sirenInstance.CurrentSirenBeat == 16) + if (sirenInstance.CurrentSirenBeat >= 16) { - int a = ((sirenInstance.TotalSirenBeats - 16) % l); int b = a + 16; int c = a + 32; // Game.LogTrivialDebug($"a = {a}, b = {b}, c = {c}"); @@ -608,11 +619,12 @@ public void ProcessSequences() newSeq = seq.Substring(c, 16) + seq.Substring(b, 16); } else { - int a = (sirenInstance.TotalSirenBeats % l); newSeq = seq.Substring(a, 32); } - eL.Lights[seqItem.Key - 1].FlashinessSequence = newSeq; + int i = seqItem.Key - 1; + if (i < eL.Lights.Length) eL.Lights[i].FlashinessSequence = newSeq; + // Game.LogTrivialDebug($"[{sirenInstance.CurrentSirenBeat} : {sirenInstance.TotalSirenBeats}] Siren {seqItem.Key} = {newSeq}"); } } diff --git a/DLSv2/Core/SirenApply.cs b/DLSv2/Core/SirenApply.cs index d16c7cc..f7479f4 100644 --- a/DLSv2/Core/SirenApply.cs +++ b/DLSv2/Core/SirenApply.cs @@ -7,23 +7,23 @@ internal static class SirenApply { public static void ApplySirenSettingsToEmergencyLighting(SirenSetting setting, EmergencyLighting els) { - els.TimeMultiplier = setting.TimeMultiplier?.Value ?? els.TimeMultiplier; - els.LightFalloffMax = setting.LightFalloffMax?.Value ?? els.LightFalloffMax; - els.LightFalloffExponent = setting.LightFalloffExponent?.Value ?? els.LightFalloffExponent; - els.LightInnerConeAngle = setting.LightInnerConeAngle?.Value ?? els.LightInnerConeAngle; - els.LightOuterConeAngle = setting.LightOuterConeAngle?.Value ?? els.LightOuterConeAngle; - els.LightOffset = setting.LightOffset?.Value ?? els.LightOffset; - els.TextureHash = setting.TextureHash ?? els.TextureHash; - els.SequencerBpm = setting.SequencerBPM?.Value ?? els.SequencerBpm; - els.UseRealLights = setting.UseRealLights?.Value ?? els.UseRealLights; - els.LeftHeadLightSequenceRaw = setting.LeftHeadLightSequencer?.Sequencer?.SequenceRaw ?? els.LeftHeadLightSequenceRaw; - els.LeftHeadLightMultiples = setting.LeftHeadLightMultiples?.Value ?? els.LeftHeadLightMultiples; - els.RightHeadLightSequenceRaw = setting.RightHeadLightSequencer?.Sequencer?.SequenceRaw ?? els.RightHeadLightSequenceRaw; - els.RightHeadLightMultiples = setting.RightHeadLightMultiples?.Value ?? els.RightHeadLightMultiples; - els.LeftTailLightSequenceRaw = setting.LeftTailLightSequencer?.Sequencer?.Value ?? els.LeftTailLightSequenceRaw; - els.LeftTailLightMultiples = setting.LeftTailLightMultiples?.Value ?? els.LeftTailLightMultiples; - els.RightTailLightSequenceRaw = setting.RightTailLightSequencer?.Sequencer?.Value ?? els.RightTailLightSequenceRaw; - els.RightTailLightMultiples = setting.RightTailLightMultiples?.Value ?? els.RightTailLightMultiples; + if (setting.TimeMultiplier != null) els.TimeMultiplier = setting.TimeMultiplier.Value; + if (setting.LightFalloffMax != null) els.LightFalloffMax = setting.LightFalloffMax.Value; + if (setting.LightFalloffExponent != null) els.LightFalloffExponent = setting.LightFalloffExponent.Value; + if (setting.LightInnerConeAngle != null) els.LightInnerConeAngle = setting.LightInnerConeAngle.Value; + if (setting.LightOuterConeAngle != null) els.LightOuterConeAngle = setting.LightOuterConeAngle.Value; + if (setting.LightOffset != null) els.LightOffset = setting.LightOffset.Value; + if (setting.TextureHash != null) els.TextureHash = setting.TextureHash.Value; + if (setting.SequencerBPM != null) els.SequencerBpm = setting.SequencerBPM.Value; + if (setting.UseRealLights != null) els.UseRealLights = setting.UseRealLights.Value; + if (setting.LeftHeadLightSequencer != null) els.LeftHeadLightSequenceRaw = setting.LeftHeadLightSequencer.Sequencer.Value; + if (setting.LeftHeadLightMultiples != null) els.LeftHeadLightMultiples = setting.LeftHeadLightMultiples.Value; + if (setting.RightHeadLightSequencer != null) els.RightHeadLightSequenceRaw = setting.RightHeadLightSequencer.Sequencer.Value; + if (setting.RightHeadLightMultiples != null) els.RightHeadLightMultiples = setting.RightHeadLightMultiples.Value; + if (setting.LeftTailLightSequencer != null) els.LeftTailLightSequenceRaw = setting.LeftTailLightSequencer.Sequencer.Value; + if (setting.LeftTailLightMultiples != null) els.LeftTailLightMultiples = setting.LeftTailLightMultiples.Value; + if (setting.RightTailLightSequencer != null) els.RightTailLightSequenceRaw = setting.RightTailLightSequencer.Sequencer.Value; + if (setting.RightTailLightMultiples != null) els.RightTailLightMultiples = setting.RightTailLightMultiples.Value; foreach (SirenEntry entry in setting.Sirens) { @@ -41,39 +41,39 @@ public static void ApplySirenSettingsToEmergencyLighting(SirenSetting setting, E // Main light settings light.Color = entry.LightColor ?? light.Color; - light.Intensity = entry.Intensity?.Value ?? light.Intensity; - light.LightGroup = entry.LightGroup?.Value ?? light.LightGroup; - light.Rotate = entry.Rotate?.Value ?? light.Rotate; - light.Scale = entry.Scale?.Value ?? light.Scale; - light.ScaleFactor = entry.ScaleFactor?.Value ?? light.ScaleFactor; - light.Flash = entry.Flash?.Value ?? light.Flash; - light.SpotLight = entry.SpotLight?.Value ?? light.SpotLight; - light.CastShadows = entry.CastShadows?.Value ?? light.CastShadows; - light.Light = entry.Light?.Value ?? light.Light; + if (entry.Intensity != null) light.Intensity = entry.Intensity.Value; + if (entry.LightGroup != null) light.LightGroup = entry.LightGroup.Value; + if (entry.Rotate != null) light.Rotate = entry.Rotate.Value; + if (entry.Scale != null) light.Scale = entry.Scale.Value; + if (entry.ScaleFactor != null) light.ScaleFactor = entry.ScaleFactor.Value; + if (entry.Flash != null) light.Flash = entry.Flash.Value; + if (entry.SpotLight != null) light.SpotLight = entry.SpotLight.Value; + if (entry.CastShadows != null) light.CastShadows = entry.CastShadows.Value; + if (entry.Light != null) light.Light = entry.Light.Value; // Corona settings - light.CoronaIntensity = entry.Corona.CoronaIntensity?.Value ?? light.CoronaIntensity; - light.CoronaSize = entry.Corona.CoronaSize?.Value ?? light.CoronaSize; - light.CoronaPull = entry.Corona.CoronaPull?.Value ?? light.CoronaPull; - light.CoronaFaceCamera = entry.Corona.CoronaFaceCamera?.Value ?? light.CoronaFaceCamera; + if (entry.Corona.CoronaIntensity != null) light.CoronaIntensity = entry.Corona.CoronaIntensity.Value; + if (entry.Corona.CoronaSize != null) light.CoronaSize = entry.Corona.CoronaSize.Value; + if (entry.Corona.CoronaPull != null) light.CoronaPull = entry.Corona.CoronaPull.Value; + if (entry.Corona.CoronaFaceCamera != null) light.CoronaFaceCamera = entry.Corona.CoronaFaceCamera.Value; // Rotation settings - light.RotationDelta = entry.Rotation.DeltaDeg ?? light.RotationDelta; - light.RotationStart = entry.Rotation.StartDeg ?? light.RotationStart; - light.RotationSpeed = entry.Rotation.Speed?.Value ?? light.RotationSpeed; - light.RotationSequenceRaw = entry.Rotation.Sequence ?? light.RotationSequenceRaw; - light.RotationMultiples = entry.Rotation.Multiples?.Value ?? light.RotationMultiples; - light.RotationDirection = entry.Rotation.Direction?.Value ?? light.RotationDirection; - light.RotationSynchronizeToBpm = entry.Rotation.SyncToBPM?.Value ?? light.RotationSynchronizeToBpm; + if (entry.Rotation.DeltaDeg.HasValue) light.RotationDelta = entry.Rotation.DeltaDeg.Value; + if (entry.Rotation.StartDeg.HasValue) light.RotationStart = entry.Rotation.StartDeg.Value; + if (entry.Rotation.Speed != null) light.RotationSpeed = entry.Rotation.Speed.Value; + if (entry.Rotation.Sequence != null) light.RotationSequenceRaw = entry.Rotation.Sequence; + if (entry.Rotation.Multiples != null) light.RotationMultiples = entry.Rotation.Multiples.Value; + if (entry.Rotation.Direction != null) light.RotationDirection = entry.Rotation.Direction.Value; + if (entry.Rotation.SyncToBPM != null) light.RotationSynchronizeToBpm = entry.Rotation.SyncToBPM.Value; // Flash settings - light.FlashinessDelta = entry.Flashiness.DeltaDeg ?? light.FlashinessDelta; - light.FlashinessStart = entry.Flashiness.StartDeg ?? light.FlashinessStart; - light.FlashinessSpeed = entry.Flashiness.Speed?.Value ?? light.FlashinessSpeed; - light.FlashinessSequenceRaw = entry.Flashiness.Sequence ?? light.FlashinessSequenceRaw; - light.FlashinessMultiples = entry.Flashiness.Multiples?.Value ?? light.FlashinessMultiples; - light.FlashinessDirection = entry.Flashiness.Direction?.Value ?? light.FlashinessDirection; - light.FlashinessSynchronizeToBpm = entry.Flashiness.SyncToBPM?.Value ?? light.FlashinessSynchronizeToBpm; + if (entry.Flashiness.DeltaDeg.HasValue) light.FlashinessDelta = entry.Flashiness.DeltaDeg.Value; + if (entry.Flashiness.StartDeg.HasValue) light.FlashinessStart = entry.Flashiness.StartDeg.Value; + if (entry.Flashiness.Speed != null) light.FlashinessSpeed = entry.Flashiness.Speed.Value; + if (entry.Flashiness.Sequence != null) light.FlashinessSequenceRaw = entry.Flashiness.Sequence; + if (entry.Flashiness.Multiples != null) light.FlashinessMultiples = entry.Flashiness.Multiples.Value; + if (entry.Flashiness.Direction != null) light.FlashinessDirection = entry.Flashiness.Direction.Value; + if (entry.Flashiness.SyncToBPM != null) light.FlashinessSynchronizeToBpm = entry.Flashiness.SyncToBPM.Value; } } } diff --git a/DLSv2/Core/SirenSetting.cs b/DLSv2/Core/SirenSetting.cs index c20e45d..995e38c 100644 --- a/DLSv2/Core/SirenSetting.cs +++ b/DLSv2/Core/SirenSetting.cs @@ -28,6 +28,33 @@ public class SirenSetting [XmlElement("lightOffset", IsNullable = true)] public ValueItem LightOffset { get; set; } + private string textureName; + private uint? textureHash; + + [XmlElement("textureName", IsNullable = true)] + public string TextureName + { + get => textureName; + set + { + textureName = value; + textureHash = Core.TextureHash.StringToHash(value); + } + } + + [XmlIgnore] + public uint? TextureHash + { + get => textureHash; + + set + { + TextureName = value.HasValue ? Core.TextureHash.HashToString(value.Value) : null; + textureHash = value; + } + } + + /* [XmlElement("textureName", IsNullable = true)] public string TextureName { get; set; } @@ -38,6 +65,7 @@ public uint? TextureHash set => TextureName = value.HasValue ? Core.TextureHash.HashToString(value.Value) : null; } + */ [XmlElement("sequencerBpm", IsNullable = true)] public ValueItem SequencerBPM { get; set; } diff --git a/DLSv2/DLSv2.csproj b/DLSv2/DLSv2.csproj index 8318585..c705ce4 100644 --- a/DLSv2/DLSv2.csproj +++ b/DLSv2/DLSv2.csproj @@ -59,7 +59,7 @@ - + @@ -88,10 +88,8 @@ - - diff --git a/DLSv2/EntryPoint.cs b/DLSv2/EntryPoint.cs index 8596615..1e72925 100644 --- a/DLSv2/EntryPoint.cs +++ b/DLSv2/EntryPoint.cs @@ -48,11 +48,6 @@ public static void Main() GameFiber.StartNew(CachedGameTime.Process, "DLS - GameTime Cache"); "Loaded: DLS - GameTime Cache Thread".ToLog(); - // Creates Triggers manager - "Loading: DLS - Triggers Manager".ToLog(); - GameFiber.StartNew(TriggersManager.Process, "DLS - Triggers Manager"); - "Loaded: DLS - Triggers Manager".ToLog(); - // Loads DLS Models DLSModels = Loaders.ParseVCFs(); @@ -66,21 +61,15 @@ public static void Main() GameFiber.StartNew(PlayerManager.MainLoop, "DLS - Player Controller"); "Loaded: DLS - Player Controller".ToLog(); - // Creates cleanup manager - "Loading: DLS - Cleanup Manager".ToLog(); - GameFiber.StartNew(Threads.CleanupManager.Process, "DLS - Cleanup Manager"); - "Loaded: DLS - Cleanup Manager".ToLog(); - // Creates AI manager "Loading: DLS - AI Manager".ToLog(); GameFiber.StartNew(AiManager.ScanProcess, "DLS - AI Manager Scan"); - GameFiber.StartNew(AiManager.MonitorProcess, "DLS - AI Manager Monitor"); "Loaded: DLS - AI Manager".ToLog(); - // Creates Extended Sequence manager - "Loading: DLS - Sequence Manager".ToLog(); - GameFiber.StartNew(SequenceManager.Process, "DLS - Sequence Manager"); - "Loaded: DLS - Sequence Manager".ToLog(); + // Creates Managed Vehicle process + "Loading: DLS - Vehicle Manager".ToLog(); + GameFiber.StartNew(VehicleManager.Process, "DLS - Vehicle Manager"); + "Loaded: DLS - Vehicle Manager".ToLog(); //If extra patch is enabled diff --git a/DLSv2/Properties/AssemblyInfo.cs b/DLSv2/Properties/AssemblyInfo.cs index 0bfb115..c2f5b95 100644 --- a/DLSv2/Properties/AssemblyInfo.cs +++ b/DLSv2/Properties/AssemblyInfo.cs @@ -14,5 +14,5 @@ [assembly: Guid("64295692-3277-4187-bccf-0af4cb1f9350")] -[assembly: AssemblyVersion("2.0.1.0")] -[assembly: AssemblyFileVersion("2.0.1.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.2.0")] +[assembly: AssemblyFileVersion("2.0.2.0")] \ No newline at end of file diff --git a/DLSv2/Threads/AIManager.cs b/DLSv2/Threads/AIManager.cs index ef295e4..6b50b31 100644 --- a/DLSv2/Threads/AIManager.cs +++ b/DLSv2/Threads/AIManager.cs @@ -40,21 +40,4 @@ public static void ScanProcess() lastScanTime = CachedGameTime.GameTime; } } - - public static void MonitorProcess() - { - while (true) - { - foreach (var mv in Entrypoint.ManagedVehicles) - { - if (!mv.Key) continue; - if (mv.Value.LightsOn == mv.Key.IsSirenOn) continue; - - mv.Value.LightsOn = mv.Key.IsSirenOn; - mv.Value.UpdateLights(); - } - - GameFiber.Sleep(timeBetweenMonitor); - } - } } \ No newline at end of file diff --git a/DLSv2/Threads/CleanupManager.cs b/DLSv2/Threads/CleanupManager.cs deleted file mode 100644 index 2686740..0000000 --- a/DLSv2/Threads/CleanupManager.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Rage; -using System; -using System.Linq; - -namespace DLSv2.Threads; - -using Core; -using Utils; - -internal class CleanupManager -{ - static uint lastProcessTime = CachedGameTime.GameTime; - static int timeBetweenChecks = 10000; - static int yieldAfterChecks = 10; - public static void Process() - { - while (true) - { - int checksDone = 0; - - foreach (ManagedVehicle managedVehicle in Entrypoint.ManagedVehicles.Values.ToList()) - { - if (!managedVehicle.Vehicle) - { - // Removes from Managed Vehicles - Entrypoint.ManagedVehicles.Remove(managedVehicle.Vehicle); - - // Adds EL to available pool, if used - if (Entrypoint.ELUsedPool.ContainsKey(managedVehicle.VehicleHandle)) - { - ("Moving " + managedVehicle.VehicleHandle + " to Available Pool").ToLog(); - Entrypoint.ELAvailablePool.Add(Entrypoint.ELUsedPool[managedVehicle.VehicleHandle]); - Entrypoint.ELUsedPool.Remove(managedVehicle.VehicleHandle); - } - - // Clears all sound IDs - foreach (var soundId in managedVehicle.SoundIds.ToList()) - managedVehicle.StopMode(soundId.Key); - } - - checksDone++; - if (checksDone % yieldAfterChecks == 0) - GameFiber.Yield(); - } - GameFiber.Sleep((int)Math.Max(timeBetweenChecks, CachedGameTime.GameTime - lastProcessTime)); - lastProcessTime = CachedGameTime.GameTime; - } - } -} \ No newline at end of file diff --git a/DLSv2/Threads/SequenceManager.cs b/DLSv2/Threads/SequenceManager.cs deleted file mode 100644 index c688a4f..0000000 --- a/DLSv2/Threads/SequenceManager.cs +++ /dev/null @@ -1,25 +0,0 @@ -using DLSv2.Core; -using Rage; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace DLSv2.Threads -{ - internal class SequenceManager - { - internal static void Process() - { - while (true) - { - foreach (ManagedVehicle mv in Entrypoint.ManagedVehicles.Values) - { - if (mv.Vehicle) mv.ProcessSequences(); - } - GameFiber.Yield(); - } - } - } -} diff --git a/DLSv2/Threads/TriggersManager.cs b/DLSv2/Threads/TriggersManager.cs deleted file mode 100644 index 109f8b8..0000000 --- a/DLSv2/Threads/TriggersManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Rage; - -namespace DLSv2.Threads; - -using Core; - -internal class TriggersManager -{ - public static void Process() - { - while (true) - { - foreach (ManagedVehicle mv in Entrypoint.ManagedVehicles.Values) - { - if (mv.Vehicle) foreach (BaseCondition condition in mv.Conditions) condition.Update(mv); - } - GameFiber.Yield(); - } - } -} \ No newline at end of file diff --git a/DLSv2/Threads/VehicleManager.cs b/DLSv2/Threads/VehicleManager.cs new file mode 100644 index 0000000..d9265ee --- /dev/null +++ b/DLSv2/Threads/VehicleManager.cs @@ -0,0 +1,56 @@ +using Rage; +using System.Linq; + +namespace DLSv2.Threads; +using Core; +using Utils; + +internal static class VehicleManager +{ + internal static void Process() + { + while(true) + { + foreach (ManagedVehicle mv in Entrypoint.ManagedVehicles.Values.ToArray()) + { + // Check if the vehicle is still valid + if (mv.Vehicle) + { + // Update triggers and conditions + foreach (BaseCondition condition in mv.Conditions) condition.Update(mv); + + // Check if DLS light status matches vehicle siren status + if (mv.LightsOn != mv.Vehicle.IsSirenOn) + { + // If status does not match, force update lights now + mv.LightsOn = mv.Vehicle.IsSirenOn; + // Change this to set waiting for update = true + mv.UpdateLights(); + } else if (mv.LightsOn) + { + // If DLS lights enabled, process sequences and updates + if (mv.lightsNeedUpdate) mv.UpdateLights(); + else mv.ProcessExtendedSequences(false); + } + } else + { + // Vehicle is no longer valid. Do cleanup. + Entrypoint.ManagedVehicles.Remove(mv.Vehicle); + + // Adds EL to available pool, if used + if (Entrypoint.ELUsedPool.ContainsKey(mv.VehicleHandle)) + { + ("Moving " + mv.VehicleHandle + " to Available Pool").ToLog(); + Entrypoint.ELAvailablePool.Add(Entrypoint.ELUsedPool[mv.VehicleHandle]); + Entrypoint.ELUsedPool.Remove(mv.VehicleHandle); + } + + // Clears all sound IDs + foreach (var soundId in mv.SoundIds.ToArray()) + mv.StopMode(soundId.Key); + } + } + GameFiber.Yield(); + } + } +} diff --git a/DLSv2/Utils/DLSExtensions.cs b/DLSv2/Utils/DLSExtensions.cs index b9b76b9..9d807aa 100644 --- a/DLSv2/Utils/DLSExtensions.cs +++ b/DLSv2/Utils/DLSExtensions.cs @@ -186,31 +186,43 @@ public static void ApplyLightModes(this ManagedVehicle managedVehicle, List(); Animation anim = null; var paints = new Dictionary(); + var sequences = new Dictionary(); foreach (var mode in modes) { - // remove any overridden sequences from extended sequence list - foreach (var siren in mode.SirenSettings.Sirens) - if (siren.Flashiness?.Sequence?.Sequence != null) - foreach (int sirenID in siren.sirenIDs) - managedVehicle.extendedSequences.Remove(sirenID); - - // add extended sequences - foreach (var seq in mode.ExtendedSequences) - managedVehicle.extendedSequences[seq.Key] = seq.Value; - // apply siren settings including regular sequences if (mode.ApplyDefaultSirenSettings && vehicle.DefaultEmergencyLighting != null) eL.Copy(vehicle.DefaultEmergencyLighting); - SirenApply.ApplySirenSettingsToEmergencyLighting(mode.SirenSettings, eL); + + if (mode.SirenSettings != null) + { + // remove any overridden sequences from extended sequence list + foreach (var siren in mode.SirenSettings.Sirens) + if (siren.Flashiness?.Sequence?.Sequence != null) + foreach (int sirenID in siren.sirenIDs) + managedVehicle.extendedSequences.Remove(sirenID); + + // apply siren settings if specified + SirenApply.ApplySirenSettingsToEmergencyLighting(mode.SirenSettings, eL); + } + // add regular sequences + foreach (var seq in mode.StandardSequences) + { + int i = seq.Key - 1; + if (i < eL.Lights.Length) eL.Lights[i].FlashinessSequence = seq.Value; + managedVehicle.extendedSequences.Remove(seq.Key); + } + + // add extended sequences + foreach (var seq in mode.ExtendedSequences) + managedVehicle.extendedSequences[seq.Key] = seq.Value; // Sets the extras for the specific mode foreach (var extra in mode.Extra) extras[extra.ID] = extra.Enabled; - // Sets modkits for the specific mode foreach (var kit in mode.ModKits) if (vehicle.HasModkitMod(kit.Type) && vehicle.GetModkitModCount(kit.Type) > kit.Index) @@ -304,6 +316,13 @@ public static void ApplyLightModes(this ManagedVehicle managedVehicle, List 0) + { + // Game.LogTrivialDebug("force update extended sequence"); + managedVehicle.ProcessExtendedSequences(true); + } + vehicle.ShouldVehiclesYieldToThisVehicle = shouldYield; managedVehicle.Vehicle.EmergencyLightingOverride = eL; From dcbd7cb34d6c74114a30f4d909a82cca236178e0 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Wed, 26 Feb 2025 23:33:15 -0800 Subject: [PATCH 08/22] Fixed sequencers not processing on first beat --- DLSv2/Core/ManagedVehicle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index 5c72822..fa98518 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -590,7 +590,7 @@ public void UpdateAudio() public void ProcessExtendedSequences(bool force = false) { // Only process if starting on beat 0, beat 16, or forced - if (sirenInstance.TotalSirenBeats <= 0) return; + if (sirenInstance.TotalSirenBeats < 0) return; if (!force && sirenInstance.CurrentSirenBeat % 16 != 0) return; if (!force && sirenInstance.CurrentSirenBeat == lastSeqChangedBeat) return; From 268f0f91400c3baa28e68fa584b1bae1864e0ff4 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Wed, 26 Feb 2025 23:37:31 -0800 Subject: [PATCH 09/22] Fixed lights not getting updated if not already enabled --- DLSv2/Threads/VehicleManager.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/DLSv2/Threads/VehicleManager.cs b/DLSv2/Threads/VehicleManager.cs index d9265ee..6fef989 100644 --- a/DLSv2/Threads/VehicleManager.cs +++ b/DLSv2/Threads/VehicleManager.cs @@ -26,11 +26,17 @@ internal static void Process() mv.LightsOn = mv.Vehicle.IsSirenOn; // Change this to set waiting for update = true mv.UpdateLights(); - } else if (mv.LightsOn) + } + else if (mv.lightsNeedUpdate) + { + // Process updates if required + // UpdateLights calls ProcessExtendedSequences already + mv.UpdateLights(); + } + else if (mv.LightsOn) { - // If DLS lights enabled, process sequences and updates - if (mv.lightsNeedUpdate) mv.UpdateLights(); - else mv.ProcessExtendedSequences(false); + // Process extended sequences if lights are enabled and no updates required + mv.ProcessExtendedSequences(false); } } else { From 54f8f0e1096e2d31118f1f379abfaa54a765664b Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Thu, 20 Mar 2025 23:51:32 -0700 Subject: [PATCH 10/22] Prevent deadlock if sequence is blank --- DLSv2/Core/DLSModel.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DLSv2/Core/DLSModel.cs b/DLSv2/Core/DLSModel.cs index eefa260..0bd7e0d 100644 --- a/DLSv2/Core/DLSModel.cs +++ b/DLSv2/Core/DLSModel.cs @@ -262,10 +262,15 @@ public string Sequence set { // If initial length is less than 32 bits, repeat the sequence until it's over the min length - string seq = value; + string origSeq = value; + origSeq = string.Concat(origSeq.Where(x => x == '1' || x == '0')); + if (string.IsNullOrEmpty(origSeq)) origSeq = "0"; + + string seq = origSeq; + while(seq.Length < 32) { - seq += value; + seq += origSeq; } sequence = seq; } From f795047a329fba996ff236bab156f72343eda76a Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Fri, 21 Mar 2025 00:36:59 -0700 Subject: [PATCH 11/22] Fixed delta/start deg getting set incorrectly --- DLSv2/Core/SirenApply.cs | 2 +- DLSv2/Core/SirenSetting.cs | 4 ++-- DLSv2/Utils/DLSExtensions.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DLSv2/Core/SirenApply.cs b/DLSv2/Core/SirenApply.cs index f7479f4..9273ef2 100644 --- a/DLSv2/Core/SirenApply.cs +++ b/DLSv2/Core/SirenApply.cs @@ -40,7 +40,7 @@ public static void ApplySirenSettingsToEmergencyLighting(SirenSetting setting, E EmergencyLight light = els.Lights[id - 1]; // Main light settings - light.Color = entry.LightColor ?? light.Color; + if (entry.LightColor != null) light.Color = entry.LightColor.Value; if (entry.Intensity != null) light.Intensity = entry.Intensity.Value; if (entry.LightGroup != null) light.LightGroup = entry.LightGroup.Value; if (entry.Rotate != null) light.Rotate = entry.Rotate.Value; diff --git a/DLSv2/Core/SirenSetting.cs b/DLSv2/Core/SirenSetting.cs index 995e38c..0957e1f 100644 --- a/DLSv2/Core/SirenSetting.cs +++ b/DLSv2/Core/SirenSetting.cs @@ -218,7 +218,7 @@ public class LightDetailEntry public float? DeltaDeg { get => DeltaRad == null ? (float?)null : Rage.MathHelper.ConvertRadiansToDegrees(DeltaRad); - set => DeltaRad = value.HasValue ? (float?)null : Rage.MathHelper.ConvertDegreesToRadians(value.Value); + set => DeltaRad = value.HasValue ? Rage.MathHelper.ConvertDegreesToRadians(value.Value) : (float?)null; } [XmlElement("start", IsNullable = true)] @@ -228,7 +228,7 @@ public float? DeltaDeg public float? StartDeg { get => StartRad == null ? (float?) null : Rage.MathHelper.ConvertRadiansToDegrees(StartRad); - set => StartRad = value.HasValue ? (float?)null : Rage.MathHelper.ConvertDegreesToRadians(value.Value); + set => StartRad = value.HasValue ? Rage.MathHelper.ConvertDegreesToRadians(value.Value) : (float?)null; } [XmlElement("speed", IsNullable = true)] diff --git a/DLSv2/Utils/DLSExtensions.cs b/DLSv2/Utils/DLSExtensions.cs index 9d807aa..69fe71c 100644 --- a/DLSv2/Utils/DLSExtensions.cs +++ b/DLSv2/Utils/DLSExtensions.cs @@ -107,7 +107,7 @@ internal static SirenSetting GetDefaultSirenSetting(this Vehicle veh) }, // Flashiness Settings - Flashiness = new LightDetailEntry + Flashiness = new LightDetailEntry() { DeltaDeg = light.FlashinessDelta, StartDeg = light.FlashinessStart, From c4b6cd5835754bd80a63de717c2a4774761f4757 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Tue, 25 Mar 2025 23:42:45 -0700 Subject: [PATCH 12/22] Adding caching of previously-active modes on control groups to resume selected mode when toggling --- DLSv2/Core/Wrappers.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/DLSv2/Core/Wrappers.cs b/DLSv2/Core/Wrappers.cs index 5151c98..6c8da41 100644 --- a/DLSv2/Core/Wrappers.cs +++ b/DLSv2/Core/Wrappers.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DLSv2.Core; @@ -23,6 +24,7 @@ public abstract class BaseControlGroupInstance public bool Enabled => ActiveIndexes.Count > 0; public List ActiveIndexes = new(); + private int[] InactiveIndexes = new int[] { }; public BaseControlGroupInstance(T cg) { @@ -32,14 +34,21 @@ public BaseControlGroupInstance(T cg) public void Toggle(int newIndex = 0) { if (Enabled) - ActiveIndexes = new(); + { + InactiveIndexes = ActiveIndexes.ToArray(); + ActiveIndexes.Clear(); + } else - ActiveIndexes = new() { newIndex }; + { + if (InactiveIndexes.Length > 0) ActiveIndexes = InactiveIndexes.ToList(); + else ActiveIndexes = new() { newIndex }; + } } public virtual void Disable() { - ActiveIndexes = new(); + InactiveIndexes = ActiveIndexes.ToArray(); + ActiveIndexes.Clear(); } public void MoveToNext(bool cycleOnly = false) From ff8b1f21a5a63f51e388843ce4e2f1ed74b9f517 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Sun, 30 Mar 2025 00:03:51 -0700 Subject: [PATCH 13/22] Add option (enabled by default) for control group condition to return true if any mode in the group is enabled, even if it wasn't enabled directly through the group (e.g. by a trigger) --- DLSv2/Conditions/ModeConditions.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/DLSv2/Conditions/ModeConditions.cs b/DLSv2/Conditions/ModeConditions.cs index 331f77c..4cb73ca 100644 --- a/DLSv2/Conditions/ModeConditions.cs +++ b/DLSv2/Conditions/ModeConditions.cs @@ -3,6 +3,7 @@ namespace DLSv2.Conditions; using Core; +using System.Linq; public class AudioControlGroupCondition : VehicleCondition { @@ -41,12 +42,23 @@ public class LightControlGroupCondition : VehicleCondition public string ControlGroupName { get; set; } [XmlAttribute("active")] - public bool GroupEnabled { get; set; } + public bool GroupEnabled { get; set; } = true; + + [XmlAttribute("any_mode")] + public bool AnyModeInGroup { get; set; } = true; protected override bool Evaluate(ManagedVehicle veh) { - return veh.LightControlGroups.ContainsKey(ControlGroupName) && - veh.LightControlGroups[ControlGroupName].Enabled == GroupEnabled; + if (!veh.LightControlGroups.ContainsKey(ControlGroupName)) + return false; + + var cg = veh.LightControlGroups[ControlGroupName]; + + if (!AnyModeInGroup) + return cg.Enabled == GroupEnabled; + + // If allowed to check any mode in group + return cg.BaseControlGroup.Modes.Any(m => m.Modes.Any(m => veh.LightModes[m].Enabled == GroupEnabled)); } } From 6b01dccf776f540998b253f7afd1c3400a8ebf72 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Sun, 30 Mar 2025 00:07:57 -0700 Subject: [PATCH 14/22] Add DLS overall status to debug output --- DLSv2/Utils/DLSExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DLSv2/Utils/DLSExtensions.cs b/DLSv2/Utils/DLSExtensions.cs index 69fe71c..af17338 100644 --- a/DLSv2/Utils/DLSExtensions.cs +++ b/DLSv2/Utils/DLSExtensions.cs @@ -371,6 +371,10 @@ internal static void DebugCurrentModes(this Vehicle vehicle, bool showConditions ($" {boolToCheck(vehicle.IsSirenSilent)} IsSirenSilent").ToLog(LogLevel.DEVMODE); ($" {boolToCheck(vehicle.ShouldVehiclesYieldToThisVehicle)} ShouldYield").ToLog(LogLevel.DEVMODE); + ("DLS:").ToLog(LogLevel.DEVMODE); + ($" {boolToCheck(managedVehicle.LightsOn)} DLS LightsOn").ToLog(LogLevel.DEVMODE); + ($" {boolToCheck(managedVehicle.SirenOn)} DLS SirenOn").ToLog(LogLevel.DEVMODE); + ("").ToLog(LogLevel.DEVMODE); ("").ToLog(LogLevel.DEVMODE); ("Light Modes:").ToLog(LogLevel.DEVMODE); From a10723c07883b7ef44db5c45f81a07a17c15aa64 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Sun, 30 Mar 2025 00:09:28 -0700 Subject: [PATCH 15/22] Improved handling of default modes when loading DLS or switching between player/non-player vehicle status --- DLSv2/Core/ManagedVehicle.cs | 37 +++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index fa98518..b071bbe 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -54,17 +54,18 @@ public ManagedVehicle(Vehicle vehicle) { LightModes[mode.Name].EnabledByTrigger = state; // Game.LogTrivialDebug($"trigger changed {mode.Name} to {state} by {condition}"); - // UpdateLights(); lightsNeedUpdate = true; }; // if requirements become false, turn off the mode mode.Requirements.GetInstance(this).OnInstanceTriggered += (sender, condition, state) => { - if (!state) LightModes[mode.Name].EnabledByTrigger = false; - // Game.LogTrivialDebug($"requirements changed {mode.Name} to {state} by {condition}"); - // UpdateLights(); - lightsNeedUpdate = true; + if (!state && (LightModes[mode.Name].Enabled || LightModes[mode.Name].EnabledByTrigger)) + { + LightModes[mode.Name].EnabledByTrigger = false; + // Game.LogTrivialDebug($"requirements changed {mode.Name} to {state} by {condition}"); + lightsNeedUpdate = true; + } }; } @@ -99,6 +100,9 @@ public ManagedVehicle(Vehicle vehicle) VehicleOwner.OnIsPlayerVehicleChanged += SetIsPlayerOwned; + // Set initial state of all conditions + foreach (BaseCondition condition in Conditions) condition.Update(this); + SetIsPlayerOwned(vehicle, vehicle.IsPlayerVehicle()); } @@ -110,14 +114,29 @@ private void SetIsPlayerOwned(Vehicle v, bool isPlayerOwned) { v.DisableSirenSounds(); DefaultMode.EnabledByTrigger = false; + if (Vehicle.IsSirenOn) + { + foreach (var cg in LightControlGroups.Values) + { + for (int m = 0; m < cg.BaseControlGroup.Modes.Count; m++) + { + if (cg.BaseControlGroup.Modes[m].Modes.Contains(DefaultMode.BaseMode.Name)) + { + cg.ActiveIndexes.Add(m); + } + } + } + } $"Vehicle {v.Handle.Value.ToString("X")} ({v.Model.Name}) set to player owned".ToLog(LogLevel.DEBUG); } else { bool silent = v.IsSirenSilent; + bool lights = v.IsSirenOn; ClearAll(); v.EnableSirenSounds(); DefaultMode.EnabledByTrigger = true; v.IsSirenSilent = silent; + v.IsSirenOn = lights; $"Vehicle {v.Handle.Value.ToString("X")} ({v.Model.Name}) set to non-player owned".ToLog(LogLevel.DEBUG); } @@ -503,20 +522,20 @@ public void UpdateLights() item.Enabled = false; // If no active modes, clears EL and disables siren - if (modes.Count == 0) + if (modes.Count == 0 || (!Vehicle.IsPlayerVehicle() && !Vehicle.IsSirenOn)) { - if (Vehicle.IsPlayerVehicle() || !Vehicle.IsSirenOn) LightsOn = false; + LightsOn = false; this.ApplyLightModes(new List()); //AudioController.KillSirens(managedVehicle); return; } // Turns on vehicle siren - if (Vehicle.IsPlayerVehicle() || Vehicle.IsSirenOn) LightsOn = true; + LightsOn = true; // Sets EL with appropriate modes this.ApplyLightModes(modes); - + // Game.LogTrivialDebug("Updated lights"); } From 2d482534918a41290dec584e7adee046f3be0cc3 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Sun, 30 Mar 2025 00:10:02 -0700 Subject: [PATCH 16/22] Increment minor version number --- DLSv2/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DLSv2/Properties/AssemblyInfo.cs b/DLSv2/Properties/AssemblyInfo.cs index c2f5b95..4c64b97 100644 --- a/DLSv2/Properties/AssemblyInfo.cs +++ b/DLSv2/Properties/AssemblyInfo.cs @@ -14,5 +14,5 @@ [assembly: Guid("64295692-3277-4187-bccf-0af4cb1f9350")] -[assembly: AssemblyVersion("2.0.2.0")] -[assembly: AssemblyFileVersion("2.0.2.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.3.0")] +[assembly: AssemblyFileVersion("2.0.3.0")] \ No newline at end of file From 5e268f688cf0ea1a2068fc262d5154c02f034bd4 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Sun, 30 Mar 2025 02:11:27 -0700 Subject: [PATCH 17/22] Add condition for R* editor recording --- DLSv2/Conditions/GlobalConditions.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/DLSv2/Conditions/GlobalConditions.cs b/DLSv2/Conditions/GlobalConditions.cs index acdfe3b..625a052 100644 --- a/DLSv2/Conditions/GlobalConditions.cs +++ b/DLSv2/Conditions/GlobalConditions.cs @@ -79,4 +79,15 @@ private bool IsWeather(string weather) NativeFunction.Natives.GET_CURR_WEATHER_STATE(out uint weather1, out uint weather2, out float pctWeather2); return (weather1 == Game.GetHashKey(weather) && pctWeather2 <= 0.5f) || (weather2 == Game.GetHashKey(weather) && pctWeather2 >= 0.5f); } -} \ No newline at end of file +} + +public class RecordingCondition : GlobalCondition +{ + [XmlAttribute("active")] + public bool IsRecording { get; set; } = true; + + protected override bool Evaluate() + { + return NativeFunction.Natives.IS_REPLAY_RECORDING() == IsRecording; + } +} From 3befb0a6758769b4ba11e294f202e6fc3e1c9680 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Wed, 16 Apr 2025 23:38:40 -0700 Subject: [PATCH 18/22] Fix group condition check --- DLSv2/Conditions/ModeConditions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DLSv2/Conditions/ModeConditions.cs b/DLSv2/Conditions/ModeConditions.cs index 4cb73ca..775894a 100644 --- a/DLSv2/Conditions/ModeConditions.cs +++ b/DLSv2/Conditions/ModeConditions.cs @@ -58,7 +58,7 @@ protected override bool Evaluate(ManagedVehicle veh) return cg.Enabled == GroupEnabled; // If allowed to check any mode in group - return cg.BaseControlGroup.Modes.Any(m => m.Modes.Any(m => veh.LightModes[m].Enabled == GroupEnabled)); + return cg.BaseControlGroup.Modes.Any(m => m.Modes.Any(m => veh.LightModes[m].Enabled)) == GroupEnabled; } } From 9d34d71d1b242400f82c18a1baabe24702f362b6 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Thu, 17 Apr 2025 00:11:04 -0700 Subject: [PATCH 19/22] Fixed sirens always being muted for AI vehicles and default siren sounds not getting disabled for vehicles spawned from console --- DLSv2/Core/ManagedVehicle.cs | 5 +++-- DLSv2/Utils/Game/SirenSounds.cs | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index b071bbe..0ff37fa 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -376,7 +376,7 @@ public void RegisterInputs() cG.ActiveIndexes = [index]; else cG.ActiveIndexes.Add(index); - + UpdateAudio(); }; @@ -542,7 +542,8 @@ public void UpdateLights() public void UpdateAudio() { if (!Vehicle) return; - + if (this != ActivePlayerVehicle) return; + // Start with no modes activated List modes = new(); diff --git a/DLSv2/Utils/Game/SirenSounds.cs b/DLSv2/Utils/Game/SirenSounds.cs index 8626516..a3436cd 100644 --- a/DLSv2/Utils/Game/SirenSounds.cs +++ b/DLSv2/Utils/Game/SirenSounds.cs @@ -47,6 +47,7 @@ static SirenSounds() Marshal.StructureToPtr(soundSet, ptr, false); EmptySoundSet = ptr; + $"EmptySoundSet = {ptr}".ToLog(LogLevel.DEBUG); } private static unsafe audSoundSet* GetAudioSoundSetPtr(this Vehicle vehicle) @@ -62,9 +63,21 @@ static SirenSounds() public static unsafe void DisableSirenSounds(this Vehicle vehicle) { if (DefaultSoundSets.ContainsKey(vehicle)) return; - - DefaultSoundSets[vehicle] = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; - $"Setting {vehicle.MemoryAddress}'s SirenSounds to Empty".ToLog(LogLevel.DEBUG); + IntPtr soundSet = soundSet = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; + // Default soundset doesn't get assigned if vehicle is spawned while game is paused + // It takes two ticks for it to get assigned, hence calling yield twice + // Vehicles with no default siren sounds always have soundset = 0, so we + // cannot just check while(soundset==0) or it will never exit the loop + while (soundSet == IntPtr.Zero && (Game.IsPaused || Game.Console.IsOpen)) + { + GameFiber.Yield(); + GameFiber.Yield(); + if (!vehicle) return; + soundSet = soundSet = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; + } + + DefaultSoundSets[vehicle] = soundSet; + $"Setting {vehicle.MemoryAddress}'s SirenSounds to Empty ({EmptySoundSet}), existing SoundSet is {DefaultSoundSets[vehicle]}".ToLog(LogLevel.DEBUG); vehicle.GetAudioSoundSetPtr()->Data = (SoundSet*)EmptySoundSet; } From 3e2b92463145c3d4cab91372a1b09e8e8b2cad2e Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Thu, 17 Apr 2025 00:11:44 -0700 Subject: [PATCH 20/22] Increment version --- DLSv2/Properties/AssemblyInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DLSv2/Properties/AssemblyInfo.cs b/DLSv2/Properties/AssemblyInfo.cs index 4c64b97..7ecc5eb 100644 --- a/DLSv2/Properties/AssemblyInfo.cs +++ b/DLSv2/Properties/AssemblyInfo.cs @@ -14,5 +14,5 @@ [assembly: Guid("64295692-3277-4187-bccf-0af4cb1f9350")] -[assembly: AssemblyVersion("2.0.3.0")] -[assembly: AssemblyFileVersion("2.0.3.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.4.0")] +[assembly: AssemblyFileVersion("2.0.4.0")] \ No newline at end of file From 574c4692d634823e55675d995fddce6178e38877 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Sun, 20 Apr 2025 23:08:46 -0700 Subject: [PATCH 21/22] Added pause/console checks, fix race condition on siren sound disable --- DLSv2/Threads/AIManager.cs | 7 +++++-- DLSv2/Threads/ControlsManager.cs | 2 +- DLSv2/Threads/PlayerManager.cs | 6 ++++-- DLSv2/Threads/VehicleManager.cs | 7 +++++-- DLSv2/Utils/Game/SirenSounds.cs | 16 ++++++++++------ 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/DLSv2/Threads/AIManager.cs b/DLSv2/Threads/AIManager.cs index 6b50b31..04b36c9 100644 --- a/DLSv2/Threads/AIManager.cs +++ b/DLSv2/Threads/AIManager.cs @@ -20,6 +20,11 @@ public static void ScanProcess() { while (true) { + GameFiber.Sleep((int)Math.Max(timeBetweenScans, CachedGameTime.GameTime - lastScanTime)); + lastScanTime = CachedGameTime.GameTime; + + if (Game.IsPaused || Game.Console.IsOpen) continue; + var checksDone = 0; var allVeh = new HashSet(World.GetAllVehicles()); @@ -36,8 +41,6 @@ public static void ScanProcess() if (checksDone % yieldAfterScan == 0) GameFiber.Yield(); } - GameFiber.Sleep((int)Math.Max(timeBetweenScans, CachedGameTime.GameTime - lastScanTime)); - lastScanTime = CachedGameTime.GameTime; } } } \ No newline at end of file diff --git a/DLSv2/Threads/ControlsManager.cs b/DLSv2/Threads/ControlsManager.cs index 1872082..368397f 100644 --- a/DLSv2/Threads/ControlsManager.cs +++ b/DLSv2/Threads/ControlsManager.cs @@ -222,7 +222,7 @@ public static void Process() var keyboardState = Game.GetKeyboardState(); - if (Game.IsPaused || keyboardState == null) continue; + if (Game.IsPaused || Game.Console.IsOpen || keyboardState == null) continue; PressedKeys = keyboardState.PressedKeys; IsTextboxOpen = NativeFunction.Natives.UPDATE_ONSCREEN_KEYBOARD() == 0; diff --git a/DLSv2/Threads/PlayerManager.cs b/DLSv2/Threads/PlayerManager.cs index 65adbbe..4b19ec5 100644 --- a/DLSv2/Threads/PlayerManager.cs +++ b/DLSv2/Threads/PlayerManager.cs @@ -19,6 +19,10 @@ internal static void MainLoop() { while (true) { + GameFiber.Yield(); + + if (Game.IsPaused || Game.Console.IsOpen) continue; + VehicleOwner.Process(); Ped playerPed = Game.LocalPlayer.Character; @@ -105,8 +109,6 @@ internal static void MainLoop() ControlsManager.ClearInputs(); registeredKeys = false; } - - GameFiber.Yield(); } } diff --git a/DLSv2/Threads/VehicleManager.cs b/DLSv2/Threads/VehicleManager.cs index 6fef989..7a0142e 100644 --- a/DLSv2/Threads/VehicleManager.cs +++ b/DLSv2/Threads/VehicleManager.cs @@ -11,6 +11,10 @@ internal static void Process() { while(true) { + GameFiber.Yield(); + + if (Game.IsPaused || Game.Console.IsOpen) continue; + foreach (ManagedVehicle mv in Entrypoint.ManagedVehicles.Values.ToArray()) { // Check if the vehicle is still valid @@ -23,8 +27,8 @@ internal static void Process() if (mv.LightsOn != mv.Vehicle.IsSirenOn) { // If status does not match, force update lights now + // TODO: Update this to smartly toggle lights for player-controlled vehicles mv.LightsOn = mv.Vehicle.IsSirenOn; - // Change this to set waiting for update = true mv.UpdateLights(); } else if (mv.lightsNeedUpdate) @@ -56,7 +60,6 @@ internal static void Process() mv.StopMode(soundId.Key); } } - GameFiber.Yield(); } } } diff --git a/DLSv2/Utils/Game/SirenSounds.cs b/DLSv2/Utils/Game/SirenSounds.cs index a3436cd..0be04b7 100644 --- a/DLSv2/Utils/Game/SirenSounds.cs +++ b/DLSv2/Utils/Game/SirenSounds.cs @@ -63,17 +63,21 @@ static SirenSounds() public static unsafe void DisableSirenSounds(this Vehicle vehicle) { if (DefaultSoundSets.ContainsKey(vehicle)) return; - IntPtr soundSet = soundSet = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; + IntPtr soundSet = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; // Default soundset doesn't get assigned if vehicle is spawned while game is paused // It takes two ticks for it to get assigned, hence calling yield twice // Vehicles with no default siren sounds always have soundset = 0, so we // cannot just check while(soundset==0) or it will never exit the loop - while (soundSet == IntPtr.Zero && (Game.IsPaused || Game.Console.IsOpen)) + + if (soundSet == IntPtr.Zero) { - GameFiber.Yield(); - GameFiber.Yield(); - if (!vehicle) return; - soundSet = soundSet = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; + do + { + GameFiber.Yield(); + GameFiber.Yield(); + if (!vehicle) return; + soundSet = (IntPtr)vehicle.GetAudioSoundSetPtr()->Data; + } while (Game.IsPaused || Game.Console.IsOpen); } DefaultSoundSets[vehicle] = soundSet; From 18c97f8b077c06b01d2e22b2f8f6eebee8fb62b3 Mon Sep 17 00:00:00 2001 From: pnwparksfan Date: Mon, 28 Apr 2025 19:24:31 -0700 Subject: [PATCH 22/22] Misc cleanup, version increment --- DLSv2/Core/DLSModel.cs | 2 -- DLSv2/Core/ManagedVehicle.cs | 11 ++--------- DLSv2/Core/SirenSetting.cs | 13 ------------- DLSv2/Properties/AssemblyInfo.cs | 4 ++-- 4 files changed, 4 insertions(+), 26 deletions(-) diff --git a/DLSv2/Core/DLSModel.cs b/DLSv2/Core/DLSModel.cs index 0bd7e0d..a0ff1e8 100644 --- a/DLSv2/Core/DLSModel.cs +++ b/DLSv2/Core/DLSModel.cs @@ -155,8 +155,6 @@ public SequenceItem[] Sequences { if (item.IsExtended) ExtendedSequences.Add(ID, item.Sequence); else StandardSequences.Add(ID, item.Sequence); - // SirenEntry siren = new SirenEntry(ID) { Flashiness = new LightDetailEntry { Sequence = new Sequencer(item.StandardSequence) } }; - // SirenSettings.SirenList.Add(siren); } else { $"Mode {Name} siren id {id} is invalid".ToLog(LogLevel.ERROR); diff --git a/DLSv2/Core/ManagedVehicle.cs b/DLSv2/Core/ManagedVehicle.cs index 0ff37fa..2b389a0 100644 --- a/DLSv2/Core/ManagedVehicle.cs +++ b/DLSv2/Core/ManagedVehicle.cs @@ -53,7 +53,7 @@ public ManagedVehicle(Vehicle vehicle) triggersAndRequirements.GetInstance(this).OnInstanceTriggered += (sender, condition, state) => { LightModes[mode.Name].EnabledByTrigger = state; - // Game.LogTrivialDebug($"trigger changed {mode.Name} to {state} by {condition}"); + // $"trigger changed {mode.Name} to {state} by {condition}".ToLog(LogLevel.DEBUG); lightsNeedUpdate = true; }; @@ -63,7 +63,7 @@ public ManagedVehicle(Vehicle vehicle) if (!state && (LightModes[mode.Name].Enabled || LightModes[mode.Name].EnabledByTrigger)) { LightModes[mode.Name].EnabledByTrigger = false; - // Game.LogTrivialDebug($"requirements changed {mode.Name} to {state} by {condition}"); + // $"requirements changed {mode.Name} to {state} by {condition}".ToLog(LogLevel.DEBUG); lightsNeedUpdate = true; } }; @@ -535,8 +535,6 @@ public void UpdateLights() // Sets EL with appropriate modes this.ApplyLightModes(modes); - - // Game.LogTrivialDebug("Updated lights"); } public void UpdateAudio() @@ -616,8 +614,6 @@ public void ProcessExtendedSequences(bool force = false) lastSeqChangedBeat = sirenInstance.CurrentSirenBeat; - // if (force) Game.LogTrivialDebug("force process extended sequences"); - foreach (var seqItem in extendedSequences) { if (seqItem.Key > eL.Lights.Length) return; @@ -634,7 +630,6 @@ public void ProcessExtendedSequences(bool force = false) { int b = a + 16; int c = a + 32; - // Game.LogTrivialDebug($"a = {a}, b = {b}, c = {c}"); if (c < 0 || b > seq.Length) continue; newSeq = seq.Substring(c, 16) + seq.Substring(b, 16); } else @@ -644,8 +639,6 @@ public void ProcessExtendedSequences(bool force = false) int i = seqItem.Key - 1; if (i < eL.Lights.Length) eL.Lights[i].FlashinessSequence = newSeq; - - // Game.LogTrivialDebug($"[{sirenInstance.CurrentSirenBeat} : {sirenInstance.TotalSirenBeats}] Siren {seqItem.Key} = {newSeq}"); } } diff --git a/DLSv2/Core/SirenSetting.cs b/DLSv2/Core/SirenSetting.cs index 0957e1f..1efb507 100644 --- a/DLSv2/Core/SirenSetting.cs +++ b/DLSv2/Core/SirenSetting.cs @@ -54,19 +54,6 @@ public uint? TextureHash } } - /* - [XmlElement("textureName", IsNullable = true)] - public string TextureName { get; set; } - - [XmlIgnore] - public uint? TextureHash - { - get => TextureName != null ? Core.TextureHash.StringToHash(TextureName) : (uint?)null; - - set => TextureName = value.HasValue ? Core.TextureHash.HashToString(value.Value) : null; - } - */ - [XmlElement("sequencerBpm", IsNullable = true)] public ValueItem SequencerBPM { get; set; } diff --git a/DLSv2/Properties/AssemblyInfo.cs b/DLSv2/Properties/AssemblyInfo.cs index 7ecc5eb..cf13276 100644 --- a/DLSv2/Properties/AssemblyInfo.cs +++ b/DLSv2/Properties/AssemblyInfo.cs @@ -14,5 +14,5 @@ [assembly: Guid("64295692-3277-4187-bccf-0af4cb1f9350")] -[assembly: AssemblyVersion("2.0.4.0")] -[assembly: AssemblyFileVersion("2.0.4.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.1.0.0")] +[assembly: AssemblyFileVersion("2.1.0.0")] \ No newline at end of file